2013-01-20 11 views
8

Ich stecke auf ein Problem in einem C-Programm unter Linux.Wie kann ich einen Socket von Eltern zu Kind Prozesse übergeben

Ich weiß, dass, wenn ein Prozesse gegabelte der Kindprozess erbt einige Dinge aus dem übergeordneten, einschließlich öffnen Dateideskriptoren.

Das Problem ist, dass ich eine Multi-Prozess-Server-Anwendung mit einem Master-Prozess, der neue Verbindungen akzeptiert und schreibt die Deskriptoren in den gemeinsamen Speicher schreibt.

Wenn der Kindprozess versucht, von einem dieser Deskriptoren aus dem gemeinsamen Speicher zu lesen, unter select() habe ich einen EBADF Fehler erhalten!

Wie kann der untergeordnete Prozess einen Socket (oder einen beliebigen Dateideskriptor im Allgemeinen) lesen und verwenden, der von einem übergeordneten Prozess erstellt wurde nach wurde er gespalten?

Antwort

9

Wenn Sie Gabel rufen, erbt der Kind-Prozess Kopien aller offenen Dateideskriptoren. Die übliche Vorgehensweise besteht darin, dass ein Parent-Prozess einen Listening-Socket öffnet, call accept, der blockiert, bis eine Verbindung eintrifft, und dann nach Erhalt der Verbindung fork aufruft. Das übergeordnete Element schließt dann seine Kopie des Dateideskriptors, während der neue untergeordnete Prozess weiterhin den Dateideskriptor verwenden und alle erforderlichen Verarbeitungsschritte ausführen kann. Sobald das Kind fertig ist es auch schließt den Sockel. Es ist wichtig, sich an zwei Dinge zu erinnern: 1. Der Dateideskriptor/Socket ist eine Ressource im Betriebssystem und nach der Verzweigung haben Eltern und Kind jeweils ein Handle für diese Ressource, was wie ein Smartzeiger für die Referenzzählung ist. Ich erkläre dies in more detail here. Die zweite Sache ist, dass nur Dateideskriptoren geöffnet werden, die aufgerufen werden, bevor Fork verwendet werden, da nach Forking Eltern und Kind völlig separate Prozesse sind, auch wenn sie einige Ressourcen wie Dateideskriptoren teilen, die vor der Gabelung vorhanden waren. Wenn Sie ein Modell verwenden, in dem ein Elternteil Arbeit an Worker-Prozessen verteilen soll, ist es möglicherweise besser, Threads und a thread pool zu verwenden.

Übrigens können Sie eine ganze Reihe von netten Beispielen von Servern und Clients von der Website herunterladen.

11

Sie können keinen Socket (oder einen anderen Dateideskriptor) über den gemeinsamen Speicher von einem Prozess zum anderen übertragen. Ein Dateideskriptor ist nur eine kleine Ganzzahl. Wenn Sie diese Ganzzahl im gemeinsam genutzten Speicher ablegen und von einem anderen Prozess darauf zugreifen, wird aus der Sicht des anderen Prozesses nicht automatisch dieselbe Ganzzahl zu einem gültigen Dateideskriptor.

Die korrekte Methode zum Senden eines Dateideskriptors von einem Prozess zu einem anderen besteht darin, ihn als SCM_RIGHTS Zusatzdaten mit sendmsg() über einen vorhandenen Socket-Kommunikationskanal zwischen den beiden Prozessen zu senden.

Zuerst erstellen Sie Ihren Kommunikationskanal mit socketpair() vor Ihnen fork(). Schließen Sie jetzt im übergeordneten Element ein Ende des Sockelpaars, und schließen Sie das andere Ende im untergeordneten Element. Sie können jetzt sendmsg() von dem Elternteil an einem Ende dieses Sockets und mit recvmsg() im Kind mit dem anderen Ende empfangen.

eine Nachricht mit SCM_RIGHTS Senden sieht wie folgt aus etwas:

struct msghdr m; 
struct cmsghdr *cm; 
struct iovec iov; 
char buf[CMSG_SPACE(sizeof(int))]; 
char dummy[2];  

memset(&m, 0, sizeof(m)); 
m.msg_controllen = CMSG_SPACE(sizeof(int)); 
m.msg_control = &buf; 
memset(m.msg_control, 0, m.msg_controllen); 
cm = CMSG_FIRSTHDR(&m); 
cm->cmsg_level = SOL_SOCKET; 
cm->cmsg_type = SCM_RIGHTS; 
cm->cmsg_len = CMSG_LEN(sizeof(int)); 
*((int *)CMSG_DATA(cm)) = your_file_descriptor_to_send; 
m.msg_iov = &iov; 
m.msg_iovlen = 1; 
iov.iov_base = dummy; 
iov.iov_len = 1; 
dummy[0] = 0; /* doesn't matter what data we send */ 
sendmsg(fd, &m, 0); 

eine Nachricht mit SCM_RIGHTS Empfangen darin etwas geht:

struct msghdr m; 
struct cmsghdr *cm; 
struct iovec iov; 
struct dummy[100]; 
char buf[CMSG_SPACE(sizeof(int))]; 
ssize_t readlen; 
int *fdlist; 

iov.iov_base = dummy; 
iov.iov_len = sizeof(dummy); 
memset(&m, 0, sizeof(m)); 
m.msg_iov = &iov; 
m.msg_iovlen = 1; 
m.msg_controllen = CMSG_SPACE(sizeof(int)); 
m.msg_control = buf; 
readlen = recvmsg(fd, &m, 0); 
/* Do your error handling here in case recvmsg fails */ 
received_file_descriptor = -1; /* Default: none was received */ 
for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm)) { 
    if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_RIGHTS) { 
     nfds = (cm->cmsg_len - CMSG_LEN(0))/sizeof(int); 
     fdlist = (int *)CMSG_DATA(cm); 
     received_file_descriptor = *fdlist; 
     break; 
    } 
} 
+0

Ich habe über Eltern/Kind-Prozess gesprochen, so ist die Frage: Wie Kind Zugriff auf Datei-Deskriptor von dem Elternteil nach einer Gabel gemacht? – user1995143

+0

+1 Für eine nette Antwort, aber es klingt für mich, als wäre er besser dran mit Threads. –

+0

@ user1995143 Das ist genau die Frage, die ich beantwortet habe. Es spielt keine Rolle, was die Eltern-Kind-Beziehung der beiden Prozesse ist. Solange Sie einen UNIX-Domain-Socket als Kommunikationskanal haben, können Sie mit dieser Technik Dateideskriptoren übertragen. Oder Sie können dem Vorschlag von [Robert S. Barnes] (http://stackoverflow.com/users/71074/robert-s-barnes) folgen und mehrere Threads anstelle von mehreren Prozessen verwenden. Dann müssen Sie sich keine Gedanken darüber machen, da Dateideskriptoren von allen Threads eines einzelnen Prozesses gemeinsam genutzt werden. – Celada