Есть ли способ для нескольких процессов совместно использовать прослушивающий сокет?

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

Два процесса не могут связываться с одним и тем же портом одновременно - по умолчанию, в любом случае.

Мне интересно, есть ли способ (на любой известной ОС, особенно Windows) запустить несколько экземпляров процесса, чтобы все они привязывались к сокету, и поэтому они эффективно обмениваются очередью. Каждый экземпляр процесса может быть однопоточным; он просто блокируется при принятии нового соединения. Когда клиент подключается, один из экземпляров процесса ожидания принимает этого клиента.

Это позволит каждому процессу иметь очень простую однопоточную реализацию, не разделяя ничего, кроме как через явную разделяемую память, и пользователь сможет настроить пропускную способность обработки, запустив больше экземпляров.

Есть ли такая функция?

Изменить: Для тех, кто спрашивает "Почему бы не использовать потоки?" Очевидно, что потоки являются опцией. Но с несколькими потоками в одном процессе все объекты являются общими, и необходимо проявлять большую осторожность, чтобы гарантировать, что объекты либо не являются общими, либо видны только одному потоку за один раз, либо абсолютно неизменяемы, а самые популярные языки и времени выполнения не хватает встроенной поддержки для управления этой сложностью.

Запустив несколько одинаковых рабочих процессов, вы получите параллельную систему, в которой по умолчанию не используется общий доступ, что значительно упрощает создание правильной и масштабируемой реализации.

Ответ 1

Вы можете совместно использовать сокет между двумя (или более) процессами в Linux и даже Windows.

В Linux (или ОС типа POSIX) использование fork() приведет к тому, что у разветвленного ребенка будут копии всех дескрипторов родительского файла. Любой, что он не закрывает, будет по-прежнему использоваться, и (например, с помощью прослушивающего сокета TCP) можно использовать для accept() новых сокетов для клиентов. Это количество серверов, включая Apache в большинстве случаев, работает.

В Windows одно и то же в принципе истинно, кроме системного вызова fork(), поэтому родительский процесс должен будет использовать CreateProcess или что-то для создания дочернего процесса (который может, конечно, использовать один и тот же исполняемый файл) и должен передать ему наследуемый дескриптор.

Создание сокета для прослушивания наследуемого дескриптора не является полностью тривиальной деятельностью, но не слишком сложной. DuplicateHandle() необходимо использовать для создания повторяющегося дескриптора (однако в родительском процессе), который будет иметь наследуемый флаг, установленный на нем. Затем вы можете передать этот дескриптор структуры STARTUPINFO дочернему процессу в CreateProcess как дескриптор STDIN, OUT или ERR (если вы не хотите использовать его ни для чего другого).

EDIT:

Чтение библиотеки MDSN, похоже, что WSADuplicateSocket является более надежным или правильным механизмом для этого; это все еще нетривиально, потому что родительские/дочерние процессы должны работать, какой дескриптор должен дублироваться некоторым механизмом IPC (хотя это может быть так же просто, как файл в файловой системе)

УТОЧНЕНИЕ:

В ответ на исходный вопрос OP нет, несколько процессов не могут bind(); только исходный родительский процесс вызовет bind(), listen() и т.д., дочерние процессы будут обрабатывать запросы только accept(), send(), recv() и т.д.

Ответ 2

Большинство других предоставили технические причины, по которым это работает. Вот какой код python вы можете запустить, чтобы продемонстрировать это для себя:

import socket
import os

def main():
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serversocket.bind(("127.0.0.1", 8888))
    serversocket.listen(0)

    # Child Process
    if os.fork() == 0:
        accept_conn("child", serversocket)

    accept_conn("parent", serversocket)

def accept_conn(message, s):
    while True:
        c, addr = s.accept()
        print 'Got connection from in %s' % message
        c.send('Thank you for your connecting to %s\n' % message)
        c.close()

if __name__ == "__main__":
    main()

Обратите внимание, что в действительности есть два прослушивателя процесса:

$ lsof -i :8888
COMMAND   PID    USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  26972 avaitla    3u  IPv4 0xc26aa26de5a8fc6f      0t0  TCP localhost:ddi-tcp-1 (LISTEN)
Python  26973 avaitla    3u  IPv4 0xc26aa26de5a8fc6f      0t0  TCP localhost:ddi-tcp-1 (LISTEN)

Ниже приведены результаты работы telnet и программы:

$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Thank you for your connecting to parent
Connection closed by foreign host.
$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Thank you for your connecting to child
Connection closed by foreign host.
$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Thank you for your connecting to parent
Connection closed by foreign host.

$ python prefork.py 
Got connection from in parent
Got connection from in child
Got connection from in parent

Ответ 3

Похоже, что этот вопрос уже был полностью удовлетворен MarkR и zackthehack, но я хотел бы добавить, что Nginx является примером модели наследования прослушивающего сокета.

Вот хорошее описание:

         Implementation of HTTP Auth Server Round-Robin and
                Memory Caching for NGINX Email Proxy

                            June 6, 2007
             Md. Mansoor Peerbhoy <[email protected]>

...

Поток рабочего процесса NGINX

После того, как основной процесс NGINX считывает файл конфигурации и вилки в сконфигурированное количество рабочих процессов, каждый рабочий процесс входит в цикл, где он ждет каких-либо событий на своих соответствующих набор сокетов.

Каждый рабочий процесс начинается с прослушивающих сокетов, поскольку пока нет доступных соединений. Следовательно, событие набор дескрипторов для каждого рабочего процесса начинается с слуховые сокеты.

(ПРИМЕЧАНИЕ) NGINX может быть настроен на использование любого из нескольких событий механизмы опроса: AIO/devpoll/Epoll/eventpoll/Kqueue/опрос/rtsig/выберите

Когда соединение приходит на любой из прослушивающих сокетов (POP3/IMAP/SMTP), каждый рабочий процесс выходит из своего опроса, так как каждый рабочий процесс NGINX наследует прослушивающий сокет. Затем, каждый рабочий процесс NGINX попытается получить глобальный мьютекс. Один из рабочих процессов получит блокировку, тогда как другие вернутся к соответствующим циклам опроса событий.

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

Если инициированное событие соответствует новому входящему соединению, NGINX принимает соединение из гнезда для прослушивания. Затем это связывает структуру данных контекста с файловым дескриптором. Эта контекст содержит информацию о соединении (независимо от того, POP3/IMAP/SMTP, независимо от того, завершен ли пользователь и т.д.). Затем, этот вновь созданный сокет добавляется в набор дескрипторов событий для этого рабочего процесса.

Теперь рабочий отказывается от мьютекса (что означает, что любые события которые прибыли на других рабочих, могут выполнить), и начинает обработку каждый запрос, который был ранее поставлен в очередь. Каждый запрос соответствует событие, которое было сообщено. Из каждого дескриптора сокета, который был сигнализируется, рабочий процесс извлекает соответствующий контекст структура данных, ранее связанная с этим дескриптором, и затем вызывает соответствующие функции обратного вызова, которые выполняют действия, основанные на состоянии этого соединения. Например, в случае недавно установленного IMAP-соединения, первое, что NGINX будет делать это, чтобы написать стандартное приветственное сообщение IMAP на (* OK IMAP4 готов).

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

Ответ 4

Я хотел бы добавить, что сокеты могут совместно использоваться в Unix/Linux через сокеты AF__UNIX (межпроцессные сокеты). Похоже, что создается новый дескриптор сокета, который является некоторым псевдонимом оригинального. Этот новый дескриптор сокета отправляется через сокет AFUNIX в другой процесс. Это особенно полезно в тех случаях, когда процесс не может fork() делиться файловыми дескрипторами. Например, при использовании библиотек, которые предотвращают это из-за проблем с потоками. Вы должны создать сокет домена Unix и использовать libancillary для отправки через дескриптор.

См:

Для создания сокетов AF_UNIX:

Например, код:

Ответ 5

Не уверен, насколько это актуально для исходного вопроса, но в ядре Linux 3.9 есть патч, добавляющий функцию TCP/UDP: поддержка TCP и UDP для опции SO_REUSEPORT; Опция нового сокета позволяет нескольким сокетам на одном узле связываться с одним и тем же портом и предназначена для повышения производительности многопоточных приложений сетевого сервера, работающих поверх многоядерных систем. более подробную информацию можно найти в ссылке LWN LWN SO_REUSEPORT в Linux Kernel 3.9, как указано в ссылке:

параметр SO_REUSEPORT является нестандартным, но доступен в аналогичной форме в ряде других UNIX-систем (в частности, BSD, откуда возникла идея). Кажется, он предлагает полезную альтернативу для сжатия максимальной производительности сетевых приложений, работающих на многоядерных системах, без использования шаблона fork.

Ответ 6

Другой подход (который позволяет избежать многих сложных деталей) в Windows, если вы используете HTTP, заключается в использовании HTTP.SYS. Это позволяет нескольким процессам прослушивать разные URL-адреса на одном и том же порту. На сервере 2003/2008/Vista/7 это работает с IIS, поэтому вы можете совместно использовать порты. (В XP SP2 поддерживается HTTP.SYS, но IIS5.1 не использует его.)

Другие API высокого уровня (включая WCF) используют HTTP.SYS.

Ответ 7

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

s = socket();
bind(s);
listen(s);
while (1) {
  s2 = accept(s);
  send_to_worker(s2);
}

Ответ 8

В Windows (и Linux) один процесс может открыть сокет, а затем передать этот сокет другому процессу, так что этот второй процесс также может использовать этот сокет (и передать его по очереди, если он захочет сделайте это).

Важным вызовом функции является WSADuplicateSocket().

Это заполняет структуру информацией о существующем сокете. Затем эта структура через механизм IPC по вашему выбору передается другому существующему процессу (примечание, которое я говорю, существующий - когда вы вызываете WSADuplicateSocket(), вы должны указать целевой процесс, который получит излучаемую информацию).

Затем процесс получения может вызывать WSASocket(), передавая эту структуру информации и получать дескриптор базового сокета.

Оба процесса теперь содержат дескриптор одного и того же основного сокета.

Ответ 9

Начиная с Linux 3.9, вы можете установить SO_REUSEPORT в сокете, а затем добавить несколько несвязанных процессов к этому сокету. Это проще, чем схема предкара, больше никаких проблем с сигналами, утечки fd для дочерних процессов и т.д.

Linux 3.9 представил новый способ записи серверов сокетов

Параметр сокета SO_REUSEPORT

Ответ 10

Похоже, что вы хотите, чтобы один процесс прослушивался для новых клиентов, а затем передал соединение после получения соединения. Сделать это через потоки легко и в .Net у вас даже есть методы BeginAccept и т.д., Чтобы заботиться о большом количестве сантехники для вас. Отключение соединений через границы процесса было бы сложным и не имело бы каких-либо преимуществ по производительности.

Кроме того, вы можете иметь несколько процессов, связанных и прослушивающих один и тот же сокет.

TcpListener tcpServer = new TcpListener(IPAddress.Loopback, 10090);
tcpServer.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
tcpServer.Start();

while (true)
{
    TcpClient client = tcpServer.AcceptTcpClient();
    Console.WriteLine("TCP client accepted from " + client.Client.RemoteEndPoint + ".");
}

Если вы запускаете два процесса, каждый из которых выполняет вышеуказанный код, он будет работать, и первый процесс, похоже, получит все соединения. Если первый процесс убит, второй получает соединения. При совместном использовании сокетов я не уверен точно, как Windows решает, какой процесс получает новые соединения, хотя быстрый тест указывает на самый старый процесс, который их получает первым. Что касается того, делится ли он, если первый процесс занят или что-то в этом роде, я не знаю.