2011-12-12 24 views
5

Después de que @cnicutar me responda en este question, intenté enviar un descriptor de archivo desde el proceso principal a su hijo. En base a esta example, escribí este código:¿Cómo usar sendmsg() para enviar un descriptor de archivo a través de sockets entre 2 procesos?

int socket_fd ,accepted_socket_fd, on = 1; 
int server_sd, worker_sd, pair_sd[2]; 
struct sockaddr_in client_address; 
struct sockaddr_in server_address; 

/* ======================================================================= 
* Setup the network socket. 
* ======================================================================= 
*/ 

if((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
{ 
    perror("socket()"); 
    exit(EXIT_FAILURE); 
} 

if((setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on))) < 0) 
{ 
    perror("setsockopt()"); 
    exit(EXIT_FAILURE); 
} 

server_address.sin_family = AF_INET;     /* Internet address type */ 
server_address.sin_addr.s_addr = htonl(INADDR_ANY); /* Set for any local IP */ 
server_address.sin_port = htons(port);    /* Set to the specified port */ 

if(bind(socket_fd, (struct sockaddr *) &server_address, sizeof(server_address)) < 0) 
{ 
    perror("bind()"); 
    exit(EXIT_FAILURE); 
} 

if(listen(socket_fd, buffers) < 0) 
{ 
    perror("listen()"); 
    exit(EXIT_FAILURE); 
} 

if(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair_sd) < 0) 
{ 
    socketpair("bind()"); 
    exit(EXIT_FAILURE); 
} 

server_sd = pair_sd[0]; 
worker_sd = pair_sd[1]; 



/* ======================================================================= 
* Worker processes 
* ======================================================================= 
*/  

struct iovec iov[1]; 
struct msghdr child_msg; 
char msg_buffer[80]; 
int pass_sd, rc; 


/* Here the parent process create a pool of worker processes (its children) */ 
for(i = 0; i < processes; i++) 
{ 
    if(fork() == 0) 
    { 
     // ... 

     /* Loop forever, serving the incoming request */ 
     for(;;) 
     { 

      memset(&child_msg, 0, sizeof(child_msg)); 
      memset(iov, 0, sizeof(iov)); 

      iov[0].iov_base = msg_buffer; 
      iov[0].iov_len = sizeof(msg_buffer); 
      child_msg.msg_iov  = iov; 
      child_msg.msg_iovlen = 1; 
      child_msg.msg_name = (char *) &pass_sd; 
      child_msg.msg_namelen = sizeof(pass_sd); 

      printf("Waiting on recvmsg\n"); 
      rc = recvmsg(worker_sd, &child_msg, 0); 
      if (rc < 0) 
      { 
       perror("recvmsg() failed"); 
       close(worker_sd); 
       exit(-1); 
      } 
      else if (child_msg.msg_namelen <= 0) 
      { 
       printf("Descriptor was not received\n"); 
       close(worker_sd); 
       exit(-1); 
      } 
      else 
      { 
       printf("Received descriptor = %d\n", pass_sd); 
      } 

      //.. Here the child process can handle the passed file descriptor 
     } 
    } 

} 



/* ======================================================================= 
* The parent process 
* ======================================================================= 
*/ 

struct msghdr parent_msg; 
size_t length; 

/* Here the parent will accept the incoming requests and passed it to its children*/ 
for(;;) 
{ 
    length = sizeof(client_address); 
    if((accepted_socket_fd = accept(socket_fd, NULL, NULL)) < 0) 
    { 
     perror("accept()"); 
     exit(EXIT_FAILURE); 
    } 

    memset(&parent_msg, 0, sizeof(parent_msg)); 
    parent_msg.msg_name = (char *) &accepted_socket_fd; 
    parent_msg.msg_namelen = sizeof(accepted_socket_fd); 

    if((sendmsg(server_sd, &parent_msg, 0)) < 0) 
    { 
     perror("sendmsg()"); 
     exit(EXIT_FAILURE); 
    } 

} 

Pero, por desgracia, tengo este error:

sendmsg(): Invalid argument 

¿Qué debo hacer para solucionar este problema? y estoy usando la estructura msghdr correctamente? porque en el ejemplo que mencioné anteriormente, usan msg_accrights y msg_accrightslen y obtuve algún error cuando los uso, así que tuve que usar msg_name y msg_namelen en su lugar.

Respuesta

8

Esto es extremadamente difícil de hacer bien. Yo recomendaría simplemente usar una biblioteca que lo haga por usted. Uno de los más simples es libancillary. Le da dos funciones, una para enviar un descriptor de archivo sobre un socket de dominio UNIX y otro para recibir uno. Son absurdamente simples de usar.

0

No puede enviar descriptores de archivo a través de AF_INET. Use un socket de dominio UNIX.

+0

El usuario no está utilizando AF_INET para el paso del socket, sino un socket AF_UNIX creado por socketpair. – Nakedible

6

El problema es que está pasando el descriptor de archivo en un campo msg_name. Este es un campo de dirección, y no está destinado a pasar datos arbitrarios.

De hecho, los descriptores de archivos deben pasarse de una manera especial para que el kernel pueda duplicar el descriptor de archivo para el proceso de recepción (y tal vez el descriptor tendrá otro valor después de la duplicación). Es por eso que hay un tipo de mensaje auxiliar especial (SCM_RIGHTS) para pasar los descriptores de archivos.

Lo siguiente funcionaría (omití parte del manejo de errores). En cliente:

memset(&child_msg, 0, sizeof(child_msg)); 
char cmsgbuf[CMSG_SPACE(sizeof(int))]; 
child_msg.msg_control = cmsgbuf; // make place for the ancillary message to be received 
child_msg.msg_controllen = sizeof(cmsgbuf); 

printf("Waiting on recvmsg\n"); 
rc = recvmsg(worker_sd, &child_msg, 0); 
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&child_msg); 
if (cmsg == NULL || cmsg -> cmsg_type != SCM_RIGHTS) { 
    printf("The first control structure contains no file descriptor.\n"); 
    exit(0); 
} 
memcpy(&pass_sd, CMSG_DATA(cmsg), sizeof(pass_sd)); 
printf("Received descriptor = %d\n", pass_sd); 

En servidor:

memset(&parent_msg, 0, sizeof(parent_msg)); 
struct cmsghdr *cmsg; 
char cmsgbuf[CMSG_SPACE(sizeof(accepted_socket_fd))]; 
parent_msg.msg_control = cmsgbuf; 
parent_msg.msg_controllen = sizeof(cmsgbuf); // necessary for CMSG_FIRSTHDR to return the correct value 
cmsg = CMSG_FIRSTHDR(&parent_msg); 
cmsg->cmsg_level = SOL_SOCKET; 
cmsg->cmsg_type = SCM_RIGHTS; 
cmsg->cmsg_len = CMSG_LEN(sizeof(accepted_socket_fd)); 
memcpy(CMSG_DATA(cmsg), &accepted_socket_fd, sizeof(accepted_socket_fd)); 
parent_msg.msg_controllen = cmsg->cmsg_len; // total size of all control blocks 

if((sendmsg(server_sd, &parent_msg, 0)) < 0) 
{ 
    perror("sendmsg()"); 
    exit(EXIT_FAILURE); 
} 

Véase también man 3 cmsg, hay algunos ejemplos.

+1

Después de sendmsg, ¿cuál es el estado de la fd en el servidor? ¿Está abierto y debe cerrarse o lo cierra sendmsg/cmsg? En el código de ejemplo, nunca veo el servidor cerrando el fd, que sería necesario si sendmsg no lo cierra. –

Cuestiones relacionadas