Ubuntu Linux使用Unix Domain Socket发送文件描述符

时间:2016-06-17 15:59:43

标签: c linux sockets file-descriptor

我试图在socketpair和下面粘贴的代码之间发送文件描述符。此代码来自:http://www.thomasstover.com/uds.html。我在64位的Ubuntu 16.04上运行。

问题是我运行的程序收到的文件描述符是" 3"而不是" 4"。我也无法在接收过程中从中读取任何数据。为什么不起作用?

控制台输出如下所示:

Parent at work
FILE TO SEND HAS DESCRIPTOR: 4
Parent read: [[hello phil
]]
Child at play
Read 3!
Done: 0 Success!
Parent exits

代码:

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>

int send_fd(int socket, int fd_to_send)
 {
  struct msghdr socket_message;
  struct iovec io_vector[1];
  struct cmsghdr *control_message = NULL;
  char message_buffer[1];
  /* storage space needed for an ancillary element with a paylod of length is CMSG_SPACE(sizeof(length)) */
  char ancillary_element_buffer[CMSG_SPACE(sizeof(int))];
  int available_ancillary_element_buffer_space;

  /* at least one vector of one byte must be sent */
  message_buffer[0] = 'F';
  io_vector[0].iov_base = message_buffer;
  io_vector[0].iov_len = 1;

  /* initialize socket message */
  memset(&socket_message, 0, sizeof(struct msghdr));
  socket_message.msg_iov = io_vector;
  socket_message.msg_iovlen = 1;

  /* provide space for the ancillary data */
  available_ancillary_element_buffer_space = CMSG_SPACE(sizeof(int));
  memset(ancillary_element_buffer, 0, available_ancillary_element_buffer_space);
  socket_message.msg_control = ancillary_element_buffer;
  socket_message.msg_controllen = available_ancillary_element_buffer_space;

  /* initialize a single ancillary data element for fd passing */
  control_message = CMSG_FIRSTHDR(&socket_message);
  control_message->cmsg_level = SOL_SOCKET;
  control_message->cmsg_type = SCM_RIGHTS;
  control_message->cmsg_len = CMSG_LEN(sizeof(int));
  *((int *) CMSG_DATA(control_message)) = fd_to_send;

  return sendmsg(socket, &socket_message, 0);
 }

int recv_fd(int socket)
 {
  int sent_fd, available_ancillary_element_buffer_space;
  struct msghdr socket_message;
  struct iovec io_vector[1];
  struct cmsghdr *control_message = NULL;
  char message_buffer[1];
  char ancillary_element_buffer[CMSG_SPACE(sizeof(int))];

  /* start clean */
  memset(&socket_message, 0, sizeof(struct msghdr));
  memset(ancillary_element_buffer, 0, CMSG_SPACE(sizeof(int)));

  /* setup a place to fill in message contents */
  io_vector[0].iov_base = message_buffer;
  io_vector[0].iov_len = 1;
  socket_message.msg_iov = io_vector;
  socket_message.msg_iovlen = 1;

  /* provide space for the ancillary data */
  socket_message.msg_control = ancillary_element_buffer;
  socket_message.msg_controllen = CMSG_SPACE(sizeof(int));

  if(recvmsg(socket, &socket_message, MSG_CMSG_CLOEXEC) < 0)
   return -1;

  if(message_buffer[0] != 'F')
  {
   /* this did not originate from the above function */
   return -1;
  }

  if((socket_message.msg_flags & MSG_CTRUNC) == MSG_CTRUNC)
  {
   /* we did not provide enough space for the ancillary element array */
   return -1;
  }

  /* iterate ancillary elements */
   for(control_message = CMSG_FIRSTHDR(&socket_message);
       control_message != NULL;
       control_message = CMSG_NXTHDR(&socket_message, control_message))
  {
   if( (control_message->cmsg_level == SOL_SOCKET) &&
       (control_message->cmsg_type == SCM_RIGHTS) )
   {
    sent_fd = *((int *) CMSG_DATA(control_message));
    return sent_fd;
   }
  }

  return -1;
 }

int main(int argc, char **argv)
{
    const char *filename = "/tmp/z7.c";

    if (argc > 1)
        filename = argv[1];
    int sv[2];
    if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) != 0)
        fprintf(stderr,"Failed to create Unix-domain socket pair\n");

    int pid = fork();
    if (pid > 0)  // in parent
    {
        fprintf(stderr,"Parent at work\n");
        close(sv[1]);
        int sock = sv[0];

        int fd = open(filename, O_RDONLY);
        if (fd < 0)
            fprintf(stderr,"Failed to open file %s for reading %s\n", filename, strerror(errno));

        fprintf(stderr,"FILE TO SEND HAS DESCRIPTOR: %d\n",fd);

        /* Read some data to demonstrate that file offset is passed */
        char buffer[32];
        int nbytes = read(fd, buffer, sizeof(buffer));
        if (nbytes > 0)
            fprintf(stderr,"Parent read: [[%.*s]]\n", nbytes, buffer);

        send_fd(sock, fd);

        close(fd);

        sleep(4);

        fprintf(stderr,"Parent exits\n");
    }
    else  // in child
    {
        fprintf(stderr,"Child at play\n");
        close(sv[0]);
        int sock = sv[1];

        sleep(2);

        int fd = recv_fd(sock);
        printf("Read %d!\n", fd);
        char buffer[256];
        ssize_t nbytes;
        while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0) {
            fprintf(stderr,"WRITING: %d\n",nbytes);
            write(1, buffer, nbytes);
        }
        printf("Done: %d %s!\n",nbytes,strerror(errno));
        close(fd);
    }
    return 0;
}

3 个答案:

答案 0 :(得分:1)

两个进程共享文件偏移量。因此,当父进程读取到EOF时,子进程无需读取任何内容。

这与两个进程从父级继承文件描述符时相同,例如shell命令:

{ echo first cat; cat ; echo second cat ; cat ; } < filename

第一个cat命令将读取所有文件,第二个cat将无法读取。

答案 1 :(得分:0)

引用Richard Stevens(编程UNIX网络):

“接收过程中的描述符编号与发送过程中的描述符编号不同是正常的。传递描述符不会传递描述符编号,而是在接收过程中创建指向内核中的文件条目与传输过程发送的描述符相同。“

答案 2 :(得分:0)

巴尔玛说的没错。

然后我完成了一些代码以使事情变得正确。 那就是寻找文件的开头:

lseek(fd, 0,SEEK_SET);

代码段

    int fd = recv_fd(sock);
    printf("Read %d!\n", fd);
    lseek(fd, 0,SEEK_SET);

    char buffer[256];
    ssize_t nbytes;