Ласкаво просимо у чудовий світ портативності ... а точніше його відсутність. Перш ніж ми розпочнемо детальний аналіз цих двох варіантів і детальніше розглянемо, як різні операційні системи обробляють їх, слід зазначити, що реалізація сокетів 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 socketA socketB Результат
-------------------------------------------------- -------------------
ON / OFF 192.168.0.1:21 192.168.0.1:21 Помилка (EADDRINUSE)
ON / OFF 192.168.0.1:21 10.0.0.1:21 Гаразд
ON / OFF 10.0.0.1:21 192.168.0.1:21 Гаразд
OFF 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 ОК
ON 192.168.1.0:21 0.0.0.0:21 ОК
ON / OFF 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 . Час Linger глобально налаштовується в більшості систем і за замовчуванням досить тривалий (дві хвилини - це загальне значення, яке ви знайдете для багатьох систем). Це також можна налаштувати на один сокет, використовуючи параметр socket, SO_LINGER
який може бути використаний для зменшення часу та більшого часу та навіть для його відключення. Вимкнути його повністю - це дуже погана ідея, однак, оскільки граціозно закривати TCP-розетку - це трохи складний процес, який включає відправлення і повернення декількох пакетів (а також повторне відправлення цих пакетів у випадку, якщо вони загубилися) і весь цей закритий процес також обмежений часом затримки. Якщо ви вимкнете затримку, ваш гніздо може не тільки втратити дані під час польоту, але також завжди буде закрито, а не витончено, що зазвичай не рекомендується. Деталі про те, як виразно закрито 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 перед цим параметром.
Підключення () Повернення 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, за двома важливими винятками:
Поки прослуховувальний (серверний) TCP-сокет пов'язаний з певним портом, SO_REUSEADDR
опція повністю ігнорується для всіх сокетів, націлених на цей порт. Прив’язання другого сокета до того ж порту можливе лише в тому випадку, якщо це було можливо і в BSD без SO_REUSEADDR
встановлення. Наприклад, ви не можете прив’язати до підстановочної адреси, а потім до більш конкретного одного чи іншого зворотного напрямку, обидва можливі в BSD, якщо ви встановили SO_REUSEADDR
. Що ви можете зробити, це ви можете прив’язати до того самого порту та двох різних недискардних адрес, як це завжди дозволено. У цьому аспекті Linux є більш обмежуючим, ніж BSD.
Другий виняток полягає в тому, що для клієнтських сокетів ця опція поводиться точно так само, як і SO_REUSEPORT
в BSD, до тих пір, поки в обох був встановлений цей прапор до того, як вони були пов'язані. Причиною цього було просто те, що важливо вміти прив'язувати декілька сокетів точно до однієї і тієї ж адреси UDP-сокета для різних протоколів, а як раніше не SO_REUSEPORT
було 3.9, поведінку SO_REUSEADDR
було відповідно змінено, щоб заповнити цей проміжок . У цьому аспекті Linux є менш обмежуючим, ніж BSD.
Linux> = 3,9
Linux 3.9 також додав цю опцію SO_REUSEPORT
до Linux. Цей параметр поводиться точно так само, як параметр у BSD і дозволяє прив’язувати точно до однієї адреси та номера порту, доки всі сокети мають цей параметр, встановлений до їх прив’язки.
Тим не менш, SO_REUSEPORT
в інших системах є дві відмінності :
Щоб запобігти "викрадення порту", існує одне особливе обмеження: Усі сокети, які хочуть ділитися однаковою адресою та комбінацією портів, повинні належати процесам, які мають однаковий ефективний ідентифікатор користувача! Тому один користувач не може "вкрасти" порти іншого користувача. Це якась особлива магія, яка дещо компенсує відсутні SO_EXCLBIND
/ SO_EXCLUSIVEADDRUSE
прапори.
Крім того, ядро виконує певну "особливу магію" для SO_REUSEPORT
сокетів, яких немає в інших операційних системах: Для сокетів UDP він намагається розподілити дейтаграми рівномірно, для розеток прослуховування TCP, він намагається розподіляти вхідні запити на з'єднання (ті, які приймаються дзвінками accept()
) рівномірно по всіх сокетах, які мають однакову комбінацію адреси та портів. Таким чином, програма може легко відкривати один і той же порт у кількох дочірніх процесах, а потім використовувати його, SO_REUSEPORT
щоб отримати дуже недорогий баланс навантаження.
Android
Незважаючи на те, що вся система Android дещо відрізняється від більшості дистрибутивів Linux, по суті працює трохи змінене ядро Linux, тому все, що стосується Linux, має застосовуватися і до Android.
Windows
Windows знає лише SO_REUSEADDR
варіант, його немає SO_REUSEPORT
. Налаштування SO_REUSEADDR
на сокет у Windows поводиться як налаштування, так SO_REUSEPORT
і SO_REUSEADDR
на сокет у BSD, за винятком: Сокет з SO_REUSEADDR
завжди може прив’язати до того самого джерела адреси та порту, що і вже пов'язаний сокет, навіть якщо інший сокет не мав цієї опції встановити, коли воно було пов'язане . Така поведінка дещо небезпечна, оскільки дозволяє додатку "вкрасти" підключений порт іншого додатка. Потрібно говорити, що це може мати серйозні наслідки для безпеки. Microsoft зрозуміла, що це може бути проблемою, і, таким чином, додала ще одну опцію розетки SO_EXCLUSIVEADDRUSE
. НалаштуванняSO_EXCLUSIVEADDRUSE
у сокеті гарантує, що якщо зв'язування вдалося, комбінація адреси джерела та порту належить виключно цьому сокету, і жоден інший сокет не може зв'язатись із ними, навіть якщо він SO_REUSEADDR
встановлений.
Щоб отримати докладніші відомості про те, як прапори працюють SO_REUSEADDR
і SO_EXCLUSIVEADDRUSE
в Windows, як вони впливають на прив'язку / повторне прив'язування, Microsoft люб’язно надала таблицю, подібну до моєї таблиці, у верхній частині відповіді. Просто відвідайте цю сторінку і прокрутіть трохи вниз. Насправді є три таблиці: перша показує стару поведінку (до Windows 2003), друга - поведінку (Windows 2003 і новіші версії), а третя - як поведінка змінюється в Windows 2003 і пізніше, якщо bind()
дзвінки здійснюються різних користувачів.
Соляріс
Solaris є спадкоємцем 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
(wildcard), 127.0.0.1
(конкретна адреса) та другу конкретну адресу, знайдену у вашому первинному інтерфейсі (для багатоадресної передачі це просто 224.1.2.3
у всіх тестах)
і друкує результати в приємній таблиці. Він також буде працювати в системах, які не знають SO_REUSEPORT
, і в цьому випадку ця опція просто не перевірена.
Те, що програма не може легко перевірити, - це те, як SO_REUSEADDR
діє на сокети в TIME_WAIT
стані, оскільки дуже складно змусити і тримати сокет у такому стані. На щастя, здається, що більшість операційних систем просто поводиться як BSD, і більшість часу програмісти можуть просто ігнорувати існування цього стану.
Ось код (я не можу включити його сюди, відповіді мають обмеження розміру, і код підштовхне цю відповідь до межі).
INADDR_ANY
не пов'язує вже існуючі локальні адреси, але й усі майбутні.listen
безумовно, створює сокети з однаковим точним протоколом, локальною адресою та місцевим портом, хоча ви сказали, що це неможливо.