Параметры сокетов SO_REUSEADDR и SO_REUSEPORT, чем они отличаются? Означают ли они одно и то же во всех основных операционных системах?

man pages и документация программиста для опций сокета SO_REUSEADDR а также SO_REUSEPORT различны для разных операционных систем и часто сильно сбивают с толку. Некоторые операционные системы даже не имеют возможности SO_REUSEPORT, Веб-сайт полон противоречивой информации по этому вопросу, и часто вы можете найти информацию, которая верна только для реализации одного сокета конкретной операционной системы, которая может даже не быть явно упомянута в тексте.

Так как именно SO_REUSEADDR отличающийся от SO_REUSEPORT?

Системы без SO_REUSEPORT более ограничен?

И каково ожидаемое поведение, если я использую один из них в разных операционных системах?

1 ответ

Решение

Добро пожаловать в удивительный мир мобильности... или, вернее, его отсутствие. Прежде чем приступить к подробному анализу этих двух вариантов и более глубокому взгляду на то, как их обрабатывают различные операционные системы, следует отметить, что реализация сокетов 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 считается, что он все еще связан с адресом и портом источника, и любая попытка связать новый сокет с тем же адресом и портом потерпит неудачу, пока сокет не будет действительно закрыт, что может занять столько же времени, сколько и настроенное время ожидания. Поэтому не ожидайте, что вы сможете повторно привязать адрес источника сокета сразу после его закрытия. В большинстве случаев это не удастся. Однако если 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() терпит неудачу с этой ошибкой также. Как это может быть? Как удаленный адрес, после всего, что подключает к сокету, уже используется? Подключение нескольких сокетов к одному и тому же удаленному адресу никогда не было проблемой, так что здесь происходит?

Как я уже говорил в самом верху моего ответа, соединение определяется набором из пяти значений, помните? И я также сказал, что эти пять значений должны быть уникальными, иначе система не сможет больше различать две связи, верно? Что ж, с повторным использованием адреса вы можете привязать два сокета одного и того же протокола к одному и тому же адресу источника и порту. Это означает, что три из этих пяти значений уже одинаковы для этих двух сокетов. Если вы сейчас попытаетесь подключить оба этих сокета также к одному и тому же адресу и порту назначения, вы создадите два подключенных сокета, чьи кортежи абсолютно идентичны. Это не может работать, по крайней мере, не для 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, с двумя важными исключениями:

  1. Пока прослушивающий (серверный) сокет TCP привязан к определенному порту, SO_REUSEADDR опция полностью игнорируется для всех сокетов, нацеленных на этот порт. Привязка второго сокета к тому же порту возможна, только если это было возможно в BSD без SO_REUSEADDR задавать. Например, вы не можете связать с подстановочным адресом, а затем с более конкретным или наоборот, оба варианта возможны в BSD, если вы установите SO_REUSEADDR, Что вы можете сделать, так это связать один и тот же порт и два разных адреса без подстановочных знаков, как это всегда разрешено. В этом аспекте Linux более строг, чем BSD.

  2. Второе исключение заключается в том, что для клиентских сокетов этот параметр ведет себя так же, как SO_REUSEPORT в BSD до тех пор, пока у обоих был установлен этот флаг, прежде чем они были связаны. Причиной этого было просто то, что важно иметь возможность привязывать несколько сокетов к одному и тому же адресу сокета UDP для разных протоколов, а так как раньше их не было. SO_REUSEPORT до 3.9, поведение SO_REUSEADDR был изменен соответственно, чтобы заполнить этот пробел. В этом аспекте Linux менее строг, чем BSD.

Linux> = 3.9

В Linux 3.9 добавлена ​​опция SO_REUSEPORT в Linux также. Эта опция ведет себя точно так же, как опция в BSD, и позволяет привязывать к одному и тому же адресу и номеру порта, если эта опция установлена ​​во всех сокетах до их привязки.

Тем не менее, есть еще два различия SO_REUSEPORT в других системах:

  1. Чтобы предотвратить "захват порта", существует одно специальное ограничение: все сокеты, которые хотят использовать один и тот же адрес и комбинацию портов, должны принадлежать процессам, которые имеют один и тот же эффективный идентификатор пользователя! Таким образом, один пользователь не может "украсть" порты другого пользователя. Это какая-то особая магия, чтобы несколько компенсировать пропажу SO_EXCLBIND/SO_EXCLUSIVEADDRUSE флаги.

  2. Кроме того, ядро ​​выполняет "особую магию" для SO_REUSEPORT сокеты, которых нет в других операционных системах: для сокетов UDP он пытается распределить дейтаграммы равномерно, для прослушивающих сокетов TCP он пытается распределить входящие запросы соединения (те, которые принимаются вызовом accept()) равномерно по всем сокетам, которые имеют одинаковый адрес и комбинацию портов. Таким образом, приложение может легко открыть один и тот же порт в нескольких дочерних процессах, а затем использовать SO_REUSEPORT чтобы получить очень недорогое распределение нагрузки.


Android

Хотя вся система Android несколько отличается от большинства дистрибутивов Linux, в ее ядре работает слегка модифицированное ядро ​​Linux, поэтому все, что относится к Linux, должно относиться и к Android.


Windows

Windows знает только SO_REUSEADDR Варианта нет SO_REUSEPORT, настройка SO_REUSEADDR на сокете в винде ведет себя как установка 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, и большую часть времени программисты могут просто игнорировать существование этого состояния.

Вот код (я не могу включить его здесь, ответы имеют ограничение по размеру, и код будет выдвигать этот ответ за предел).

Ответ Меки абсолютно идеален, но стоит добавить, что FreeBSD также поддерживает SO_REUSEPORT_LB, который имитирует Linux' SO_REUSEPORTповедение - уравновешивает нагрузку; см. setsockopt(2)

Другие вопросы по тегам