Чим відрізняються SO_REUSEADDR та SO_REUSEPORT?


663

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

То як саме SO_REUSEADDRвідрізняється від SO_REUSEPORT?

Є системи без SO_REUSEPORTбільш обмежених?

І яка саме очікувана поведінка, якщо я використовую будь-яку в різних операційних системах?

Відповіді:


1615

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

  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 є спадкоємцем 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, і більшість часу програмісти можуть просто ігнорувати існування цього стану.

Ось код (я не можу включити його сюди, відповіді мають обмеження розміру, і код підштовхне цю відповідь до межі).


9
Наприклад, "адреса джерела" дійсно повинна бути "локальною адресою", наступні три поля також. Зв'язування з INADDR_ANYне пов'язує вже існуючі локальні адреси, але й усі майбутні. listenбезумовно, створює сокети з однаковим точним протоколом, локальною адресою та місцевим портом, хоча ви сказали, що це неможливо.
Бен Войгт

9
@Ben Source and Destination - це офіційні терміни, які використовуються для IP-адреси (до якої я посилаюся в першу чергу). Місцеве та віддалене не мають сенсу, оскільки віддалена адреса насправді може бути адресою "локальний", а протилежність пункту призначення - джерело, а не місцеве. Я не знаю, з чим у вас проблема INADDR_ANY, я ніколи не казав, що не прив'язується до майбутніх адрес. І listenзовсім не створює жодних розеток, що робить все ваше речення трохи дивним.
Mecki

7
@Ben Коли в систему додається нова адреса, яка також є "існуючою локальною адресою", вона лише почала існувати. Я не сказав «всім в даний час існуючих локальних адрес». Насправді я навіть кажу, що розетка насправді прив’язана до шаблону , що означає, що гніздо прив’язане до того, що відповідає цьому wildcard, зараз, завтра і через сто років. Подібно до джерела та пункту призначення, ви тут просто заїжджаєте. Чи маєте якийсь реальний технічний внесок?
Mecki

8
@Mecki: Ви дійсно думаєте, що слово існуюче включає речі, які не існують зараз, але будуть у майбутньому? Джерело та місце призначення не є ниткою. Коли вхідні пакети збігаються з сокетом, ви говорите, що адреса призначення в пакеті буде відповідна "адресу" джерела сокета? Це неправильно, і ви це знаєте, ви вже говорили, що джерело та місце призначення - протилежності. Локальний адреса на сокеті порівнюється з адресою призначення вхідних пакетів, і поміщений в вихідному адресу на вихідних пакетів.
Ben Voigt

10
@Mecki: Це має набагато більше сенсу, якщо ви скажете "Місцева адреса сокета - це адреса джерела вихідних пакетів та адреса призначення вхідних пакетів". Пакети мають адреси джерела та призначення. Господарі та розетки для господарів - ні. У розетках дейтаграми обидва колеги рівні. Для сокетів TCP через тристоронній рукостискання є ініціатор (клієнт) та респондент (сервер), але це все ще не означає, що з'єднання або підключені сокети також мають джерело та призначення , оскільки трафік протікає в обох напрямках.
Бен Войгт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.