Est-il un moyen pour plusieurs processus partagent une socket d'écoute?

dans la programmation de socket, vous créez une socket d'écoute et ensuite pour chaque client qui se connecte, vous obtenez une socket de flux normale que vous pouvez utiliser pour traiter la demande du client. L'OS gère la file d'attente des connexions entrantes dans les coulisses.

deux processus ne peuvent pas se lier au même port en même temps - de toute façon par défaut.

je me demande s'il y a un moyen (sur un OS bien connu, en particulier Windows) de lancer plusieurs instances de un processus, tel qu'ils se lient tous à la socket, et donc ils partagent effectivement la file d'attente. Chaque instance de processus pourrait alors être filetée individuellement; elle se contenterait de bloquer lors de l'acceptation d'une nouvelle connexion. Lorsqu'un client est connecté, l'une des instances de processus inactif accepte ce client.

cela permettrait à chaque processus d'avoir une implémentation très simple, avec un seul thread, ne partageant rien sauf par le biais d'une mémoire partagée explicite, et l'utilisateur serait en mesure d'ajuster le traiter la bande passante en démarrant plus d'instances.

une telle fonction existe?

Edit: Pour ceux qui demandent "Pourquoi ne pas utiliser les threads?"Évidemment threads sont une option. Mais avec plusieurs threads dans un même processus, tous les objets sont partageables et il faut prendre grand soin de s'assurer que les objets ne sont pas partagés, ou ne sont visibles que par un seul thread à la fois, ou sont absolument immuables, et la plupart des langues populaires et les runtimes ne disposent pas d'un support intégré pour gérer cette complexité.

en démarrant une poignée de processus ouvriers identiques, vous obtiendriez un système concurrent dans lequel le défaut n'est pas de partage, ce qui rend beaucoup plus facile de construire une mise en œuvre correcte et évolutive.

77
demandé sur Daniel Earwicker 2009-03-22 14:56:56

10 réponses

vous pouvez partager une socket entre deux (ou plus) Processus sous Linux et même Windows.

sous Linux (ou os de type POSIX), en utilisant fork() fera que l'enfant bifurqué d'avoir des copies de tous les descripteurs de dossier du parent. Tout ce qu'il ne ferme pas continuera à être partagé, et (par exemple avec une socket D'écoute TCP) peut être utilisé pour les accept() nouvelles sockets pour les clients. C'est le nombre de serveurs, y compris Apache dans la plupart des cas, qui fonctionnent.

sur Windows la même chose est fondamentalement vraie, sauf qu'il n'y a pas d'appel système fork() de sorte que le processus parent devra utiliser CreateProcess ou quelque chose pour créer un processus enfant (qui peut bien sûr utiliser le même exécutable) et a besoin de passer une poignée héréditaire.

faire d'une prise d'écoute une poignée héritable n'est pas une activité complètement banale, mais pas trop délicate non plus. DuplicateHandle() doit être utilisé pour créer un double poignée (toujours dans le processus parent), qui aura l'pouvant être héritées du drapeau. Ensuite, vous pouvez donner cette poignée dans la structure STARTUPINFO au processus enfant dans CreateProcess comme une poignée STDIN , OUT ou ERR (en supposant que vous ne vouliez pas l'utiliser pour autre chose).

EDIT:

en lisant la bibliothèque MDSN, il apparaît que WSADuplicateSocket est un mécanisme plus robuste ou correct de faire ceci; il est toujours non trivial parce que le les processus parent/enfant doivent déterminer quelle manipulation doit être dupliquée par un mécanisme quelconque de la CIB (bien que cela puisse être aussi simple qu'un fichier dans le système de fichiers)

CLARIFICATION:

En réponse à la discussion de la question initiale, non, plusieurs processus ne peut pas bind() ; le processus parent d'origine pourrait appeler bind() , listen() , etc, l'enfant dans le processus de simplement traiter les demandes par accept() , send() , recv() etc.

82
répondu MarkR 2014-08-14 03:17:48

la Plupart des autres ont fourni les raisons techniques pour lesquelles cela fonctionne. Voici du code python que vous pouvez utiliser pour le démontrer par vous-même:

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

notez qu'il y a en effet deux écouteurs de process 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)

Voici les résultats de l'exécution de telnet et du programme:

$ 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
répondu Anil Vaitla 2014-09-16 20:17:55

on dirait que cette question a déjà reçu une réponse complète de MarkR et zackthehack mais je voudrais ajouter que Nginx est un exemple du modèle d'héritage de socket d'écoute.

Voici une bonne description:

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

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

...

flux d'un processus d'ouvrier de NGINX

après le processus NGINX principal lit le fichier de configuration et les fourches dans la configuration du nombre de processus de travail, chaque processus de travail entre dans une boucle où il attend les événements sur son un ensemble de prises de.

chaque processus ouvrier commence avec seulement les prises d'écoute, puisqu'il n'y a pas encore de connexion disponible. Par conséquent, l'événement descripteur pour chaque processus de travail commence avec l' les sockets d'écoute.

(NOTE) NGINX peut être configuré pour utiliser n'importe lequel de plusieurs événements les mécanismes d'interrogation: aio/devpoll/epoll/eventpoll/kqueue/sondage/rtsig/sélectionnez

Lorsqu'une connexion arrive sur les sockets en écoute (POP3 / IMAP / SMTP), chaque processus de travail émerge de son sondage, depuis chaque processus NGINX worker hérite de la socket d'écoute. Puis, chaque processus NGINX worker tentera d'acquérir un mutex global. L'un des processus ouvriers acquerra la serrure, tandis que le d'autres retourneront à leurs boucles de sondage respectives.

pendant ce temps, le processus ouvrier qui a acquis le mutex global va examiner les événements déclenchés, et créera la file d'attente de travail nécessaire les demandes pour chaque événement a été déclenché. Un événement correspond à un descripteur de socket simple de l'ensemble des descripteurs que le travailleur a été regarder pour les événements d'.

si l'événement déclenché correspond à une nouvelle connexion entrante, NGINX accepte la connexion à partir de la socket d'écoute. Ensuite, il associe une structure de données contextuelles avec le descripteur de fichier. Ce contexte contient des informations sur la connexion (si POP3/IMAP / SMTP, si l'utilisateur est encore authentifié, etc). Puis, cette nouvelle socket est ajoutée dans le jeu de descripteurs d'événements pour ce processus de travail.

l'ouvrier abandonne maintenant le mutex (ce qui signifie que tout événement qui sont arrivés sur d'autres travailleurs peuvent procéder) , et commence à la transformation chaque demande qui a été mise en file d'attente plus tôt. Chaque demande correspond à un l'événement qui a été signalé. De chaque descripteur de douille qui a été signalé, le processus de travail récupère le contexte correspondant la structure des données qui était auparavant associée à ce descripteur, et puis appelle le rappel correspondant fonctions qui remplissent actions fondées sur l'état de cette connexion. Par exemple, dans le cas D'une connexion IMAP nouvellement établie, la première chose que NGINX va faire est d'écrire le standard IMAP message de bienvenue sur le

prise connectée (*OK IMAP4 prêt).

par et par, chaque processus de travailleur achève le traitement de la file d'attente de travail entrée pour chaque événement exceptionnel, et retourne à son événement boucle d'interrogation. Une fois que tout la connexion est établie avec un client, le les événements sont généralement plus rapides, car chaque fois que la prise connectée est prêt pour la lecture, l'événement de lecture est déclenché, et le des mesures correspondantes doivent être prises.

13
répondu richardw 2011-09-02 09:33:45

je voudrais ajouter que les sockets peuvent être partagés sur Unix/Linux via des sockets AF__UNIX (inter-process sockets). Ce qui semble se passer, c'est un nouveau descripteur de socket est créé, qui est en quelque sorte un alias à l'original. Ce nouveau descripteur de socket est envoyé via la socket AFUNIX à L'autre processus. Ceci est particulièrement utile dans les cas où un processus ne peut pas bifurquer() pour partager ses descripteurs de fichiers. Par exemple, lors de l'utilisation de bibliothèques qui empêchent contre cela en raison de problèmes de threading. Vous devrait créer une socket de domaine Unix et utiliser libancillary pour envoyer le descripteur.

voir:

Pour créer les Sockets AF_UNIX:

exemple de code:

12
répondu zachthehack 2009-07-18 23:41:14

Je ne suis pas sûr que cela soit pertinent pour la question d'origine, mais dans le noyau Linux 3.9 il y a un patch qui ajoute une fonctionnalité TCP/UDP: le support TCP et UDP pour l'option socket SO_REUSEPORT; la nouvelle option socket permet à plusieurs sockets sur le même hôte de se lier au même port, et est destiné à améliorer les performances des applications de serveur réseau multithread sur les systèmes multicore. plus d'informations peuvent être trouvées dans le lien LWN LWN SO_REUSEPORT dans le noyau Linux 3.9 comme mentionné dans le lien de référence:

L'option SO_REUSEPORT n'est pas standard, mais disponible sous une forme similaire sur un certain nombre d'autres systèmes UNIX (notamment, le BSDs, d'où vient l'idée). Il semble offrir une alternative utile pour extraire les performances maximales des applications réseau fonctionnant sur des systèmes multicore, sans avoir à utiliser le modèle de fourche.

9
répondu Walid 2017-01-05 05:54:32

ont une seule tâche dont la seule tâche est d'écouter les connexions entrantes. Quand une connexion est reçue, elle accepte la connexion - cela crée un descripteur de socket séparé. Le socket accepté est passé à un de vos travailleurs des tâches, et la tâche principale remonte à l'écoute.

s = socket();
bind(s);
listen(s);
while (1) {
  s2 = accept(s);
  send_to_worker(s2);
}
3
répondu HUAGHAGUAH 2009-03-22 13:44:23

à partir de Linux 3.9, vous pouvez configurer le SO_REUSEPORT sur une socket et ensuite avoir plusieurs processus non liés qui partagent cette socket. C'est plus simple que le schéma prefork, plus de problèmes de signal, fuite de fd vers les processus enfants, etc.

Linux 3.9 introduit une nouvelle façon d'écrire les serveurs socket

l'option socket SO_REUSEPORT

3
répondu Benoît 2016-02-20 18:20:44

une autre approche (qui évite beaucoup de détails complexes) dans Windows si vous utilisez HTTP, est d'utiliser HTTP.SYS . Cela permet à plusieurs processus d'écouter des URLs différentes sur le même port. Sur le serveur 2003/2008 / Vista / 7 c'est ainsi que fonctionne IIS, vous pouvez donc partager des ports avec lui. (Sur XP SP2 HTTP.SYS est supporté, mais IIS5.Je ne l'utilise pas.)

autres API de haut niveau (y compris WCF) utilisent HTTP.SYS.

2
répondu Richard 2009-03-22 12:19:25

sous Windows (et Linux) il est possible pour un processus d'ouvrir une socket et de passer ensuite cette socket à un autre processus tel que ce second processus peut également utiliser cette socket (et la transmettre à son tour, s'il le souhaite).

l'appel de fonction crucial est WSADuplicateSocket().

cette structure contient des informations sur une socket existante. Cette structure est ensuite, via un mécanisme IPC de votre choix, transmise à un autre processus existant (note je dis existant-lorsque vous appelez WSADuplicateSocket (), vous devez indiquer le processus cible qui recevra les informations émises).

le processus de réception peut alors appeler WSASocket(), passer dans cette structure d'information, et recevoir une poignée à la socket sous-jacente.

les deux processus tiennent maintenant une poignée à la même prise sous-jacente.

2
répondu 2009-03-28 18:15:35

il semble que ce que vous voulez est un processus d'écoute pour les nouveaux clients et ensuite passer la connexion une fois que vous obtenez une connexion. Faire cela à travers les fils est facile et dans. Net vous avez même le BeginAccept etc. méthodes pour prendre soin de beaucoup de la plomberie pour vous. Transférer les connexions au-delà des limites du processus serait compliqué et n'aurait pas d'avantages sur le plan du rendement.

alternativement vous pouvez avoir plusieurs processus liés et l'écoute sur le même socket.

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

si vous démarrez deux processus en exécutant chacun le code ci-dessus, il fonctionnera et le premier processus semble obtenir toutes les connexions. Si le premier processus est tué, le second reçoit alors les connexions. Avec le partage de socket comme ça, Je ne sais pas exactement comment Windows décide quel processus obtient de nouvelles connexions, bien que le test rapide indique que le plus vieux processus les obtient en premier. Quant à savoir si elle partage si le premier processus est occupé ou quelque chose comme ça je ne sais pas.

1
répondu sipwiz 2009-03-22 12:18:31