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

515

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

Итак, как точно SO_REUSEADDR отличается от SO_REUSEPORT?

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

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

Теги:
sockets
portability

1 ответ

1278
Лучший ответ

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

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 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_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 до этого параметра.

Подключить() Возврат 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 под названием "Дарвин", основанный на довольно поздней вилке кода 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 не было 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_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

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-адрес и использует его как второй "конкретный адрес".

Он проверяет все возможные комбинации, о которых вы можете думать:

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

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

  • 2
    Здесь тонна хорошей информации. К сожалению, некоторые детали неверны.
  • 8
    Например, «адрес источника» действительно должен быть «локальным адресом», следующие три поля также. Связывание с INADDR_ANY не связывает существующие локальные адреса, но также и все будущие. listen безусловно, создает сокеты с одинаковым точным протоколом, локальным адресом и локальным портом, даже если вы сказали, что это невозможно.
Показать ещё 45 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню