TCP: Чи можуть два різні сокети поділити порт?


124

Це може бути дуже основним питанням, але це мене бентежить.

Чи можуть два різні підключені розетки мати порт? Я пишу сервер додатків, який повинен мати змогу обробляти більше 100 К одночасних з'єднань, і ми знаємо, що кількість портів, доступних у системі, становить близько 60 К (16 біт). З'єднаний сокет присвоюється новому (виділеному) порту, тому це означає, що кількість одночасних з'єднань обмежена кількістю портів, за винятком випадків, коли декілька сокетів можуть спільно використовувати один і той же порт. Отже питання.

Заздалегідь дякую за допомогу!

Відповіді:


175

Сервер сокет прослуховує один порт. Усі встановлені клієнтські з'єднання на цьому сервері пов'язані з тим самим портом прослуховування на стороні сервера підключення. Встановлене з'єднання однозначно ідентифікується комбінацією пар клієнтів та портів IP / портів на стороні сервера. Кілька з'єднань на одному сервері можуть обмінюватися однією і тією ж парою IP / Порт на стороні сервера , якщо вони пов'язані з різними парами IP / Порт на стороні клієнта , і сервер зможе обробляти стільки клієнтів, скільки доступні системні ресурси дозволяють. до.

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


2
Дякую за відповідь, Ремі! Ваша відповідь - все, про що мені було цікаво. ;)
KJ

2
@Remy Connections дискримінуються не тільки джерелом / портом призначення / IP, але і протоколом (TCP, UDP тощо), якщо я не помиляюся.
Ondrej Peterka

1
@OndraPeterka: так, але не всі платформи обмежуються цим. Наприклад, Windows із задоволенням дозволяє окремим серверним розеткам IPv4 та IPv6 слухати один і той же локальний IP: Порт, не перестрибуючи обручі, але * системи Nix (включаючи Linux та Android) цього не роблять.
Ремі Лебо

6
@ user2268997: Ви не можете використовувати один сокет для підключення до декількох серверів. Ви повинні створити окремий розетку для кожного з'єднання.
Ремі Лебо

3
@FernandoGonzalezSanchez: Один клієнт може мати декілька TCP-розеток, прив'язаних до однієї локальної пари IP / Port, доки вони підключені до різних віддалених пар IP / Port. Це не характерно для Windows, це частина того, як працює TCP загалом.
Ремі Лебо

182

Прослуховування TCP / HTTP на портах: як багато користувачів можуть використовувати один і той же порт

Отже, що відбувається, коли сервер прослуховує вхідні з'єднання через порт TCP? Наприклад, скажімо, що у вас є веб-сервер на порту 80. Припустимо, що ваш комп'ютер має відкриту IP-адресу 24.14.181.229, а особа, яка намагається підключитися до вас, має IP-адресу 10.1.2.3. Ця особа може підключитися до вас, відкривши розетку TCP до 24.14.181.229:80. Досить просто.

Інтуїтивно (і неправильно) більшість людей припускають, що це виглядає приблизно так:

    Local Computer    | Remote Computer
    --------------------------------
    <local_ip>:80     | <foreign_ip>:80

    ^^ not actually what happens, but this is the conceptual model a lot of people have in mind.

Це інтуїтивно зрозуміло, оскільки, з точки зору клієнта, він має IP-адресу та підключається до сервера за адресою IP: PORT. Оскільки клієнт підключається до порту 80, то і його порт повинен бути 80? Це розумна думка, але насправді не те, що відбувається. Якби це було правильно, ми могли б обслуговувати лише одного користувача на іноземну IP-адресу. Як тільки віддалений комп'ютер підключиться, він би повісив порт 80 на порт 80, і ніхто більше не міг підключитися.

Треба розуміти три речі:

1.) На сервері прослуховується процес на порту. Як тільки він отримує з'єднання, він передає його іншій потоці. Комунікація ніколи не зависає порт прослуховування.

2.) Підключення однозначно ідентифікуються ОС за допомогою наступних 5-карт: (локальний IP, локальний порт, віддалений IP, віддалений порт, протокол). Якщо будь-який елемент кортежу інший, то це абсолютно незалежне з'єднання.

3.) Коли клієнт підключається до сервера, він вибирає випадковий, невикористаний вихідний порт високого замовлення . Таким чином, один клієнт може мати до ~ 64k підключень до сервера для одного і того ж порту призначення.

Отже, це дійсно те, що створюється, коли клієнт підключається до сервера:

    Local Computer   | Remote Computer           | Role
    -----------------------------------------------------------
    0.0.0.0:80       | <none>                    | LISTENING
    127.0.0.1:80     | 10.1.2.3:<random_port>    | ESTABLISHED

Дивлячись на те, що насправді відбувається

По-перше, давайте скористаємось netstat, щоб побачити, що відбувається на цьому комп'ютері. Ми будемо використовувати порт 500 замість 80 (адже на порту 80 відбувається ціла купа речей, оскільки це звичайний порт, але функціонально це не має значення).

    netstat -atnp | grep -i ":500 "

Як і очікувалося, вихід порожній. Тепер почнемо веб-сервер:

    sudo python3 -m http.server 500

Тепер ось знову запущений netstat:

    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      - 

Отже, зараз існує один процес, який активно слухає (State: LISTEN) на порту 500. Локальна адреса - 0,0.0,0, що є кодом для "прослуховування всіх IP-адрес". Просту помилку зробити лише прослуховування на порту 127.0.0.1, який прийме з'єднання лише з поточного комп'ютера. Отже, це не з'єднання, це просто означає, що процес, який вимагається прив’язати () до IP-порту, і цей процес відповідає за обробку всіх з'єднань з цим портом. Це натякає на обмеження того, що на порту може бути лише один процес на прослуховування комп'ютера (є способи обійти цей метод за допомогою мультиплексування, але це набагато складніша тема). Якщо веб-сервер слухає порт 80, він не може поділитися цим портом з іншими веб-серверами.

Тож тепер давайте підключимо користувача до нашої машини:

    quicknet -m tcp -t localhost:500 -p Test payload.

Це простий скрипт ( https://github.com/grokit/quickweb ), який відкриває TCP-розетку, надсилає корисну навантаження ("У цьому випадку тестова корисна навантаження"), чекає кілька секунд і відключається. Повторне виконання netstat, коли це відбувається, відображає наступне:

    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      -
    tcp        0      0 192.168.1.10:500        192.168.1.13:54240      ESTABLISHED -

Якщо ви підключитесь до іншого клієнта і знову зробите netstat, ви побачите наступне:

    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      -
    tcp        0      0 192.168.1.10:500        192.168.1.13:26813      ESTABLISHED -

... тобто клієнт використовував інший випадковий порт для з'єднання. Тому між IP-адресами ніколи не виникає плутанина.


11
Це найкраща відповідь, яку я коли-небудь бачив на SO.
Робота

1
@ N0thing "Таким чином, один клієнт може мати до ~ 64k підключень до сервера для одного і того ж порту призначення." Тож на практиці, якщо клієнт не підключається до одного і того ж сервера і порту двічі або кілька разів одночасно, то клієнт може мати навіть більше ~ 64K підключень. Це правда. Якщо так, то це означає, що з одного порту на стороні клієнта він може мати з'єднання з багатьма різними серверними процесами (такими, що з'єднання сокета відрізняється). Таким чином, чи багато клієнтських розеток можуть перебувати на одному порту на клієнтській машині? Будь ласка, прочитайте мій коментар до відповіді "Ремі Лебо". Дякую: D
Прем KTiw

6
@premktiw: Так, кілька клієнтських сокетів можуть бути прив'язані до однієї локальної пари IP / порт одночасно, якщо вони підключені до різних пар IP / портів сервера, так що кортежі локальних + віддалених пар унікальні. І так, клієнт може мати більше 64K одночасних з'єднань. З одного порту він може бути підключений до потенційно нескінченної кількості серверів (обмежених наявними ресурсами ОС, доступними портами маршрутизатора тощо), до тих пір, поки IP-адреси / порти сервера будуть унікальними.
Ремі Лебо

1
@RemyLebeau Задоволений. Дуже дякую: D
Прем KTiw

1
@bibstha Яким чином брандмауер обробляє випадкові порти, коли всі вхідні з'єднання відхиляються?
PatrykG

35

Підключений сокет призначений новому (виділеному) порту

Це звичайна інтуїція, але це неправильно. Підключений сокет не призначений новому / виділеному порту. Єдиним фактичним обмеженням, яким повинен відповідати стек TCP, є те, що кортеж (local_address, local_port, remote_address, remote_port) повинен бути унікальним для кожного з'єднання сокета. Таким чином, сервер може мати безліч сокетів TCP, використовуючи той самий локальний порт, якщо кожен з сокетів на порту підключений до іншого віддаленого місця.

Дивіться параграф "Socket Pair" за адресою: http://books.google.com/books?id=ptSC4LpwGA0C&lpg=PA52&dq=socket%20pair%20tuple&pg=PA52#v=onepage&q=socket%20pair%20tuple&f=false


1
Дякую за ідеальну відповідь, Джеремі!
KJ

6
Те, що ви говорите, цілком вірно з боку сервера. Однак структура API BSD Sockets означає, що вихідні порти на стороні клієнта повинні бути унікальними на практиці, оскільки bind()операція передує connect()операції навіть неявно.
Маркіз Лорн

1
@EJP Привіт, я думав, bind()він використовується лише на сервері, перш ніж accept()?клієнтська сторона також прив'яже конкретний порт?
GMsoF

5
@GMsoF: bind()можна використовувати на клієнтській стороні раніше connect().
Ремі Лебо

10

Теоретично так. Практика, ні. Більшість ядер (включаючи linux) не дозволяє отримати секунду bind()до вже виділеного порту. Це було не дуже великий пластир, щоб зробити це дозволеним.

У винятковому випадку ми повинні розрізняти сокет і порт . Розетки - це кінцеві точки двосторонньої комунікації, тобто "речі", куди ми можемо відправляти та отримувати байти. Це концептуальна річ, у заголовку пакету під назвою "socket" немає такого поля.

Порт - це ідентифікатор, який здатний ідентифікувати сокет. У випадку TCP порт - це 16-бітове ціле число, але є й інші протоколи (наприклад, в unix-сокетах "порт" - це по суті рядок).

Основна проблема полягає в наступному: якщо надходить вхідний пакет, ядро ​​може ідентифікувати його сокет за номером його порту призначення. Це найпоширеніший спосіб, але це не єдина можливість:

  • Розетки можна ідентифікувати за IP-адресою призначення вхідних пакетів. Так відбувається, наприклад, якщо у нас є сервер, який одночасно використовує два IP-адреси. Тоді ми можемо запускати, наприклад, різні веб-сервери на одних і тих же портах, але на різних IP-адресах.
  • Сокети можна ідентифікувати за їх джерелом і ip. Це стосується багатьох конфігурацій балансування навантаження.

Оскільки ви працюєте на сервері додатків, це зможе зробити.


2
Він не запитував про те, щоб зробити секунду bind().
Маркіз Лорн

1
@ user207421 Ви коли-небудь бачили ОС, на якій не налаштовані розетки для прослуховування bind()? Я можу це уявити, так, це цілком можливо, але факт полягає в тому, що і WinSock, і Posix API використовують для цього bind()виклик, навіть їх параметризація практично однакова. Навіть якщо API не має цього виклику, вам потрібно якось його сказати, звідки ви хочете прочитати вхідні байти .
peterh

1
@ user207421 Звичайно, 100k або більше TCP-з'єднань можуть оброблятися тими ж портами, listen()/ accept()API-дзвінки можуть створювати сокети таким чином, що ядро ​​буде диференціювати їх за своїми вхідними портами. Питання про ОП можна трактувати так, як він його по суті задає. Я думаю, це цілком реально, але це не те, що його питання буквально означає.
peterh

1

Ні. Неможливо поділити один і той же порт у певний момент. Але ви можете зробити свою програму таким чином, що вона зробить доступ до порту в різний момент.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.