W programowaniu za pomocą gniazd tworzy się gniazdo nasłuchujące, a następnie dla każdego klienta, który się łączy, otrzymuje się normalne gniazdo strumieniowe, którego można użyć do obsługi żądań klienta. System operacyjny zarządza kolejką połączeń przychodzących w tle.
Dwa procesy nie mogą łączyć się z tym samym portem w tym samym czasie - w każdym razie domyślnie.
Zastanawiam się, czy istnieje sposób (w każdym dobrze znanym systemie operacyjnym, zwłaszcza Windows), aby uruchomić wiele instancji procesu, tak aby wszystkie łączyły się z gniazdem i efektywnie współdzieliły kolejkę. Każda instancja procesu może być wtedy jednowątkowa; po prostu blokowałby się przy akceptowaniu nowego połączenia. Gdy klient się połączył, jedna z bezczynnych instancji procesu zaakceptowałaby tego klienta.
Pozwoliłoby to każdemu procesowi mieć bardzo prostą, jednowątkową implementację, nie współużytkując niczego, chyba że poprzez jawną pamięć współdzieloną, a użytkownik byłby w stanie dostosować przepustowość przetwarzania, uruchamiając więcej instancji.
Czy taka funkcja istnieje?
Edycja: dla osób pytających „Dlaczego nie używać wątków?” Oczywiście wątki są opcją. Ale w przypadku wielu wątków w jednym procesie wszystkie obiekty można udostępniać i należy bardzo uważać, aby obiekty albo nie były udostępniane, albo były widoczne tylko dla jednego wątku na raz lub były absolutnie niezmienne, a najpopularniejsze języki i Środowiskom wykonawczym brakuje wbudowanej obsługi zarządzania tą złożonością.
Uruchamiając kilka identycznych procesów roboczych, można uzyskać system współbieżny, w którym domyślnie nie ma współużytkowania, co znacznie ułatwia zbudowanie poprawnej i skalowalnej implementacji.
źródło
Odpowiedzi:
Możesz współdzielić gniazdo między dwoma (lub więcej) procesami w systemie Linux, a nawet Windows.
W Linuksie (lub systemie operacyjnym typu POSIX) użycie
fork()
spowoduje, że rozwidlone dziecko będzie miało kopie wszystkich deskryptorów plików rodzica. Wszystko, czego nie zamknie, będzie nadal udostępniane i (na przykład z gniazdem nasłuchującym TCP) może być używane doaccept()
nowych gniazd dla klientów. Tak działa wiele serwerów, w tym w większości przypadków Apache.W systemie 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 może oczywiście używać tego samego pliku wykonywalnego) i musi przekazać mu dziedziczony uchwyt.Uczynienie gniazda nasłuchującego uchwytem dziedzicznym nie jest całkowicie trywialną czynnością, ale też nie jest zbyt trudne.
DuplicateHandle()
musi być użyty do stworzenia zduplikowanego dojścia (jednak nadal w procesie nadrzędnym), który będzie miał ustawioną dziedziczoną flagę. Następnie możesz przekazać ten uchwyt wSTARTUPINFO
strukturze procesowi potomnemu w CreateProcess jako uchwytSTDIN
,OUT
lubERR
uchwyt (zakładając, że nie chcesz go używać do niczego innego).EDYTOWAĆ:
Czytając bibliotekę MDSN, okazuje się, że
WSADuplicateSocket
jest to bardziej solidny lub poprawny mechanizm robienia tego; jest to nadal nietrywialne, ponieważ procesy nadrzędne / potomne muszą wypracować, który uchwyt musi zostać zduplikowany przez jakiś mechanizm IPC (chociaż może to być tak proste, jak plik w systemie plików)WYJAŚNIENIE:
Odpowiadając na pierwotne pytanie PO: nie, wiele procesów nie może
bind()
; tylko oryginalny proces rodzic nazwałbybind()
,listen()
etc, procesy potomne po prostu przetworzyć prośbyaccept()
,send()
,recv()
itd.źródło
Większość innych podała techniczne powody, dla których to działa. Oto kod w Pythonie, który możesz uruchomić, aby to zademonstrować:
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 nasłuchują dwa identyfikatory procesów:
Oto wyniki działania telnetu i programu:
źródło
Chciałbym dodać, że gniazda mogą być współużytkowane w systemie Unix / Linux przez gniazda AF__UNIX (gniazda międzyprocesowe). Wydaje się, że zostaje utworzony nowy deskryptor gniazda, który jest w pewnym sensie aliasem do oryginalnego. Ten nowy deskryptor gniazda 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ątkami. Powinieneś utworzyć gniazdo domeny Unix i użyć libancillary do przesłania deskryptora.
Widzieć:
Aby utworzyć gniazda AF_UNIX:
Na przykład kod:
źródło
Wygląda na to, że MarkR i zackthehack w pełni odpowiedzieli na to pytanie, ale chciałbym dodać, że Nginx jest przykładem modelu dziedziczenia gniazd nasłuchowych.
Oto dobry opis:
źródło
Nie jestem pewien, jak istotne jest to w stosunku do pierwotnego pytania, ale w jądrze Linuksa 3.9 jest łatka dodająca funkcję TCP / UDP: obsługa TCP i UDP dla opcji gniazda SO_REUSEPORT; Nowa opcja gniazda umożliwia powiązanie wielu gniazd na tym samym hoście z tym samym portem i ma na celu poprawę wydajności wielowątkowych aplikacji serwera sieciowego działających na systemach wielordzeniowych. więcej informacji można znaleźć w linku LWN LWN SO_REUSEPORT w Linux Kernel 3.9, jak wspomniano w odnośniku referencyjnym:
opcja SO_REUSEPORT jest niestandardowa, ale dostępna w podobnej formie w wielu innych systemach UNIX (w szczególności BSD, skąd pomysł). Wydaje się, że jest to użyteczna alternatywa dla wyciskania maksymalnej wydajności z aplikacji sieciowych działających w systemach wielordzeniowych, bez konieczności używania wzorca rozwidlenia.
źródło
SO_REUSEPORT
tworzy pulę wątków, w której każde gniazdo znajduje się w innym wątku, ale tylko jedno gniazdo w grupie wykonujeaccept
. Czy możesz potwierdzić, że wszystkie gniazda w grupie otrzymują kopię danych?Począwszy od Linuksa 3.9, można ustawić SO_REUSEPORT w gnieździe, a następnie mieć wiele niepowiązanych procesów współużytkujących to gniazdo. To prostsze niż schemat prefork, koniec z problemami z sygnałem, wyciekiem fd do procesów potomnych itp.
Linux 3.9 wprowadził nowy sposób pisania serwerów gniazd
Opcja gniazda SO_REUSEPORT
źródło
Miej jedno zadanie, którego jedynym zadaniem jest nasłuchiwanie połączeń przychodzących. Po odebraniu połączenia akceptuje połączenie - tworzy to oddzielny deskryptor gniazda. Zaakceptowane gniazdo jest przekazywane do jednego z dostępnych zadań roboczych, a zadanie główne wraca do nasłuchiwania.
źródło
W systemie Windows (i Linux) jeden proces może otworzyć gniazdo, a następnie przekazać to gniazdo innemu procesowi, tak że ten drugi proces może również użyć tego gniazda (i przekazać je po kolei, jeśli chce to zrobić) .
Kluczowym wywołaniem funkcji jest WSADuplicateSocket ().
Spowoduje to wypełnienie struktury informacjami o istniejącym gnieździe. Ta struktura następnie, poprzez wybrany przez ciebie mechanizm IPC, jest przekazywana do innego istniejącego procesu (uwaga, mówię istniejący - kiedy wywołujesz WSADuplicateSocket (), musisz wskazać proces docelowy, który otrzyma wyemitowane informacje).
Proces odbierający może następnie wywołać WSASocket (), przekazując tę strukturę informacji i otrzymać uchwyt do odpowiedniego gniazda.
Oba procesy mają teraz uchwyt do tego samego podstawowego gniazda.
źródło
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ć między wątkami jest łatwe, a w .Net masz nawet metody BeginAccept itp., Aby zająć się dużą ilością instalacji hydraulicznych za Ciebie. Przekazywanie połączeń między granicami procesu byłoby skomplikowane i nie miałoby żadnych korzyści w zakresie wydajności.
Alternatywnie możesz mieć wiele procesów powiązanych i nasłuchujących w tym samym gnieździe.
Jeśli uruchomisz dwa procesy, z których każdy wykonuje powyższy kod, zadziała, a pierwszy proces wydaje się pobierać wszystkie połączenia. Jeśli pierwszy proces zostanie zabity, drugi pobierze połączenia. Przy takim udostępnianiu gniazd nie jestem pewien, w jaki sposób system Windows decyduje, który proces uzyskuje nowe połączenia, chociaż szybki test wskazuje, że najstarszy proces uzyskuje je jako pierwszy. Nie wiem, czy udostępnia, czy pierwszy proces jest zajęty, czy coś podobnego.
źródło
Innym podejściem (które pozwala uniknąć wielu złożonych szczegółów) w systemie Windows, jeśli używasz protokołu HTTP, jest użycie protokołu HTTP.SYS . Umożliwia to wielu procesom nasłuchiwanie różnych adresów URL na tym samym porcie. Na serwerze 2003/2008 / Vista / 7 tak działają usługi IIS, więc możesz udostępniać mu porty. (W systemie XP SP2 obsługiwany jest protokół HTTP.SYS, ale IIS5.1 go nie używa).
Inne interfejsy API wysokiego poziomu (w tym WCF) korzystają z HTTP.SYS.
źródło