【问题标题】:How to use sendmsg() to send a file-descriptor via sockets between 2 processes?如何使用 sendmsg() 在 2 个进程之间通过套接字发送文件描述符?
【发布时间】:2012-01-18 20:23:32
【问题描述】:

在@cnicutar 就这个question 回答我之后,我尝试将文件描述符从父进程发送给它的子进程。基于这个example,我写了这段代码:

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

【问题讨论】:

    标签: c linux sockets network-programming file-descriptor


    【解决方案1】:

    这很难做到。我建议只使用一个为你做这件事的库。最简单的一种是libancillary。它为您提供了两个功能,一个是通过 UNIX 域套接字发送文件描述符,另一个是接收一个。它们使用起来非常简单。

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 用户不是使用AF_INET进行socket传递,而是使用socketpair创建的AF_UNIX socket。
      【解决方案3】:

      问题是您在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,有一些例子。

      【讨论】:

      • sendmsg后,服务器中fd的状态如何?它是打开并需要关闭,还是 sendmsg/cmsg 关闭它?在示例代码中,我从来没有看到服务器关闭 fd,如果 sendmsg 没有关闭它,这将是必要的。
      • 我相信它会保持打开状态,如果您不先终止进程,操作系统会关闭它。不过还没有完全检查。
      猜你喜欢
      • 2015-03-16
      • 2012-07-13
      • 2011-05-28
      • 1970-01-01
      • 1970-01-01
      • 2019-03-29
      • 2020-05-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多