Я думаю, что вы можете ссылаться на опцию SO_REUSEPORT
, доступную в некоторых системах.
На странице справки BSD:
SO_REUSEPORT позволяет полностью дублировать привязки несколькими процессами, есливсе они устанавливают SO_REUSEPORT перед привязкой порта.Эта опция позволяет нескольким экземплярам программы получать каждый UDP / IP многоадресные или широковещательные дейтаграммы, предназначенные для связанного порта.
Реализации для этого сильно различаются (от несуществующих до ограниченных UDP,разрешить TCP также).В случаях, когда разрешен TCP, соединения различаются по парам source и target (ip, port).Этого достаточно, чтобы реализация могла решить, какому приложению нужен какой пакет.(см., например, Trek - опции сокетов .)
При наличии нескольких приложений, привязанных к одному и тому же порту TCP, вы можете иметь только один сокет accept
на этом порту.Другие будут использовать порт для инициирования исходящих соединений.Стек TCP всегда знает, куда отправлять пакеты.
- входящие пакеты, которые инициируют (SYN) соединение, направляются в единственный принимающий сокет
- входящие пакеты для подключенного потока маршрутизируютсяк сокету они принадлежат
Примечание: сами сокеты (включая, я полагаю, принимающий сокет) могут совместно использоваться несколькими процессами.См. Есть ли способ для нескольких процессов совместно использовать прослушивающий сокет? например.
Вот как это может работать.Добровольно упрощение ПТС (без трехстороннего рукопожатия).Давайте отметим информацию о сокете, хранящуюся в стеке TCP следующим образом:
(socketname)[owner app, (local IP, local port), (state, remote IP, remote port)]
. Итак, давайте настроим три приложения A, B и C:
App A -> bind (localhost,12345,SO_REUSEPORT)
TCP stack: create socket (s1)[belongs to A, (localhost,12345), (not connected)]
App A <- s1
App B -> bind (localhost,12345,SO_REUSEPORT)
TCP stack: create socket (s2)[belongs to B, (localhost,12345), (not connected)]
App B <- s2
App C -> bind (localhost,12345,SO_REUSEPORT)
TCP stack: create socket (s3)[belongs to C, (localhost,12345), (not connected)]
App C <- s3
App C -> s3.listen()
TCP stack: update socket (s3)[belongs to C, (localhost,12345), (open for business)]
App C -> s3.accept()
. На данный момент нет данных.был отправлен, но ядро точно знает, какой сокет принадлежит какому приложению.Давайте на самом деле попробуем сделать что-то со своим сокетом:
App A -> s1.connect(otherhost,54321)
TCP stack: update socket (s1)[belongs to A, (localhost,12345), (connecting, otherhost,54321)]
TCP stack: send SYN
Здесь могут произойти три вещи:
входящий ACK-пакет от (otherhost, 54321) с правильной последовательностью:это нормально, это для s1, другой возможности нет
TCP stack: update socket (s1)[belongs to A, (localhost,12345), (connected,otherhost,54321)]
TCP stack: send SYN/ACK
TCP stack: notify App A that socket is connected
App A <- connect succeeded, you can start doin' stuff
входящий пакет SYN от (client, 4343): может быть только для s3, только сокет готов к SYN
TCP stack: create new socket (s4)[belongs to C, (localhost,12345), (connected,client,4343)]
TCP stack: send ACK to client
TCP stack: notify App C that (s4) has been accepted
App C <- s4 returned from accept()
входящий пакет откуда-то еще:
TCP stack: drop or reject, there are no matching sessions
Давайте представим, что две вещи произошли выше.Информация о ядре теперь:
(s1)[belongs to A, (localhost,12345), (connected,otherhost,54321)]
(s2)[belongs to B, (localhost,12345), (not connected)]
(s3)[belongs to C, (localhost,12345), (open for business)]
(s4)[belongs to C, (localhost,12345), (connected,client,4343)]
Теперь могут входить четыре вида пакетов:
входящий нормальный пакет от (otherhost, 54321): соответствует s1, передать егов приложение A входящий нормальный пакет от (client, 4343): соответствует s4, передает его приложению C входящий пакет SYN от (otherclient, 2398): соответствует s3, как и раньше что угодноиначе: отбросить или отклонить, недопустимое состояние
Стек TCP всегда определяет, к какому сокету принадлежит пакет.Таким образом, он знает, куда доставлять данные.
SO_REUSEADDR
отличается: он позволяет привязывать только порт в состоянии TIME_WAIT
- т.е. порт уже закрывается, сокет, который имелоткрыт он уже выдан close
(более или менее, есть другие условия).При SO_REUSEADDR
только одна розетка за раз удерживает розетку открытой.