Есть ли способ для нескольких процессов совместно использовать сокет прослушивания? - PullRequest
83 голосов
/ 22 марта 2009

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

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

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

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

Существует ли такая функция?

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

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

Ответы [ 10 ]

87 голосов
/ 22 марта 2009

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

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

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

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

EDIT:

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

РАЗЪЯСНЕНИЯ:

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

27 голосов
/ 09 октября 2013

Большинство других предоставили технические причины, по которым это работает. Вот код 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
13 голосов
/ 02 сентября 2011

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

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

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

                            June 6, 2007
             Md. Mansoor Peerbhoy <mansoor@zimbra.com>

...

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

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

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

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

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

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

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

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

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

12 голосов
/ 16 июля 2009

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

См:

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

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

10 голосов
/ 04 мая 2014

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

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

4 голосов
/ 20 февраля 2016

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

В Linux 3.9 появился новый способ написания сокет-серверов

Опция сокета SO_REUSEPORT

3 голосов
/ 28 марта 2009

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

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

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

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

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

3 голосов
/ 22 марта 2009

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

s = socket();
bind(s);
listen(s);
while (1) {
  s2 = accept(s);
  send_to_worker(s2);
}
2 голосов
/ 22 марта 2009

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

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

2 голосов
/ 22 марта 2009

Звучит так, будто вы хотите, чтобы один процесс прослушивал новых клиентов, а затем передавал соединение, как только вы установили соединение. Это легко сделать, и в .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 решает, какой процесс получает новые соединения, хотя быстрый тест указывает на самый старый процесс, получающий их первым. Что касается того, разделяет ли он, занят ли первый процесс, или что-то подобное, я не знаю.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...