2013-01-20 5 views
8

Sono bloccato su un problema in un programma C su Linux.Come posso passare un socket dai processi padre a figlio

So che quando un processo viene biforcato il processo figlio eredita alcune cose dal genitore, tra cui descrittori di file aperti.

Il problema è che sto scrivendo un'applicazione server multiprocesso con un processo master che accetta nuove connessioni e inserisce i descrittori nella memoria condivisa.

Quando il processo figlio tenta di leggere da uno di questi descrittori dalla memoria condivisa, su select() ho ricevuto un errore EBADF!

Come può il processo figlio leggere e utilizzare un socket (o qualsiasi descrittore di file in generale) creato da un processo principale dopo è stato biforcuto?

risposta

9

Quando si chiama la forcella, il processo figlio eredita copie di tutti i descrittori di file aperti. Il modo tipico per farlo è che un processo genitore apra un socket di ascolto, chiama i blocchi che accettano fino a quando arriva una connessione e poi chiama fork dopo aver ricevuto la connessione. Il genitore quindi chiude la sua copia del descrittore di file, mentre il nuovo processo figlio può continuare a usare il descrittore di file e fare qualsiasi elaborazione necessaria. Una volta che il bambino ha finito, lo chiude la presa. È importante ricordare due cose: 1. Il descrittore di file/socket è una risorsa nel sistema operativo e dopo il fork il padre e il figlio hanno ciascuno un handle per quella risorsa, che è un po 'come un puntatore intelligente conteggiato di riferimento. Spiego questo in more detail here. La seconda cosa è che solo i descrittori di file che sono aperti prima del fork di chiamate sono condivisi, perché dopo aver forato padre e figlio sono processi completamente separati, anche se possono condividere alcune risorse come i descrittori di file che esistevano prima del fork. Se stai utilizzando un modello in cui desideri che un genitore distribuisca il lavoro ai processi di lavoro, potrebbe essere meglio prendere in considerazione l'utilizzo dei thread e a thread pool.

A proposito, è possibile scaricare molti esempi di server e client dal sito Web Unix Network Programming.

11

Non è possibile trasmettere un socket (o qualsiasi altro descrittore di file) da un processo a un altro attraverso la memoria condivisa. Un descrittore di file è solo un piccolo numero intero. Inserendo questo numero intero nella memoria condivisa e accedendo da un altro processo, non automaticamente lo stesso numero intero in un descrittore di file valido dal punto di vista dell'altro processo.

Il modo corretto per inviare un descrittore di file da un processo all'altro è inviare come SCM_RIGHTS dati ausiliari con sendmsg() attraverso un canale di comunicazione zoccolo esistente tra i due processi.

Innanzitutto, creare il canale di comunicazione con socketpair() prima di fork(). Ora, nel genitore, chiudi un'estremità della coppia di socket e, nel bambino, chiudi l'altra estremità. È ora possibile sendmsg() dal padre su un'estremità di questo socket e ricevere con recvmsg() nel figlio utilizzando l'altra estremità.

L'invio di un messaggio con SCM_RIGHTS sembra qualcosa di simile:

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

Ricezione di un messaggio con SCM_RIGHTS in esso più o meno così:

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

stavo parlando del processo genitore/figlio, quindi la domanda è: come accedere al file del descrittore di file creato dal genitore dopo un fork? – user1995143

+0

+1 Per una bella risposta, ma mi sembra che starebbe meglio usando i thread. –

+0

@ user1995143 Questa è esattamente la domanda cui ho risposto. Non importa quale sia la relazione genitore/figlio dei 2 processi. Finché si dispone di un socket di dominio UNIX come canale di comunicazione, è possibile trasmettere i descrittori di file utilizzando questa tecnica. Oppure puoi seguire il suggerimento [Robert S. Barnes] (http://stackoverflow.com/users/71074/robert-s-barnes) e utilizzare più thread anziché più processi. Quindi non devi preoccuparti perché i descrittori di file sono condivisi tra tutti i thread di un singolo processo. – Celada