Документация man pages
и программника для опций сокета SO_REUSEADDR
и SO_REUSEPORT
различна для разных операционных систем и часто очень запутанна. Некоторые операционные системы даже не имеют опции SO_REUSEPORT
. WEB полон противоречивой информацией по этому вопросу, и часто вы можете найти информацию, которая верна только для одной реализации сокета конкретной операционной системы, которая, возможно, даже не упоминается в тексте.
Итак, как точно SO_REUSEADDR
отличается от SO_REUSEPORT
?
Ограничены ли системы без SO_REUSEPORT
?
А что такое ожидаемое поведение, если я использую один из них в разных операционных системах?
Добро пожаловать в удивительный мир переносимости... или, скорее, его отсутствие. Прежде чем мы начнем подробно анализировать эти два параметра и глубже рассмотрим, как с ними справляются разные операционные системы, следует отметить, что реализация сокета 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
с A:X
и socketB
B:Y
в B:Y
, где A
и B
являются адресами, а X
и Y
- портами, всегда возможно, если X != Y
имеет значение true. Однако, даже если X == Y
, привязка все еще возможна, если A != B
имеет значение true. Например, 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, так как, как я сказал выше, это мать всех реализаций сокетов.
Если 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
socketA
и socketB
; без SO_REUSEADDR
он всегда будет терпеть неудачу, SO_REUSEADDR
всегда будет успешным.
Чтобы дать вам лучший обзор, сделайте таблицу здесь и перечислите все возможные комбинации:
SO_REUSEADDR socketA socketB Result --------------------------------------------------------------------- ON/OFF 192.168.0.1:21 192.168.0.1:21 Error (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 OFF 0.0.0.0:21 192.168.1.0:21 Error (EADDRINUSE) OFF 192.168.1.0:21 0.0.0.0:21 Error (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 ON/OFF 0.0.0.0:21 0.0.0.0:21 Error (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
все еще привязан к исходному адресу и порту, и любая попытка связать новый сокет с тем же адресом, и порт завершится с ошибкой до тех пор, пока сокет действительно не будет закрыт, что может занять пока настроенное время задержки. Поэтому не ожидайте, что вы сможете восстановить исходный адрес сокета сразу после его закрытия. В большинстве случаев это не удастся. Однако, если SO_REUSEADDR
установлен для сокета, который вы пытаетесь связать, другой сокет, привязанный к одному и тому же адресу, и порт в состоянии TIME_WAIT
просто игнорируется, после того как все его уже "полумертвые", и ваш сокет может связываться с точно таким же адресом без каких-либо проблем. В этом случае он не играет никакой роли в том, что другой сокет может иметь точно такой же адрес и порт. Обратите внимание, что привязка сокета к одному и тому же адресу и порту в качестве умирающего сокета в TIME_WAIT
может иметь неожиданные и обычно нежелательные побочные эффекты, если другой сокет все еще "работает", но это выходит за рамки этого ответа и, к счастью, эти побочные эффекты на практике довольно редки.
Есть одна последняя вещь, которую вы должны знать о SO_REUSEADDR
. Все написанное выше будет работать до тех пор, пока сокет, который вы хотите связать, включил повторное использование адреса. Не обязательно, чтобы другой сокет, тот, который уже связан или находится в TIME_WAIT
, также установил этот флаг, когда он был связан. Код, который решает, будет ли связывание успешным или завершится, проверяет SO_REUSEADDR
флаг SO_REUSEADDR
сокета, который подается в вызов bind()
, для проверки всех других сокетов этот флаг даже не просматривается.
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 до этого параметра.
Большинство людей знают, что bind()
может завершиться ошибкой с ошибкой EADDRINUSE
, однако, когда вы начинаете играть с повторным использованием адреса, вы можете столкнуться с странной ситуацией, при которой connect()
терпит неудачу с этой ошибкой. Как это может быть? Как удаленный адрес, после того, что соединение добавляет к сокету, уже используется? Подключение нескольких сокетов к одному и тому же удаленному адресу никогда не было проблемой раньше, так что здесь происходит неправильно?
Как я сказал в самом верху моего ответа, соединение определяется кортежем из пяти значений, помните? И я также сказал, что эти пять значений должны быть уникальными, иначе система больше не сможет отличить два соединения, верно? Ну, с повторным использованием адреса вы можете связать два сокета одного и того же протокола с одним и тем же адресом и портом источника. Это означает, что три из этих пяти значений уже одинаковы для этих двух сокетов. Если теперь вы попытаетесь подключить оба эти сокета к одному и тому же адресу назначения и порту, вы должны создать два подключенных сокета, чьи кортежи абсолютно идентичны. Это не может работать, по крайней мере, не для TCP-соединений (UDP-соединения в любом случае не являются реальными подключениями). Если данные были получены для одного из двух соединений, система не могла определить, к какому соединению относятся данные. По крайней мере, адрес назначения или порт назначения должны быть разными для любого соединения, так что система не имеет проблем для определения того, к какому соединению относятся входящие данные.
Поэтому, если вы привязываете два сокета одного и того же протокола к одному и тому же исходному адресу и порту и пытаетесь подключить их как к одному и тому же адресу назначения и порту, connect()
действительно выйдет из строя с ошибкой EADDRINUSE
для второго сокета, который вы пытаетесь подключить, что означает, что сокет с идентичным кортежем из пяти значений уже подключен.
Большинство людей игнорируют тот факт, что существуют адреса многоадресной рассылки, но они существуют. Если одноадресные адреса используются для индивидуальной связи, адреса многоадресной рассылки используются для связи "один ко многим". Большинство людей узнали о многоадресных адресах, когда узнали о IPv6, но многоадресные адреса также существовали в IPv4, хотя эта функция никогда не использовалась широко в общедоступном Интернете.
Значение SO_REUSEADDR
изменяется для многоадресных адресов, так как позволяет нескольким сокетам привязываться к точно такой же комбинации адреса и порта многоадресной рассылки. Другими словами, для адресов многоадресной рассылки SO_REUSEADDR
ведет себя точно так же, как SO_REUSEPORT
для одноадресных адресов. На самом деле код рассматривает SO_REUSEADDR
и SO_REUSEPORT
одинаково для многоадресных адресов, это означает, что вы можете сказать, что SO_REUSEADDR
подразумевает SO_REUSEPORT
для всех многоадресных адресов и наоборот.
Все это довольно поздние вилки исходного кода BSD, поэтому все они предлагают те же опции, что и BSD, и они также ведут себя так же, как в BSD.
По своей сути, macOS - это просто UNIX под BSD под названием "Дарвин", основанный на довольно поздней вилке кода BSD (BSD 4.3), который позже был даже ресинхронизирован с (в то время текущей) FreeBSD 5 для выпуска Mac OS 10.3, так что Apple может получить полное соответствие POSIX (macOS сертифицирован POSIX). Несмотря на наличие микроядра в своем ядре ("Mach"), остальная часть ядра ("XNU") в основном представляет собой просто BSD-ядро, и поэтому macOS предлагает те же опции, что и BSD, и они также ведут себя так же, как и в BSD.
iOS - это просто вилка macOS со слегка измененным и обрезанным ядром, несколько урезанный набор инструментов для пользовательского пространства и немного другой набор фреймов по умолчанию. watchOS и tvOS - вилки iOS, которые еще более урезаны (особенно watchOS). Насколько мне известно, все они ведут себя точно так же, как MacOS.
До Linux 3.9 существовала только опция SO_REUSEADDR
. Эта опция ведет себя в целом так же, как и в BSD, с двумя важными исключениями:
До тех пор, пока TCP-сокет прослушивания (сервера) привязан к определенному порту, параметр SO_REUSEADDR
полностью игнорируется для всех сокетов, предназначенных для этого порта. Связывание второго сокета с одним и тем же портом возможно только в том случае, если это также возможно в BSD без установки SO_REUSEADDR
. Например, вы не можете привязываться к подстановочному адресу, а затем к более конкретному или наоборот, оба варианта возможны в BSD, если вы установите SO_REUSEADDR
. Что вы можете сделать, так это то, что вы можете привязываться к одному и тому же порту и двум различным несимвольным адресам, как это всегда разрешено. В этом аспекте Linux более ограничительный, чем BSD.
Второе исключение заключается в том, что для клиентских сокетов эта опция ведет себя точно так же, как SO_REUSEPORT
в BSD, если обе эти флаги установлены до их привязки. Причиной этого было просто, что важно иметь возможность привязывать несколько сокетов точно к одному и тому же адресу сокета UDP для различных протоколов, и как раньше SO_REUSEPORT
не было SO_REUSEPORT
до 3.9, поведение SO_REUSEADDR
было изменено соответственно для заполнения это разрыв. В этом аспекте Linux менее ограничительный, чем BSD.
Linux 3.9 также добавила опцию SO_REUSEPORT
в Linux. Эта опция ведет себя точно так же, как опция в BSD, и позволяет привязывать точно к одному и тому же адресу и номеру порта, если все сокеты имеют эту опцию перед привязкой.
Тем не менее, для SO_REUSEPORT
все еще существуют две отличия:
Чтобы предотвратить "захват порта", существует одно специальное ограничение: все сокеты, которые хотят использовать один и тот же адрес и комбинацию портов, должны относиться к процессам, которые имеют один и тот же эффективный идентификатор пользователя! Таким образом, один пользователь не может "красть" порты другого пользователя. Это особая магия, которая несколько компенсирует недостающие SO_EXCLBIND
/SO_EXCLUSIVEADDRUSE
.
Кроме того, ядро выполняет некоторую "специальную магию" для сокетов SO_REUSEPORT
которая не найдена в других операционных системах: для сокетов UDP он пытается равномерно распределить датаграммы, для сокетов прослушивания TCP, пытается распространять входящие запросы на подключение (принятые по вызову accept()
) равномерно во всех сокетах, которые используют один и тот же адрес и комбинацию портов. Таким образом, приложение может легко открыть один и тот же порт в нескольких дочерних процессах, а затем использовать SO_REUSEPORT
чтобы получить очень недорогую балансировку нагрузки.
Несмотря на то, что вся система Android несколько отличается от большинства дистрибутивов Linux, в ее основе работает немного модифицированное ядро Linux, поэтому все, что относится к Linux, должно относиться и к Android.
Windows знает SO_REUSEADDR
опцию SO_REUSEADDR
, SO_REUSEPORT
отсутствует. Установка SO_REUSEADDR
в сокете в Windows ведет себя как установка SO_REUSEPORT
и SO_REUSEADDR
в сокете в BSD, за одним исключением: сокет с SO_REUSEADDR
всегда может связываться с точно таким же адресом источника и портом как уже связанный сокет, даже если другой сокет сделал не устанавливайте эту опцию, когда она была связана. Такое поведение несколько опасно, поскольку оно позволяет приложению "украсть" подключенный порт другого приложения. Излишне говорить, что это может иметь серьезные последствия для безопасности. Microsoft поняла, что это может быть проблемой, и, таким образом, добавлена опция сокета SO_EXCLUSIVEADDRUSE
. Установка SO_EXCLUSIVEADDRUSE
в сокете гарантирует, что если привязка SO_EXCLUSIVEADDRUSE
успешно, комбинация адреса источника и порта принадлежит исключительно этому сокету, и никакой другой сокет не может привязываться к ним, даже если он имеет SO_REUSEADDR
.
Еще более подробно о том, как работают флагов SO_REUSEADDR
и SO_EXCLUSIVEADDRUSE
в Windows, как они влияют на привязку/повторное привязку, Microsoft любезно предоставила таблицу, похожую на мою таблицу, в верхней части этого ответа. Просто зайдите на эту страницу и немного прокрутите список. На самом деле есть три таблицы, первая показывает старое поведение (до Windows 2003), второе - поведение (Windows 2003 и выше), а третье показывает, как поведение изменяется в Windows 2003 и более поздних, если вызовы bind()
сделаны разными пользователями.
Solaris является преемником SunOS. SunOS первоначально была основана на вилке BSD, SunOS 5 и позже была основана на вилке SVR4, однако SVR4 является слиянием BSD, System V и Xenix, поэтому до некоторой степени Solaris также является вилкой BSD, и скорее ранний. В результате Solaris знает только SO_REUSEADDR
, SO_REUSEPORT
отсутствует. SO_REUSEADDR
ведет себя почти так же, как в BSD. Насколько я знаю, нет никакого способа получить такое же поведение, как SO_REUSEPORT
в Solaris, это означает, что невозможно привязать два сокета к одному и тому же адресу и порту.
Подобно Windows, Solaris имеет возможность предоставить сокету исключительную привязку. Этот параметр называется SO_EXCLBIND
. Если этот параметр установлен на сокет до его привязки, установка SO_REUSEADDR
в другом сокете не действует, если два сокета протестированы для конфликта адресов. Например, если socketA
привязан к подстановочному адресу и socketB
имеет SO_REUSEADDR
включен и привязан к socketA
адресу и тому же порту, что и socketA
, это связывание обычно socketA
успешно, если socketA
не SO_EXCLBIND
, и в этом случае он будет терпеть неудачу, независимо от того, SO_REUSEADDR
флаг socketB
.
Если ваша система не указана выше, я написал небольшую пробную программу, которую вы можете использовать, чтобы узнать, как ваша система обрабатывает эти два параметра. Также, если вы считаете, что мои результаты ошибочны, сначала запустите эту программу, прежде чем размещать какие-либо комментарии и, возможно, делать ложные утверждения.
Все, что требуется для создания кода, - это немного POSIX API (для сетевых частей) и компилятор C99 (на самом деле большинство компиляторов, отличных от C99, будут работать так же долго, как они предлагают inttypes.h
и stdbool.h
; например, gcc
поддерживается как задолго до предоставления полной поддержки C99).
Все, что должна выполнить программа, состоит в том, что по крайней мере один интерфейс в вашей системе (кроме локального интерфейса) имеет назначенный IP-адрес и задан маршрут по умолчанию, который использует этот интерфейс. Программа соберет этот IP-адрес и использует его как второй "конкретный адрес".
Он проверяет все возможные комбинации, о которых вы можете думать:
SO_REUSEADDR
устанавливается на socket1, socket2 или оба сокетаSO_REUSEPORT
установленный на socket1, socket2 или оба сокета0.0.0.0
(wildcard), 127.0.0.1
(конкретный адрес) и второй конкретный адрес, найденный в вашем основном интерфейсе (для многоадресной передачи всего 224.1.2.3
во всех тестах) и распечатывает результаты в хорошей таблице. Он также будет работать с системами, которые не знают SO_REUSEPORT
, и в этом случае этот параметр просто не тестируется.
То, что программа не может легко проверить, заключается в том, как SO_REUSEADDR
действует на сокеты в TIME_WAIT
так как очень сложно заставить и сохранить сокет в этом состоянии. К счастью, большинство операционных систем просто ведут себя как BSD, и большинство программистов просто могут игнорировать существование этого состояния.
Здесь код (я не могу включить его здесь, ответы имеют ограничение по размеру, и код будет подталкивать этот ответ за лимит).
INADDR_ANY
не связывает существующие локальные адреса, но также и все будущие.listen
безусловно, создает сокеты с одинаковым точным протоколом, локальным адресом и локальным портом, даже если вы сказали, что это невозможно.