Czy istnieje sposób na współdzielenie gniazda nasłuchowego przez wiele procesów?

W programowaniu gniazd tworzysz Gniazdo nasłuchowe, a następnie dla każdego klienta, który się połączy, otrzymujesz normalne Gniazdo strumieniowe, którego możesz użyć do obsługi żądania klienta. System operacyjny zarządza kolejką połączeń przychodzących za kulisami.

Dwa procesy nie mogą połączyć się z tym samym portem w tym samym czasie - domyślnie i tak.

Zastanawiam się, czy istnieje sposób (na dowolnym znanym systemie operacyjnym, zwłaszcza Windows), aby uruchomić wiele instancji procesu, tak aby wszystkie wiązały się z gniazda, a więc skutecznie dzielą się kolejką. Każda instancja procesu może być jednowątkowa; blokuje się po zaakceptowaniu nowego połączenia. Gdy klient jest podłączony, jedno z bezczynnych instancji procesu zaakceptuje tego klienta.

To pozwoliłoby każdemu procesowi na bardzo prostą, jednowątkową implementację, dzielącą się niczym, chyba że poprzez jawną pamięć współdzieloną, a użytkownik byłby w stanie dostosować przepustowość przetwarzania, uruchamiając więcej przypadki.

Czy taka funkcja istnieje?

Edit: dla tych, którzy pytają " Dlaczego nie używać wątków?"Oczywiście wątki są opcją. Jednak w przypadku wielu wątków w jednym procesie wszystkie obiekty są udostępniane i należy zachować szczególną ostrożność, aby upewnić się, że obiekty nie są udostępniane lub są widoczne tylko dla jednego wątku na raz, lub są absolutnie niezmienne, a większość popularnych języków i środowisk uruchomieniowych nie ma wbudowanej obsługi zarządzania tą złożonością.

Rozpoczynając garść identycznych procesów roboczych, można uzyskać system współbieżny, w którym domyślnym jest brak współdzielenia, co znacznie ułatwia zbudowanie poprawnej i skalowalnej implementacji.

Author: Daniel Earwicker, 2009-03-22

10 answers

Możesz współdzielić Gniazdo pomiędzy dwoma (lub więcej) procesami w Linuksie, a nawet w systemie Windows.

Pod Linuksem (lub systemem operacyjnym typu POSIX) użycie fork() spowoduje, że rozwidlone dziecko będzie miało kopie wszystkich deskryptorów plików rodzica. Wszystkie, które nie zostaną zamknięte, będą nadal współdzielone i (na przykład z gniazdem nasłuchującym TCP) mogą być używane do accept() nowych gniazd dla klientów. Tak działa wiele serwerów, w tym Apache w większości przypadków.

W Windows To samo jest w zasadzie prawdą, z wyjątkiem tego, że nie ma fork() wywołania systemowego, więc proces nadrzędny będzie musiał użyć CreateProcess lub czegoś, aby utworzyć proces potomny (który oczywiście może używać tego samego pliku wykonywalnego) i musi przekazać mu dziedziczny uchwyt.

Uczynienie gniazda nasłuchowego dziedzicznym uchwytem nie jest całkowicie trywialną czynnością, ale nie jest zbyt skomplikowaną. DuplicateHandle() musi być użyty do utworzenia zduplikowanego uchwytu (nadal jednak w procesie nadrzędnym), który będzie miał ustawioną dziedziczną flagę. Wtedy możesz dać to obsługuje w strukturze STARTUPINFO proces potomny w CreateProcess jako STDIN, OUT lub ERR Uchwyt (zakładając, że nie chcesz go używać do niczego innego).

EDIT:

Czytając bibliotekę MDSN, wydaje się, że WSADuplicateSocket jest bardziej solidnym lub poprawnym mechanizmem tego działania; nadal jest to nietrywialne, ponieważ procesy nadrzędne / podrzędne muszą ustalić, który uchwyt musi być duplikowany przez jakiś mechanizm IPC (chociaż może to być tak proste, jak plik w protokole IPC). filesystem)

Wyjaśnienie:

W odpowiedzi na pierwotne pytanie OP, nie, wiele procesów nie może bind(); tylko oryginalny proces macierzysty wywołałby bind(), listen() itp., procesy potomne będą przetwarzać żądania przez accept(), send(), recv() itd.

 82
Author: MarkR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-14 03:17:48

Większość innych podała techniczne powody, dla których to działa. Oto kod Pythona, który możesz uruchomić, aby zademonstrować to sobie:

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()

Zauważ, że rzeczywiście istnieją dwa procesy ID nasłuchujące:

$ 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)

Oto wyniki z uruchomienia telnetu i programu:

$ 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
 26
Author: Anil Vaitla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-16 20:17:55

Wygląda na to, że na to pytanie odpowiedzieli już MarkR i zackthehack, ale chciałbym dodać, że Nginx jest przykładem modelu dziedziczenia gniazd nasłuchowych.

Oto dobry opis:

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

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

...

W związku z tym, że NGINX nie jest w stanie sprostać wymaganiom klientów, nie jest w stanie sprostać ich wymaganiom.]}

Po głównym procesie NGINX odczytuje plik konfiguracyjny i widły do skonfigurowanej liczby procesów roboczych, każdy proces roboczy wchodzi w pętlę, w której czeka na wszelkie zdarzenia na swoich zestaw gniazd.

Każdy proces roboczy zaczyna się od samych gniazd nasłuchowych, ponieważ nie ma jeszcze żadnych połączeń. W związku z tym wydarzenie zestaw deskryptorów dla każdego procesu roboczego rozpoczyna się od gniazda nasłuchowe.

(Uwaga) NGINX można skonfigurować tak, aby używał dowolnego z kilku zdarzeń ankieta mechanizmy: aio / devpoll / epoll/eventpoll/kqueue/poll/rtsig / select

Gdy połączenie dociera do któregokolwiek z gniazd nasłuchowych (POP3/IMAP/SMTP), każdy proces roboczy wyłania się z ankiety zdarzeń, ponieważ każdy proces roboczy nginx dziedziczy Gniazdo nasłuchowe. Wtedy, każdy proces nginx worker będzie próbował zdobyć globalny mutex. Jeden z procesów roboczych uzyska blokadę, podczas gdy inni wrócą do swoich pętli wyborczych.

Tymczasem proces pracownika, który nabył globalny mutex, będzie zbadaj wywołane zdarzenia i utworzy niezbędną kolejkę roboczą żądania dla każdego zdarzenia, które zostało wywołane. Zdarzenie odpowiada deskryptor pojedynczego gniazda z zestawu deskryptorów, które pracownik obserwował wydarzenia z.

Jeśli wywołane zdarzenie odpowiada nowemu połączeniu przychodzącemu, NGINX akceptuje połączenie z gniazda nasłuchowego. Wtedy to kojarzy kontekst struktura danych z deskryptorem pliku. To context przechowuje informacje o połączeniu (czy POP3/IMAP/SMTP, czy użytkownik jest jeszcze uwierzytelniony, itp.). Wtedy, to nowo zbudowane gniazdo jest dodawane do zestawu deskryptorów zdarzeń dla tego procesu pracowniczego.

Pracownik rezygnuje teraz z mutexu (co oznacza, że wszelkie zdarzenia który przybył na innych pracowników może procedeed), i rozpoczyna przetwarzanie każde żądanie, które było wcześniej w kolejce. Każde żądanie odpowiada zdarzenie, które zostało zasygnalizowane. Z każdego deskryptora gniazda, który był sygnalizowane, proces worker pobiera odpowiedni kontekst strukturę danych, która wcześniej była powiązana z tym deskryptorem, oraz następnie wywołuje odpowiednie funkcje call back, które wykonują działania oparte na stanie tego połączenia. Na przykład, w przypadku z nowo utworzonego połączenia IMAP, pierwszą rzeczą, którą NGINX zrobi to napisanie standardowej wiadomości powitalnej IMAP na
podłączone Gniazdo (*OK IMAP4 ready).

By and by, każdy proces roboczy kończy przetwarzanie kolejki pracy wpis do każdego wyjątkowego wydarzenia i wraca do swojego wydarzenia pętla Wyborcza. Po nawiązaniu jakiegokolwiek połączenia z klientem, zdarzenia są zwykle szybsze, ponieważ gdy podłączone Gniazdo jest gotowy do odczytu, wyzwalane jest Zdarzenie read, a należy podjąć odpowiednie działania.

 13
Author: richardw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-02 09:33:45

Chciałbym dodać, że gniazda mogą być współdzielone na Unix/Linux poprzez gniazda AF _ _ UNIX (gniazda między procesami). Wydaje się, że tworzony jest nowy deskryptor gniazda, który jest w pewnym sensie aliasem do oryginalnego. Ten nowy deskryptor gniazd jest wysyłany przez gniazdo AFUNIX do innego procesu. Jest to szczególnie przydatne w przypadkach, gdy proces nie może fork() udostępniać swoich deskryptorów plików. Na przykład podczas korzystania z bibliotek, które zapobiegają temu z powodu problemów z wątkiem. Powinieneś Utwórz Gniazdo domeny uniksowej i użyj libancillary do wysłania przez deskryptor.

Zobacz:

Do tworzenia gniazd AF_UNIX:

Na przykład kod:

 12
Author: zachthehack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-18 23:41:14

Nie wiem, jak istotne jest to dla pierwotnego pytania, ale w jądrze Linuksa 3.9 jest łatka dodająca funkcję TCP / UDP: obsługa TCP i UDP dla opcji so_reuseport so_reuseport; nowa opcja socket pozwala wielu gniazdom na tym samym hoście wiązać się z tym samym portem i ma na celu poprawę wydajności wielowątkowych aplikacji serwerowych działających na systemach wielordzeniowych. więcej informacji można znaleźć w linku LWN LWN SO_REUSEPORT w jądrze Linuksa 3.9 jako wymienione w odnośniku:

Opcja SO_REUSEPORT jest niestandardowa, ale dostępna w podobnej formie na wielu innych systemach uniksowych(w szczególności na BSD, gdzie powstał pomysł). Wydaje się być użyteczną alternatywą dla wyciskania maksymalnej wydajności z aplikacji sieciowych działających w systemach wielordzeniowych, bez konieczności używania wzorca fork.

 9
Author: Walid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-05 05:54:32

Mają jedno zadanie, którego jedynym zadaniem jest nasłuchiwanie połączeń przychodzących. Gdy połączenie jest odbierane, akceptuje połączenie - tworzy to oddzielny deskryptor gniazda. Zaakceptowane gniazdo jest przekazywane do jednego z dostępnych zadań workera, a główne zadanie wraca do nasłuchu.

s = socket();
bind(s);
listen(s);
while (1) {
  s2 = accept(s);
  send_to_worker(s2);
}
 3
Author: HUAGHAGUAH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-22 13:44:23

Począwszy od Linuksa 3.9, możesz ustawić SO_REUSEPORT na gnieździe, a następnie mieć wiele niezwiązanych procesów współdzielących to gniazdo. To prostsze niż schemat prefork, koniec problemów z sygnałem, wyciek fd do procesów potomnych itp.

Linux 3.9 wprowadził nowy sposób zapisu serwerów socket

Opcja so_reuseport

 3
Author: Benoît,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-20 18:20:44

Innym podejściem (które pozwala uniknąć wielu skomplikowanych szczegółów) w systemie Windows, jeśli używasz HTTP, jest użycie HTTP.SYS . Pozwala to wielu procesom nasłuchiwać różnych adresów URL na tym samym porcie. Na serwerze 2003/2008 / Vista / 7 tak działa IIS, dzięki czemu można z nim współdzielić porty. (NA XP SP2 HTTP.SYS jest obsługiwany, ale IIS5.1 go nie używa.)

Inne interfejsy API wysokiego poziomu (w tym WCF) wykorzystują protokół HTTP.SYS.

 2
Author: Richard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-22 12:19:25

Pod Windows (i Linuksem) jest możliwe, że jeden proces otworzy gniazdo, a następnie przekaże je innemu procesowi, tak że ten drugi proces może również użyć tego gniazda (i przekazać je dalej, jeśli zechce).

Kluczowym wywołaniem funkcji jest WSADuplicateSocket ().

To zapełnia strukturę informacjami o istniejącym gnieździe. Ta struktura następnie, za pośrednictwem mechanizmu IPC do wyboru, jest przekazywana do innego istniejącego procesu (Uwaga mówię istniejący - po wywołaniu WSADuplicateSocket () należy wskazać proces docelowy, który otrzyma emitowaną informację).

Proces odbierający może następnie wywołać WSASocket (), przekazując tę strukturę informacji i odbierając uchwyt do gniazda bazowego.

Oba procesy trzymają teraz uchwyt do tego samego gniazda.

 2
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-28 18:15:35

Wygląda na to, że chcesz, aby jeden proces nasłuchiwał nowych klientów, a następnie przekazywał połączenie po uzyskaniu połączenia. Aby to zrobić w różnych wątkach jest łatwe i w. Net masz nawet BeginAccept itp. metody dbania o wiele hydrauliki dla Ciebie. Przekazanie połączeń ponad granicami procesu byłoby skomplikowane i nie miałoby żadnych zalet wydajności.

Alternatywnie możesz mieć wiele procesów połączonych i nasłuchujących na tym samym Gniazdo.

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 + ".");
}

Jeśli odpalisz dwa procesy, każdy wykonujący powyższy kod będzie działał i pierwszy proces wydaje się uzyskać wszystkie połączenia. Jeśli pierwszy proces zostanie zabity, drugi otrzyma połączenia. Z socket sharing w ten sposób nie jestem pewien dokładnie, jak Windows decyduje, który proces dostaje nowe połączenia, chociaż szybki test wskazuje na najstarszy proces dostaje je pierwszy. Co do tego, czy dzieli się, czy pierwszy Proces jest zajęty, czy coś w tym stylu, Nie wiem wiedzieć.

 1
Author: sipwiz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-22 12:18:31