2011-12-12 43 views
5

@cnicutar在这个question上回答我后,我试图从父进程发送一个文件描述符给它的子进程。在此基础上example,我写了这个代码:如何使用sendmsg()通过2个进程间的套接字发送文件描述符?

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

} 

但不幸的是,我得到这个错误:

sendmsg(): Invalid argument 

我应该怎么做才能解决这个问题?我是否正确使用msghdr结构?因为在上面我提到的例子中,他们使用msg_accrightsmsg_accrightslen,当我使用它们时出现了一些错误,所以我不得不使用msg_namemsg_namelen代替。

回答

8

这是非常难以得到正确的。我建议只使用一个库来为你做。其中最简单的是libancillary。它提供了两个函数,一个通过UNIX域套接字发送文件描述符,另一个接收文件描述符。它们非常简单易用。

0

您无法通过AF_INET发送文件描述符。使用UNIX域套接字。

+0

用户未使用AF_INET进行套接字传递,而是使用socketpair创建的AF_UNIX套接字。 – Nakedible

6

问题是您正在传递msg_name字段中的文件描述符。这是一个地址字段,它不打算传递任意数据。

事实上,文件描述符应该以特殊方式传递,以便内核可以复制接收过程的文件描述符(也许描述符在复制之后会有另一个值)。这就是为什么有一个特殊的辅助消息类型(SCM_RIGHTS)来传递文件描述符。

以下将工作(我省略了一些错误处理)。 在客户端:

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

在服务器:

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

又见man 3 cmsg,也有一些例子。

+1

sendmsg之后,服务器中fd的状态是什么?它是开放的,需要关闭,还是sendmsg/cmsg关闭它?在示例代码中,我从来没有看到服务器关闭fd,如果sendmsg没有关闭它,这将是必要的。 –