Чи є спосіб для кількох процесів спільно використовувати сокет прослуховування?


90

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

Два процеси не можуть одночасно прив’язатись до одного порту - так чи інакше за замовчуванням.

Цікаво, чи є спосіб (у будь-якій відомій ОС, особливо Windows) запустити кілька екземплярів процесу, наприклад, щоб усі вони прив’язувались до сокета, і тому вони ефективно ділилися чергою. Тоді кожен екземпляр процесу може бути однопоточним; він просто заблокував би, приймаючи нове з'єднання. Коли клієнт підключається, один із неактивних екземплярів процесу приймає цього клієнта.

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

Чи існує така особливість?

Редагувати: Для тих, хто запитує "Чому б не використовувати нитки?" Очевидно, що нитки - це варіант. Але з кількома потоками в одному процесі всі об’єкти можна спільно використовувати, і слід подбати про те, щоб об’єкти або не були спільними, або були видимими лише для одного потоку за раз, або були абсолютно незмінними, і найпопулярніші мови та час виконання не має вбудованої підтримки для управління цією складністю.

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


2
Я погоджуюсь, що кілька процесів можуть спростити створення правильної та надійної реалізації. Масштабується, я не впевнений, це залежить від вашого проблемного домену.
MarkR

Відповіді:


92

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

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

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

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

РЕДАГУВАТИ:

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

ПОЯСНЕННЯ:

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


3
Кілька процесів можуть пов’язати, вказавши опцію SocketOptionName.ReuseAddress.
sipwiz

Але який сенс? Процеси в будь-якому випадку важчі, ніж нитки.
Антон Тихий

7
Процеси важчі, ніж потоки, але оскільки вони діляться лише явно спільним доступом, потрібно менше синхронізації, що полегшує програмування, а в деяких випадках може бути навіть ефективнішим.
MarkR

11
Більше того, якщо дочірній процес аварійно чи якось зламається, це менш імовірно, що вплине на батька.
MarkR

3
Також добре відзначити, що в Linux ви можете передавати сокети іншим програмам без використання fork () і не має батьківських / дочірніх відносин, використовуючи Unix Sockets.
Рахлі

34

Більшість інших надали технічні причини, чому це працює. Ось код 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

2
Отже, за один зв’язок його отримує або батько, або дитина. Але хто отримує зв'язок, є недетермінованим, так?
Hot.PxL

1
так, я думаю, це залежить від того, який процес планується запускати ОС.
Аніл Вайтла,

14

Я хотів би додати, що сокети можна спільно використовувати в Unix / Linux через AF__UNIX сокети (міжпроцесові сокети). Здається, трапляється, створюється новий дескриптор сокета, який є певним псевдонімом оригінального. Цей новий дескриптор сокета надсилається через сокет AFUNIX до іншого процесу. Це особливо корисно у випадках, коли процес не може fork () надавати спільний доступ до своїх дескрипторів файлів. Наприклад, при використанні бібліотек, які запобігають цьому через проблеми з потоками. Вам слід створити сокет домену Unix і використовувати libancillary для надсилання дескриптора.

Побачити:

Для створення розеток AF_UNIX:

Наприклад код:


13

Схоже, на це запитання 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, чи аутентифіковано користувача тощо). Потім цей нещодавно побудований сокет додається до набору дескрипторів подій для цього робочого процесу.

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

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


11

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

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


Зі статті LWN майже схоже, що він SO_REUSEPORTстворює пул потоків, де кожен сокет знаходиться в іншому потоці, але лише один сокет у групі виконує accept. Чи можете ви підтвердити, що всі сокети в групі отримують копію даних?
jww

4

Починаючи з Linux 3.9, ви можете встановити SO_REUSEPORT на сокеті, а потім мати кілька спільних процесів, які спільно використовують цей сокет. Це простіше, ніж схема попереднього випромінювання, більше не виникає проблем із сигналами, витоку fd у дочірні процеси тощо.

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

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


3

Майте одне завдання, єдиною роботою якого є прослуховування вхідних зв’язків. Отримавши підключення, він приймає підключення - це створює окремий дескриптор сокета. Прийнятий сокет передається одному з доступних робочих завдань, і основне завдання повертається до прослуховування.

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

Як передається сокет робочому? Майте на увазі, що ідея полягає в тому, що працівник - це окремий процес.
Даніель Ервікер

fork () можливо, або одна з інших наведених вище ідей. Або, можливо, ви повністю відокремили введення-виведення сокета від обробки даних; надсилати корисне навантаження робочим процесам за допомогою механізму IPC. OpenSSH та інші інструменти OpenBSD використовують цю методологію (без потоків).
HUAGHAGUAH

3

В ОС Windows (і Linux) один процес може відкрити сокет, а потім передати цей сокет іншому процесу, таким чином, що цей другий процес може потім використовувати цей сокет (і передавати його по черзі, якщо він цього захоче) .

Вирішальним викликом функції є WSADuplicateSocket ().

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

Потім процес отримання може викликати WSASocket (), передаючи цю структуру інформації, і отримувати дескриптор базового сокета.

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


2

Це схоже на те, що ви хочете - це один процес, який прослуховується для нових клієнтів, а потім перервати з'єднання, як тільки ви отримаєте з'єднання. Зробити це через нитки легко і в .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 вирішує, який процес отримує нові підключення, хоча швидкий тест вказує на те, що найстаріший процес отримує їх першим. Щодо того, чи поділяється він, якщо перший процес зайнятий, чи щось подібне, я не знаю.


2

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

Інші API високого рівня (включаючи WCF) використовують HTTP.SYS.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.