Добро пожаловать в удивительный мир мобильности ... или, вернее, его отсутствие. Прежде чем приступить к подробному анализу этих двух вариантов и более глубокому взгляду на то, как их обрабатывают различные операционные системы, следует отметить, что реализация сокетов BSD является матерью всех реализаций сокетов. В основном все другие системы в какой-то момент времени копировали реализацию сокета BSD (или, по крайней мере, его интерфейсы), а затем начали развивать ее самостоятельно. Конечно, реализация BSD-сокета также развивалась в то же время, и поэтому системы, которые ее копировали, позже получили функции, которых не хватало в системах, которые копировали ее ранее. Понимание реализации сокетов BSD является ключом к пониманию всех других реализаций сокетов, поэтому вам следует прочитать об этом, даже если вы не хотите писать код для системы BSD.
Есть несколько основ, которые вы должны знать, прежде чем мы рассмотрим эти два варианта. Соединение TCP / UDP идентифицируется кортежем из пяти значений:
{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}
Любая уникальная комбинация этих значений идентифицирует соединение. В результате никакие два соединения не могут иметь одинаковые пять значений, иначе система не сможет больше различать эти соединения.
Протокол сокета устанавливается при создании сокета с помощью socket()
функции. Адрес источника и порт задаются с помощью bind()
функции. Адрес и порт назначения задаются с помощью connect()
функции. Поскольку UDP - это протокол без установления соединения, сокеты UDP можно использовать без их подключения. Тем не менее, их можно подключать, а в некоторых случаях это очень выгодно для вашего кода и общего дизайна приложения. В режиме без установления соединения UDP-сокеты, которые не были явно связаны при первой отправке данных через них, обычно автоматически связываются системой, так как несвязанный сокет UDP не может принимать никакие (ответные) данные. То же самое верно для несвязанного сокета TCP, он автоматически связывается до того, как будет подключен.
Если вы явно привязываете сокет, вы можете привязать его к порту 0
, что означает «любой порт». Поскольку сокет не может быть действительно привязан ко всем существующим портам, система в этом случае должна будет сама выбрать конкретный порт (обычно из предопределенного, определенного для ОС диапазона исходных портов). Подобный подстановочный знак существует для адреса источника, который может быть «любым адресом» ( 0.0.0.0
в случае IPv4 и::
в случае IPv6). В отличие от портов, сокет действительно может быть привязан к «любому адресу», что означает «все исходные IP-адреса всех локальных интерфейсов». Если сокет подключается позже, система должна выбрать конкретный IP-адрес источника, поскольку сокет не может быть подключен и в то же время привязан к любому локальному IP-адресу. В зависимости от адреса назначения и содержимого таблицы маршрутизации система выберет соответствующий исходный адрес и заменит «любую» привязку привязкой к выбранному исходному IP-адресу.
По умолчанию никакие два сокета не могут быть связаны с одной и той же комбинацией адреса источника и порта источника. Пока порт источника отличается, адрес источника на самом деле не имеет значения. Привязка socketA
к A:X
и socketB
к B:Y
, где A
и B
являются адресами и X
и Y
являются портами, всегда возможна, пока это X != Y
верно. Однако, даже если X == Y
, привязка все еще возможна, пока A != B
верно. Например , socketA
относится к программе сервера FTP и связан 192.168.0.1:21
и socketB
принадлежит к другой программе сервера FTP и связан 10.0.0.1:21
, как переплеты удастся. Имейте в виду, однако, что сокет может быть локально привязан к «любому адресу». Если сокет связан с0.0.0.0:21
, он привязан ко всем существующим локальным адресам одновременно, и в этом случае никакой другой сокет не может быть привязан к порту 21
, независимо от того, к какому конкретному IP-адресу он пытается привязаться, так как 0.0.0.0
конфликтует со всеми существующими локальными IP-адресами.
Все сказанное до сих пор в значительной степени одинаково для всех основных операционных систем. Ситуация начинает зависеть от ОС, когда в игру вступает повторное использование адресов. Мы начнем с BSD, поскольку, как я сказал выше, он является матерью всех реализаций сокетов.
BSD
SO_REUSEADDR
Если SO_REUSEADDR
он включен для сокета до его привязки, сокет может быть успешно связан, если не будет конфликта с другим сокетом, связанным с точно такой же комбинацией адреса источника и порта. Теперь вы можете задаться вопросом, чем это отличается от того, что было раньше? Ключевое слово "точно". SO_REUSEADDR
в основном меняет способ обработки адресов подстановки («любой IP-адрес») при поиске конфликтов.
Без SO_REUSEADDR
этого привязка socketA
к, 0.0.0.0:21
а затем привязка socketB
к ней 192.168.0.1:21
завершится неудачно (с ошибкой EADDRINUSE
), поскольку 0.0.0.0 означает «любой локальный IP-адрес», поэтому все локальные IP-адреса считаются используемыми этим сокетом, и это также включает в себя 192.168.0.1
. С SO_REUSEADDR
оно будет успешным, так как 0.0.0.0
и 192.168.0.1
это не точно таким же адрес, один является символом для всех локальных адресов , а другой один очень специфический локальный адрес. Обратите внимание, что приведенное выше утверждение верно независимо от того, в каком порядке socketA
и socketB
связаны; без SO_REUSEADDR
этого всегда не получится, с SO_REUSEADDR
ним всегда получится.
Для лучшего обзора давайте составим здесь таблицу и перечислим все возможные комбинации:
SO_REUSEADDR сокетA сокетB Результат
-------------------------------------------------- -------------------
ВКЛ / ВЫКЛ 192.168.0.1:21 192.168.0.1:21 Ошибка (EADDRINUSE)
ON / OFF 192.168.0.1:21 10.0.0.1:21 OK
ON / OFF 10.0.0.1:21 192.168.0.1:21 OK
ВЫКЛ 0.0.0.0:21 192.168.1.0:21 Ошибка (EADDRINUSE)
ВЫКЛ. 192.168.1.0:21 0.0.0.0:21 Ошибка (EADDRINUSE)
ON 0.0.0.0:21 192.168.1.0:21 OK
ON 192.168.1.0:21 0.0.0.0:21 OK
ВКЛ / ВЫКЛ 0.0.0.0:21 0.0.0.0:21 Ошибка (EADDRINUSE)
В приведенной выше таблице предполагается, что socketA
она уже успешно связана с указанным адресом socketA
, затем socketB
создается, либо SO_REUSEADDR
устанавливается, либо нет, и, наконец, привязывается к указанному адресу socketB
. Result
является результатом операции связывания для socketB
. Если в первом столбце указано ON/OFF
, значение не SO_REUSEADDR
имеет отношения к результату.
Хорошо, SO_REUSEADDR
влияет на адреса подстановочных знаков, полезно знать. Но это не единственный эффект. Есть еще один хорошо известный эффект, который также является причиной того, что большинство людей используют SO_REUSEADDR
серверные программы. Для другого важного использования этой опции мы должны глубже взглянуть на то, как работает протокол TCP.
Сокет имеет буфер отправки, и если вызов send()
функции завершается успешно, это не означает, что запрошенные данные действительно были отправлены, это только означает, что данные были добавлены в буфер отправки. Для сокетов UDP данные обычно отправляются довольно скоро, если не сразу, но для сокетов TCP может быть относительно длительная задержка между добавлением данных в буфер отправки и реализацией отправки данных реализацией TCP. В результате, когда вы закрываете сокет TCP, в буфере отправки могут оставаться ожидающие данные, которые еще не были отправлены, но ваш код считает их отправленными, так какsend()
вызов выполнен. Если реализация TCP немедленно закрывает сокет по вашему запросу, все эти данные будут потеряны, и ваш код даже не узнает об этом. TCP считается надежным протоколом, и такая потеря данных не очень надежна. Вот почему сокет, в котором все еще есть данные для отправки, перейдет в состояние, вызываемое TIME_WAIT
при его закрытии. В этом состоянии он будет ждать, пока все ожидающие данные будут успешно отправлены или пока не истечет время ожидания, и в этом случае сокет будет принудительно закрыт.
Время ожидания ядром, прежде чем оно закроет сокет, независимо от того, есть ли у него данные в полете или нет, называется временем задержки . Linger Time глобально настраивается на большинстве систем и по умолчанию довольно длинный (две минуты это общее значение вы найдете на многих системах). Он также настраивается для каждого сокета с помощью параметра сокета, SO_LINGER
который можно использовать для сокращения или увеличения времени ожидания и даже для его полного отключения. Полностью отключить его - очень плохая идея, поскольку закрытие сокета TCP изящно - это немного сложный процесс, включающий отправку и отправку пары пакетов (а также повторную отправку этих пакетов в случае их потери) и весь этот процесс закрытия. также ограничено Linger Time, Если вы отключите задержку, ваш сокет может не только потерять данные в полете, он также всегда будет принудительно закрыт, а не изящно, что обычно не рекомендуется. Подробная информация о том, как правильно закрывается TCP-соединение, выходит за рамки этого ответа. Если вы хотите узнать больше о, я рекомендую вам взглянуть на эту страницу . И даже если вы отключили задержку SO_LINGER
, если ваш процесс умирает без явного закрытия сокета, BSD (и, возможно, другие системы) будут задерживаться, игнорируя то, что вы настроили. Это произойдет, например, если ваш код просто вызываетexit()
(довольно часто для крошечных, простых серверных программ) или процесс прерывается сигналом (что включает в себя вероятность того, что он просто завершится сбоем из-за несанкционированного доступа к памяти). Поэтому вы ничего не можете сделать, чтобы сокет никогда не задерживался при любых обстоятельствах.
Вопрос в том, как система обрабатывает сокет в состоянии TIME_WAIT
? Если SO_REUSEADDR
не установлено, считается, что сокет в состоянии TIME_WAIT
все еще связан с адресом и портом источника, и любая попытка связать новый сокет с тем же адресом и портом потерпит неудачу, пока сокет действительно не будет закрыт, что может занять много времени. как настроено Linger Time . Поэтому не ожидайте, что вы сможете повторно привязать адрес источника сокета сразу после его закрытия. В большинстве случаев это не удастся. Однако, если SO_REUSEADDR
установлен для сокета, который вы пытаетесь связать, другой сокет привязан к тому же адресу и порту в состоянииTIME_WAIT
просто игнорируется, ведь он уже "наполовину мертв", и ваш сокет может без проблем связываться с одним и тем же адресом. В этом случае это не играет роли, что другой сокет может иметь точно такой же адрес и порт. Обратите внимание, что привязка сокета к точно такому же адресу и порту, что и умирающий сокет в TIME_WAIT
состоянии, может иметь неожиданные и, как правило, нежелательные побочные эффекты в случае, если другой сокет все еще «в работе», но это выходит за рамки этого ответа и к счастью, эти побочные эффекты довольно редки на практике.
Есть одна последняя вещь, о которой вы должны знать SO_REUSEADDR
. Все написанное выше будет работать до тех пор, пока в сокете, к которому вы хотите привязать, включено повторное использование адреса. Нет необходимости, чтобы другой сокет, тот, который уже связан или находился в TIME_WAIT
состоянии, также имел этот флаг, установленный, когда он был связан. Код, который решает, будет ли связывание успешным или неудачным, проверяет только SO_REUSEADDR
флаг сокета, переданного в bind()
вызов, для всех других проверенных сокетов этот флаг даже не просматривается.
SO_REUSEPORT
SO_REUSEPORT
это то, что большинство людей ожидали SO_REUSEADDR
бы быть. В принципе, SO_REUSEPORT
позволяет связать произвольное количество сокетов точно тот же адрес источника и порт до тех пор , как все предыдущие связанные гнезда также было SO_REUSEPORT
установить , прежде чем они были связаны. Если первый сокет, который связан с адресом и портом, не имеет SO_REUSEPORT
установленного, никакой другой сокет не может быть связан с точно таким же адресом и портом, независимо от того, установлен этот другой сокет SO_REUSEPORT
или нет, пока первый сокет не освободит свою привязку снова. В отличие от случая, SO_REUESADDR
когда обработка кода SO_REUSEPORT
будет не только проверять, установлен ли в данный момент связанный сокет, SO_REUSEPORT
но также будет проверять, что сокет с конфликтующим адресом и портом был SO_REUSEPORT
установлен, когда он был связан.
SO_REUSEPORT
не подразумевает SO_REUSEADDR
. Это означает, что если сокет не SO_REUSEPORT
установил, когда он был связан, и другой сокет SO_REUSEPORT
установил, когда он связан с точно таким же адресом и портом, связывание завершается неудачно, что ожидается, но также происходит сбой, если другой сокет уже умирает и в TIME_WAIT
состоянии. Чтобы иметь возможность связать сокет с теми же адресами и портом, что и другой сокет в TIME_WAIT
состоянии, необходимо либо SO_REUSEADDR
установить этот сокет, либо он SO_REUSEPORT
должен быть установлен на обоих сокетах до их привязки. Конечно разрешено устанавливать как, так SO_REUSEPORT
и SO_REUSEADDR
, на сокет.
Больше нечего сказать о SO_REUSEPORT
другом, кроме того, что он был добавлен позже SO_REUSEADDR
, поэтому вы не найдете его во многих реализациях сокетов других систем, которые «разветвляли» код BSD до добавления этой опции, и что не было способ связать два сокета с точно таким же адресом сокета в BSD до этой опции.
Connect () Возвращение EADDRINUSE?
Большинство людей знают, что bind()
может произойти сбой с ошибкой EADDRINUSE
, однако, когда вы начинаете играть с повторным использованием адреса, вы можете столкнуться со странной ситуацией, которая также connect()
завершается с этой ошибкой. Как это может быть? Как может уже использоваться удаленный адрес, после того, что connect добавляет к сокету? Подключение нескольких сокетов к одному и тому же удаленному адресу никогда не было проблемой, так что здесь происходит?
Как я уже говорил в самом верху моего ответа, соединение определяется набором из пяти значений, помните? И я также сказал, что эти пять значений должны быть уникальными, иначе система не сможет больше различать две связи, верно? Что ж, при повторном использовании адресов вы можете привязать два сокета одного и того же протокола к одному и тому же адресу источника и порту. Это означает, что три из этих пяти значений уже одинаковы для этих двух сокетов. Если вы сейчас попытаетесь подключить оба этих сокета также к одному и тому же адресу и порту назначения, вы создадите два подключенных сокета, чьи кортежи абсолютно идентичны. Это не может работать, по крайней мере, для TCP-соединений (UDP-соединения в любом случае не являются реальными). Если данные поступили для одного из двух соединений, система не могла бы определить, к какому соединению принадлежат данные.
Поэтому, если вы связываете два сокета одного и того же протокола с одним и тем же адресом и портом источника и пытаетесь соединить их оба с одним и тем же адресом и портом назначения, на connect()
самом деле произойдет сбой с ошибкой EADDRINUSE
для второго сокета, который вы пытаетесь подключить, что означает, что сокет с идентичным кортежем из пяти значений уже подключен.
Адреса многоадресной рассылки
Большинство людей игнорируют тот факт, что многоадресные адреса существуют, но они существуют. В то время как одноадресные адреса используются для связи один-к-одному, многоадресные адреса используются для связи один-ко-многим. Большинство людей узнали о многоадресных адресах, когда узнали об IPv6, но многоадресные адреса также существовали в IPv4, хотя эта функция никогда широко не использовалась в общедоступном Интернете.
Значение SO_REUSEADDR
изменений для адресов многоадресной рассылки, поскольку позволяет нескольким сокетам быть привязанными к одной и той же комбинации исходного адреса многоадресной рассылки и порта. Другими словами, для многоадресных адресов SO_REUSEADDR
ведет себя точно так же, как и SO_REUSEPORT
для одноадресных адресов. На самом деле, код обрабатывает SO_REUSEADDR
и SO_REUSEPORT
одинаково для адресов многоадресной рассылки, это означает, что вы можете сказать, что SO_REUSEADDR
подразумевается SO_REUSEPORT
для всех адресов многоадресной рассылки и наоборот.
FreeBSD / OpenBSD / NetBSD
Все это довольно поздние форки исходного кода BSD, поэтому все они предлагают те же опции, что и BSD, и ведут себя так же, как в BSD.
macOS (MacOS X)
По своей сути macOS - это просто UNIX в стиле BSD под названием « Darwin », основанный на довольно позднем форке кода BSD (BSD 4.3), который впоследствии был даже повторно синхронизирован с (в то время действующим) FreeBSD. 5 кодовая база для выпуска Mac OS 10.3, чтобы Apple могла получить полное соответствие POSIX (macOS сертифицирована POSIX). Несмотря на то, что в его ядре есть микроядро (« Mach »), остальное ядро (« XNU ») - это просто ядро BSD, и поэтому macOS предлагает те же опции, что и BSD, и они также ведут себя так же, как и в BSD. ,
iOS / watchOS / tvOS
iOS - это просто macOS-форк с немного измененным и урезанным ядром, несколько урезанным набором инструментов пользовательского пространства и немного другим набором фреймворков по умолчанию. watchOS и tvOS - это iOS-вилки, которые урезаны еще больше (особенно watchOS). Насколько я знаю, все они ведут себя точно так же, как и MacOS.
Linux
Linux <3.9
До Linux 3.9 SO_REUSEADDR
существовала только опция . Этот параметр обычно ведет себя так же, как в BSD, с двумя важными исключениями:
Пока прослушивающий (серверный) сокет TCP привязан к определенному порту, эта SO_REUSEADDR
опция полностью игнорируется для всех сокетов, нацеленных на этот порт. Привязка второго сокета к тому же порту возможна, только если это было возможно в BSD без SO_REUSEADDR
установки. Например, вы не можете связать с подстановочным адресом, а затем с более конкретным или наоборот, оба варианта возможны в BSD, если вы установите SO_REUSEADDR
. Что вы можете сделать, так это связать один и тот же порт и два разных не подстановочных адреса, как это всегда разрешено. В этом аспекте Linux более строг, чем BSD.
Второе исключение заключается в том, что для клиентских сокетов этот параметр ведет себя точно так же, как SO_REUSEPORT
в BSD, если у обоих был установлен этот флаг, прежде чем они были связаны. Причиной для этого было просто то, что важно иметь возможность привязывать несколько сокетов к одному и тому же адресу сокета UDP для различных протоколов, и, как это было SO_REUSEPORT
до 3.9, поведение SO_REUSEADDR
было изменено соответствующим образом, чтобы заполнить этот пробел. , В этом аспекте Linux менее строг, чем BSD.
Linux> = 3.9
Linux 3.9 также добавил эту опцию SO_REUSEPORT
в Linux. Эта опция ведет себя точно так же, как опция в BSD, и позволяет привязывать к одному и тому же адресу и номеру порта, если эта опция установлена во всех сокетах до их привязки.
Тем не менее, есть еще два отличия в SO_REUSEPORT
других системах:
Для предотвращения «перехвата портов» существует одно специальное ограничение: все сокеты, которые хотят использовать один и тот же адрес и комбинацию портов, должны принадлежать процессам, которые имеют один и тот же эффективный идентификатор пользователя! Таким образом, один пользователь не может «украсть» порты другого пользователя. Это особая магия, чтобы несколько компенсировать отсутствующие SO_EXCLBIND
/ SO_EXCLUSIVEADDRUSE
флаги.
Кроме того, ядро выполняет некоторую «особую магию» для SO_REUSEPORT
сокетов, которых нет в других операционных системах: для UDP-сокетов оно пытается распределить дейтаграммы равномерно, для TCP-прослушивающих сокетов оно пытается распределить входящие запросы соединения (те, которые принимаются вызовом accept()
) равномерно по всем сокетам, которые имеют одинаковый адрес и комбинацию портов. Таким образом, приложение может легко открыть один и тот же порт в нескольких дочерних процессах, а затем использовать его SO_REUSEPORT
для получения очень недорогой балансировки нагрузки.
Android
Хотя вся система Android несколько отличается от большинства дистрибутивов Linux, в ее ядре работает слегка модифицированное ядро Linux, поэтому все, что относится к Linux, должно относиться и к Android.
Windows
Windows знает только SO_REUSEADDR
вариант, нет SO_REUSEPORT
. Установка SO_REUSEADDR
для сокета в Windows ведет себя как установка SO_REUSEPORT
и SO_REUSEADDR
для сокета в BSD, за одним исключением: сокет с SO_REUSEADDR
всегда может связываться с точно таким же адресом источника и портом, что и уже связанный сокет, даже если другой сокет не имеет этой опции установить, когда это было связано . Такое поведение несколько опасно, поскольку оно позволяет приложению «украсть» подключенный порт другого приложения. Излишне говорить, что это может иметь серьезные последствия для безопасности. Microsoft поняла, что это может быть проблемой, и добавила еще одну опцию сокетов SO_EXCLUSIVEADDRUSE
. настройкаSO_EXCLUSIVEADDRUSE
на сокете гарантирует, что в случае успешного связывания комбинация адреса источника и порта принадлежит исключительно этому сокету, и никакой другой сокет не может связываться с ними, даже если он SO_REUSEADDR
установлен.
Для получения более подробной информации о том, как флаги SO_REUSEADDR
и SO_EXCLUSIVEADDRUSE
работа в Windows, как они влияют на привязку / повторное связывание, Microsoft любезно предоставила таблицу, аналогичную моей таблице, в верхней части этого ответа. Просто зайдите на эту страницу и прокрутите немного вниз. На самом деле существует три таблицы: первая показывает старое поведение (ранее Windows 2003), вторая - поведение (Windows 2003 и выше), а третья показывает, как поведение меняется в Windows 2003 и более поздних версиях, если bind()
вызовы выполняются разные пользователи.
Solaris
Solaris является преемником SunOS. SunOS изначально был основан на форке BSD, SunOS 5 и позже был основан на форке SVR4, однако SVR4 - это слияние BSD, System V и Xenix, поэтому до некоторой степени Solaris также является форком BSD, и довольно ранний. В результате Солярис только знает SO_REUSEADDR
, нет SO_REUSEPORT
. В SO_REUSEADDR
ведет себя почти так же , как это делает в BSD. Насколько я знаю, нет способа получить такое же поведение, как SO_REUSEPORT
в Solaris, это означает, что невозможно привязать два сокета к одному и тому же адресу и порту.
Как и в Windows, Solaris имеет опцию для предоставления сокету эксклюзивной привязки. Эта опция называется SO_EXCLBIND
. Если эта опция установлена на сокете до его привязки, установка SO_REUSEADDR
на другом сокете не имеет никакого эффекта, если эти два сокета проверены на конфликт адресов. Например , если socketA
привязан к шаблону адреса и socketB
имеет SO_REUSEADDR
включен и связан с не-подстановочные адрес и тот же порт socketA
, эта привязка обычно успешным, если socketA
не был SO_EXCLBIND
включен, и в этом случае она не будет выполнена вне зависимости от SO_REUSEADDR
флага socketB
.
Другие Системы
Если вашей системы нет в списке выше, я написал небольшую тестовую программу, которую вы можете использовать, чтобы узнать, как ваша система обрабатывает эти два параметра. Также, если вы считаете, что мои результаты неверны , сначала запустите эту программу, прежде чем публиковать комментарии и, возможно, делать ложные заявления.
Все, что требуется для построения кода, - это немного POSIX API (для сетевых частей) и компилятор C99 (на самом деле большинство компиляторов, отличных от C99, будут работать так же долго, как они предлагают inttypes.h
и stdbool.h
, например, gcc
поддерживаются как задолго до полной поддержки C99) ,
Все, что нужно программе для запуска, - это то, что по крайней мере одному интерфейсу в вашей системе (кроме локального интерфейса) назначен IP-адрес и задан маршрут по умолчанию, который использует этот интерфейс. Программа соберет этот IP-адрес и использует его в качестве второго «конкретного адреса».
Он проверяет все возможные комбинации, которые вы можете придумать:
- Протокол TCP и UDP
- Обычные сокеты, слушающие (серверные) сокеты, многоадресные сокеты
SO_REUSEADDR
установить на socket1, socket2 или на оба сокета
SO_REUSEPORT
установить на socket1, socket2 или на оба сокета
- Все комбинации адресов, которые вы можете составить
0.0.0.0
(подстановочный знак), 127.0.0.1
(конкретный адрес) и второй конкретный адрес, найденный в вашем основном интерфейсе (для многоадресной рассылки это просто 224.1.2.3
во всех тестах)
и печатает результаты в хорошей таблице. Он также будет работать на системах, которые не знают SO_REUSEPORT
, и в этом случае эта опция просто не проверяется.
То, что программа не может легко проверить, так это то, как она SO_REUSEADDR
работает с сокетами в TIME_WAIT
состоянии, так как очень сложно форсировать и поддерживать сокет в этом состоянии. К счастью, большинство операционных систем, похоже, просто ведут себя как BSD, и большую часть времени программисты могут просто игнорировать существование этого состояния.
Вот код (я не могу включить его здесь, ответы имеют ограничение по размеру, и код будет выдвигать этот ответ за предел).
INADDR_ANY
не связывает существующие локальные адреса, но также и все будущие.listen
безусловно, создает сокеты с одинаковым точным протоколом, локальным адресом и локальным портом, даже если вы сказали, что это невозможно.