TCP: могут ли два разных сокета иметь общий порт?

125

Это может быть очень простой вопрос, но он меня смущает.

Могут ли два разных подключенных сокета использовать общий порт? Я пишу сервер приложений, который должен иметь возможность обрабатывать более 100 тысяч одновременных подключений, и мы знаем, что количество портов, доступных в системе, составляет около 60 тысяч (16 бит). Подключенный сокет назначается новому (выделенному) порту, поэтому это означает, что количество одновременных подключений ограничено количеством портов, если только несколько сокетов не могут использовать один и тот же порт. Итак, вопрос.

Спасибо за помощь в продвижении!

KJ
источник

Ответы:

176

Сервер сокет прослушивает один порт. Все установленные клиентские соединения на этом сервере связаны с тем же портом прослушивания на стороне сервера соединения. Установленное соединение однозначно идентифицируется комбинацией пар IP / Port на стороне клиента и на стороне сервера. Несколько подключений на одном сервере могут совместно использовать одну и ту же пару IP / Port на стороне сервера, если они связаны с разными парами IP / Port на стороне клиента , и сервер сможет обрабатывать столько клиентов, сколько позволяют доступные системные ресурсы к.

На стороне клиента обычная практика для новых исходящих подключений - использовать случайный порт на стороне клиента , и в этом случае возможно исчерпание доступных портов, если вы сделаете много подключений за короткий промежуток времени.

Реми Лебо
источник
2
Спасибо за ответ, Реми! Ваш ответ - это все, что меня интересовало. ;)
KJ
3
@Remy Соединения различаются не только по порту источника / назначения / IP, но и по протоколу (TCP, UDP и т. Д.), Если я не ошибаюсь.
Ондрей Петерка
2
@OndraPeterka: да, но не все платформы ограничиваются этим. Например, Windows с радостью позволяет отдельным серверным сокетам IPv4 и IPv6 прослушивать один и тот же локальный IP-адрес: порт без перебоев, но системы * Nix (включая Linux и Android) этого не делают.
Реми Лебо
7
@ user2268997: нельзя использовать один сокет для подключения к нескольким серверам. Вы должны создать отдельный сокет для каждого соединения.
Реми Лебо,
3
@FernandoGonzalezSanchez: один клиент может иметь несколько сокетов TCP, привязанных к одной и той же локальной паре IP / Port, если они подключены к разным парам удаленных IP / Port. Это не относится к Windows, это часть общей работы TCP.
Реми Лебо
183

Прослушивание 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.) Соединения однозначно идентифицируются операционной системой с помощью следующих пяти кортежей: (локальный 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      - 

Итак, теперь есть один процесс, который активно прослушивает (Состояние: 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-адресами никогда не бывает путаницы.

n0thing
источник
12
Это лучший ответ, который я когда-либо видел на SO.
Джобс
1
@ N0thing "Таким образом, один клиент может иметь до ~ 64k подключений к серверу для одного и того же порта назначения." Таким образом, на практике, если клиент не подключается к одному и тому же серверу и порту дважды или несколько раз одновременно, то клиент может иметь даже более ~ 64 КБ подключений. Это правда. Если да, то это означает, что с одного порта на стороне клиента он может иметь соединение со многими различными серверными процессами (например, соединение сокета отличается). Итак, в целом несколько клиентских сокетов могут находиться на одном и том же порту на клиентской машине? Пожалуйста, прочтите мой комментарий к ответу «Рими Лебо». Спасибо: D
Prem KTiw
6
@premktiw: Да, несколько клиентских сокетов могут быть привязаны к одной и той же локальной паре IP / порт одновременно, если они подключены к разным парам IP / Port сервера, поэтому кортежи локальных + удаленных пар уникальны. И да, у клиента может быть более 64К одновременных подключений. Через один порт он может быть подключен к потенциально бесконечному количеству серверов (ограниченных доступными ресурсами ОС, доступными портами маршрутизатора и т. Д.), Если пары IP / порт сервера уникальны.
Реми Лебо
1
@RemyLebeau Доволен. Большое спасибо: D
Prem KTiw
1
@bibstha Как межсетевой экран работает со случайными портами, когда все входящие соединения отклоняются?
PatrykG
35

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

Это обычная интуиция, но она неверна. Подключенный сокет не назначается новому / выделенному порту. Единственное фактическое ограничение, которому должен удовлетворять стек TCP, - это то, что кортеж (local_address, local_port, remote_address, remote_port) должен быть уникальным для каждого соединения сокета. Таким образом, на сервере может быть много сокетов TCP, использующих один и тот же локальный порт, если каждый из сокетов порта подключен к другому удаленному месту.

См. Параграф «Пара сокетов» на странице 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
То, что вы говорите, полностью касается серверной части. Однако структура BSD Sockets API означает, что исходящие порты на стороне клиента должны быть уникальными на практике, поскольку bind()операция предшествует connect()операции, даже неявно.
Marquis of Lorne
1
@EJP Привет, я думал, что bind()он используется только на стороне сервера. accept()?Значит, клиентская сторона также привяжет конкретный порт?
GMsoF
5
@GMsoF: раньше bind()можно было использовать на стороне клиента connect().
Реми Лебо
10

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

По идее, мы должны различать сокет и порт . Сокеты - это конечные точки двунаправленной связи, то есть «вещи», куда мы можем отправлять и получать байты. Это концептуальная вещь, такого поля в заголовке пакета с именем «сокет» нет.

Порт - это идентификатор, позволяющий идентифицировать сокет. В случае TCP порт - это 16-битное целое число, но есть и другие протоколы (например, в сокетах unix «порт» по сути представляет собой строку).

Основная проблема заключается в следующем: если приходит входящий пакет, ядро ​​может идентифицировать свой сокет по номеру порта назначения. Это наиболее распространенный способ, но не единственный:

  • Сокеты можно идентифицировать по IP-адресу назначения входящих пакетов. Это имеет место, например, если у нас есть сервер, использующий два IP-адреса одновременно. Затем мы можем запустить, например, разные веб-серверы на одних и тех же портах, но на разных IP-адресах.
  • Сокеты также можно идентифицировать по их исходному порту и IP. Так обстоит дело во многих конфигурациях балансировки нагрузки.

Поскольку вы работаете на сервере приложений, он сможет это сделать.

Питер - Восстановить Монику
источник
2
Он не просил заработать секунду bind().
Marquis of Lorne
1
@ user207421 Вы когда-нибудь видели ОС, в которой сокеты для прослушивания не настраиваются bind()? Я могу себе это представить, да, это вполне возможно, но факт в том, что и WinSock, и API Posix используют bind()для этого вызов, даже их параметризация практически одинакова. Даже если у API нет этого вызова, вам нужно как-то сказать это, откуда вы хотите читать входящие байты .
Питер - Восстановить Монику
1
@ user207421 Конечно, 100 тыс. или более TCP-соединений могут обрабатываться с помощью одних и тех же портов, вызовы listen()/ accept()API могут создавать сокеты таким образом, чтобы ядро ​​различало их по входящим портам. Вопрос о OP можно интерпретировать так, как он этого требует. Думаю, это вполне реально, но не об этом буквально означает его вопрос.
Питер - Восстановить Монику
1

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

SAKEER T
источник