Чем отличаются SO_REUSEADDR и SO_REUSEPORT?

663

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

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

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

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

Mecki
источник

Ответы:

1618

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

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

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

Mecki
источник
9
Например, «адрес источника» действительно должен быть «локальным адресом», следующие три поля также. Связывание с INADDR_ANYне связывает существующие локальные адреса, но также и все будущие. listenбезусловно, создает сокеты с одинаковым точным протоколом, локальным адресом и локальным портом, даже если вы сказали, что это невозможно.
Бен Фойгт
9
@Ben Source и Destination - официальные термины, используемые для IP-адресации (к которым я обращаюсь в первую очередь). Локальный и удаленный не имеет смысла, так как удаленный адрес на самом деле может быть «локальным», а противоположным назначению является источник, а не локальный. Я не знаю, в чем твоя проблема INADDR_ANY, я никогда не говорил, что это не будет связано с будущими адресами. И listenвообще не создает никаких сокетов, что делает ваше предложение немного странным.
Меки
7
@Ben Когда новый адрес добавляется в систему, это также «существующий локальный адрес», он только начал существовать. Я не сказал "всем существующим в настоящее время локальным адресам". На самом деле я даже говорю, что на самом деле сокет действительно связан с подстановочным знаком , что означает, что сокет связан с тем, что соответствует этому подстановочному символу, сейчас, завтра и через сто лет. Аналогично для источника и назначения, вы просто придирчивы здесь. Есть ли у вас реальный технический вклад?
Меки
8
@Mecki: Вы действительно думаете, что слово « существующий» включает в себя то, что не существует сейчас, но будет в будущем? Источник и пункт назначения не придирки. Когда входящие пакеты сопоставляются с сокетом, вы говорите, что адрес назначения в пакете будет сопоставлен с «исходным» адресом сокета? Это неправильно, и вы это знаете, вы уже сказали, что источник и пункт назначения противоположны. Локальный адрес на сокете сравнивается с адресом назначения входящих пакетов, и помещен в исходном адресе на исходящих пакетов.
Бен Фойгт
10
@Mecki: Это имеет гораздо больше смысла, если вы говорите: «Локальный адрес сокета является адресом исходящих пакетов и адресом назначения входящих пакетов». Пакеты имеют адреса отправителя и получателя. Хосты и сокеты на хостах - нет. Для сокетов дейтаграмм оба узла равны. Для сокетов TCP, из-за трехстороннего рукопожатия, есть отправитель (клиент) и ответчик (сервер), но это все равно не означает, что соединение или подключенные сокеты также имеют источник и назначение , потому что трафик проходит в обоих направлениях.
Бен Фойгт
1

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

Эдвард Томаш Напирала
источник
Хорошая находка. Я не видел этого на страницах руководства, когда проверял. Обязательно стоит упомянуть, поскольку это может быть очень полезно при портировании программного обеспечения Linux на FreeBSD.
Меки