Frage Gibt es eine Möglichkeit für mehrere Prozesse, sich einen abhörenden Socket zu teilen?


Bei der Socket-Programmierung erstellen Sie einen Listening-Socket, und dann erhalten Sie für jeden Client, der eine Verbindung herstellt, einen normalen Stream-Socket, mit dem Sie die Anfrage des Clients bearbeiten können. Das OS verwaltet die Warteschlange eingehender Verbindungen im Hintergrund.

Zwei Prozesse können nicht gleichzeitig an denselben Port gebunden werden - standardmäßig sowieso.

Ich frage mich, ob es einen Weg (auf jedem bekannten Betriebssystem, insbesondere Windows) gibt, mehrere Instanzen eines Prozesses zu starten, so dass sie alle an den Socket binden, und so teilen sie effektiv die Warteschlange. Jede Prozessinstanz könnte dann single threaded sein; Es würde nur blockieren, wenn eine neue Verbindung akzeptiert wird. Wenn ein Client verbunden ist, würde eine der inaktiven Prozessinstanzen diesen Client akzeptieren.

Dies würde es jedem Prozess ermöglichen, eine sehr einfache Implementierung mit nur einem Thread zu haben, die nichts teilen würde, außer durch expliziten gemeinsamen Speicher, und der Benutzer wäre in der Lage, die Verarbeitungsbandbreite anzupassen, indem er mehr Instanzen startet.

Gibt es ein solches Feature?

Bearbeiten: Für diejenigen, die fragen "Warum nicht Threads verwenden?" Offensichtlich sind Threads eine Option. Aber bei mehreren Threads in einem einzigen Prozess sind alle Objekte gemeinsam nutzbar und es muss sehr sorgfältig darauf geachtet werden, dass Objekte entweder nicht gemeinsam oder nur für jeweils einen Thread sichtbar sind oder absolut unveränderlich sind und dass die meisten populären Sprachen und Laufzeiten fehlt integrierte Unterstützung für die Verwaltung dieser Komplexität.

Wenn Sie eine Handvoll identischer Worker-Prozesse starten, erhalten Sie ein Concurrent-System, in dem die Standard ist keine gemeinsame Nutzung, wodurch es viel einfacher ist, eine korrekte und skalierbare Implementierung zu erstellen.


76
2018-03-22 11:56


Ursprung


Antworten:


Sie können einen Socket zwischen zwei (oder mehr) Prozessen unter Linux und sogar Windows freigeben.

Unter Linux (oder POSIX-Betriebssystem), mit fork() bewirkt, dass das gegabelte Kind Kopien aller Dateideskriptoren des Elternteils hat. Alles was es nicht schließt, wird weiterhin geteilt und kann (zB mit einem TCP-Abhörsocket) benutzt werden accept() neue Sockets für Clients. Dies ist, wie viele Server, einschließlich Apache in den meisten Fällen, arbeiten.

Unter Windows gilt das Gleiche, außer dass es keine gibt fork() Systemaufruf, so dass der Elternprozess verwendet werden muss CreateProcess oder etwas, um einen Child-Prozess zu erstellen (der natürlich dieselbe ausführbare Datei verwenden kann), und muss ihm einen vererbbaren Handle übergeben.

Einen hörenden Sockel zu einem vererbbaren Handle zu machen ist keine völlig triviale Aktivität, aber auch nicht zu kompliziert. DuplicateHandle() muss verwendet werden, um ein doppeltes Handle zu erstellen (das sich immer noch im übergeordneten Prozess befindet), auf dem das vererbbare Flag gesetzt sein wird. Dann können Sie diesen Griff in die geben STARTUPINFO Struktur zum Kindprozess in CreateProcess als a STDIN, OUT oder ERR handle (vorausgesetzt, Sie wollten es für nichts anderes verwenden).

BEARBEITEN:

Wenn man die MDSN-Bibliothek liest, sieht es so aus WSADuplicateSocket ist ein robusterer oder korrekterer Mechanismus, dies zu tun; es ist immer noch nicht-trivial, weil die Eltern / Kind-Prozesse ausarbeiten müssen, welches Handle von irgendeinem IPC-Mechanismus dupliziert werden muss (obwohl dies so einfach wie eine Datei im Dateisystem sein könnte)

KLÄRUNG:

Als Antwort auf die ursprüngliche Frage des OP, nein, können mehrere Prozesse nicht bind(); nur der ursprüngliche Elternprozess würde aufrufen bind(), listen() usw. würden die untergeordneten Prozesse nur Anfragen verarbeiten accept(), send(), recv() etc.


81
2018-03-22 12:02



Die meisten anderen haben die technischen Gründe dafür angegeben. Hier ist etwas Python-Code, den Sie ausführen können, um dies selbst zu demonstrieren:

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

Beachten Sie, dass tatsächlich zwei Prozess-IDs zuhören:

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

Hier sind die Ergebnisse von Telnet und dem Programm:

$ 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
2017-10-09 10:27



Sieht so aus, als ob diese Frage bereits vollständig von MarkR und zackthehack beantwortet wurde, aber ich möchte hinzufügen, dass Nginx ein Beispiel für das Vererbungsmodell des Hörsockets ist.

Hier ist eine gute Beschreibung:

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

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

...

Ablauf eines NGINX-Arbeitsprozesses

Nachdem der Hauptprozess von NGINX die Konfigurationsdatei und die Gabeln gelesen hat   in die konfigurierte Anzahl von Worker-Prozessen, jeden Worker-Prozess   tritt in eine Schleife ein, in der es auf irgendwelche Ereignisse auf seinem jeweiligen wartet   Satz Steckdosen.

Jeder Arbeitsprozess beginnt mit nur den hörenden Sockets,   da sind noch keine Verbindungen verfügbar. Daher das Ereignis   Der Deskriptorsatz für jeden Arbeitsprozess beginnt mit nur dem   Buchsen hören.

(HINWEIS) NGINX kann für die Verwendung eines beliebigen Ereignisses konfiguriert werden   Abfragemechanismen:   aio / devpoll / epoll / ereignispoll / kqueue / poll / rtsig / select

Wenn eine Verbindung an einer der hörbaren Buchsen ankommt   (POP3 / IMAP / SMTP), jeder Arbeitsprozess ergibt sich aus seiner Ereignisumfrage,   da jeder NGINX-Arbeitsprozess den hörenden Socket erbt. Dann,   Jeder NGINX-Worker-Prozess versucht, einen globalen Mutex zu erhalten.   Einer der Worker-Prozesse wird die Sperre erhalten, während die   Andere werden zu ihren jeweiligen Ereignisabrufschleifen zurückkehren.

Inzwischen wird der Worker-Prozess, der den globalen Mutex erworben hat, dies tun   Untersuchen Sie die ausgelösten Ereignisse und erstellen Sie die erforderliche Arbeitswarteschlange   Anforderungen für jedes Ereignis, das ausgelöst wurde. Ein Ereignis entspricht   ein einzelner Socket-Deskriptor aus der Menge der Deskriptoren, die der   Arbeiter warteten auf Ereignisse von.

Wenn das ausgelöste Ereignis einer neuen eingehenden Verbindung entspricht,   NGINX akzeptiert die Verbindung vom hörenden Socket. Dann es   Ordnet dem Dateideskriptor eine Kontextdatenstruktur zu. Dies   Kontext hält Informationen über die Verbindung (ob   POP3 / IMAP / SMTP, ob der Benutzer noch authentifiziert ist, usw.). Dann,   Dieser neu konstruierte Socket wird dem Ereignisdeskriptorsatz hinzugefügt   für diesen Arbeiterprozess.

Der Arbeiter gibt jetzt den Mutex (was irgendwelche Ereignisse bedeutet) auf   die an anderen Arbeitern angekommen sind, kann ausgeführt werden) und beginnt mit der Verarbeitung   jede Anfrage, die früher in die Warteschlange gestellt wurde. Jede Anfrage entspricht einem   Ereignis, das signalisiert wurde. Von jedem Socket-Deskriptor, der war   signalisiert, ruft der Worker-Prozess den entsprechenden Kontext ab   Datenstruktur, die zuvor diesem Deskriptor zugeordnet wurde, und   ruft dann die entsprechenden Rückruffunktionen auf, die ausgeführt werden   Aktionen basierend auf dem Status dieser Verbindung. Zum Beispiel für den Fall   einer neu eingerichteten IMAP-Verbindung, das erste, was NGINX   wird tun, ist die standard - IMAP Willkommensnachricht auf die schreiben
angeschlossener Anschluss (* OK IMAP4 bereit).

Nach und nach beendet jeder Arbeitsprozess die Verarbeitung der Arbeitswarteschlange   Eintrag für jedes herausragende Ereignis und kehrt zu seinem Ereignis zurück   Abfrageschleife. Sobald eine Verbindung zu einem Client hergestellt wurde,   Ereignisse sind normalerweise schneller, seit wann immer der angeschlossene Socket   ist bereit zum Lesen, das Leseereignis wird ausgelöst, und das   entsprechende Maßnahmen müssen getroffen werden.


13
2017-09-02 09:33



Ich möchte hinzufügen, dass die Sockets unter Unix / Linux über AF__UNIX-Sockets (Interprozess-Sockets) gemeinsam genutzt werden können. Was zu geschehen scheint, ist ein neuer Socket-Deskriptor, der etwas von einem Alias ​​zum ursprünglichen erstellt wird. Dieser neue Socket-Deskriptor wird über den AFUNIX-Socket an den anderen Prozess gesendet. Dies ist besonders nützlich in Fällen, in denen ein Prozess die Dateideskriptoren nicht teilen kann (). Zum Beispiel bei der Verwendung von Bibliotheken, die dies aufgrund von Threading-Problemen verhindern. Sie sollten einen Unix-Domain-Socket erstellen und verwenden libanesisch über den Deskriptor senden.

Sehen:

Zum Erstellen von AF_UNIX-Sockets:

Zum Beispiel Code:


12
2017-07-16 18:29



Nicht sicher, wie relevant dies für die ursprüngliche Frage ist, aber im Linux-Kernel 3.9 gibt es einen Patch, der ein TCP / UDP-Feature hinzufügt: TCP- und UDP-Unterstützung für die SO_REUSEPORT-Socket-Option; Mit der neuen Socket-Option können mehrere Sockets auf demselben Host an den gleichen Port gebunden werden, und die Leistung von Multithread-Netzwerkserveranwendungen, die auf Multicore-Systemen ausgeführt werden, soll verbessert werden. Weitere Informationen finden Sie im LWN-Link LWN SO_REUSEPORT in Linux Kernel 3.9 wie im Referenzlink erwähnt:

Die SO_REUSEPORT-Option ist nicht standardgemäß, aber in ähnlicher Form auf einer Reihe anderer UNIX-Systeme verfügbar (insbesondere auf den BSDs, von denen die Idee stammt). Es scheint eine nützliche Alternative zu sein, um Netzwerkanwendungen, die auf Multicore-Systemen laufen, die maximale Leistung zu entziehen, ohne das Gabelmuster zu verwenden.


9
2018-05-04 03:25



Haben Sie eine einzelne Aufgabe, deren einzige Aufgabe es ist, auf eingehende Verbindungen zu warten. Wenn eine Verbindung empfangen wird, akzeptiert sie die Verbindung - dies erstellt einen separaten Socket-Deskriptor. Der akzeptierte Socket wird an einen Ihrer verfügbaren Worker-Tasks übergeben, und die Hauptaufgabe wird wieder mit dem Abhören behandelt.

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

3
2018-03-22 13:44



Ab Linux 3.9 können Sie den SO_REUSEPORT für einen Socket festlegen und dann mehrere nicht verwandte Prozesse diesen Socket freigeben. Das ist einfacher als das Prefork-Schema, keine Signalstörungen mehr, fd-Leak zu Child-Prozessen usw.

Linux 3.9 führte eine neue Art ein, Socket-Server zu schreiben

Die SO_REUSEPORT-Socket-Option


3
2018-02-20 18:20



Ein anderer Ansatz (der viele komplexe Details vermeidet) in Windows, wenn Sie HTTP verwenden, ist zu verwenden HTTP.SYS. Dadurch können mehrere Prozesse verschiedene URLs auf demselben Port abhören. Auf Server 2003/2008 / Vista / 7 funktioniert IIS so, dass Sie Ports damit teilen können. (Unter XP SP2 wird HTTP.SYS unterstützt, aber IIS 5.1 verwendet es nicht.)

Andere High-Level-APIs (einschließlich WCF) verwenden HTTP.SYS.


2
2018-03-22 12:19



Unter Windows (und Linux) ist es möglich, dass ein Prozess einen Socket öffnet und diesen Socket an einen anderen Prozess weiterleitet, so dass der zweite Prozess diesen Socket dann auch verwenden kann (und ihn dann weitergeben kann, falls er dies wünscht). .

Der entscheidende Funktionsaufruf ist WSADuplicateSocket ().

Dadurch wird eine Struktur mit Informationen zu einem vorhandenen Socket gefüllt. Diese Struktur wird dann über einen IPC-Mechanismus Ihrer Wahl an einen anderen vorhandenen Prozess übergeben (Hinweis: Ich sage vorhanden - wenn Sie WSADuplicateSocket () aufrufen, müssen Sie den Zielprozess angeben, der die ausgegebenen Informationen erhält).

Der empfangende Prozess kann dann WSASocket () aufrufen, diese Informationsstruktur übergeben und ein Handle für den zugrunde liegenden Socket empfangen.

Beide Prozesse enthalten jetzt einen Handle für denselben zugrunde liegenden Socket.


2
2018-03-28 18:15