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


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

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

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

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

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

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

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

10 77

10 ответов:

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

под Linux (или OS типа 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() etc.

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

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

$ 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

похоже, что на этот вопрос уже полностью ответили 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 / poll/rtsig / select

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

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

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

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

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

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

посмотреть:

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

например код:

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

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

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

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

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

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

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

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

другие API высокого уровня (включая WCF) используют HTTP.СИСТЕМНЫЙ.

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

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

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

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

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

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