使用unix域套接字在进程之间传递文件描述符

在多进程的程序中经常需要在不同的进程之间传递文件描述符,但是不同的进程之间文件描述代表的是不同的对象。那么如何在不同的进程中使用相同的文件描述符,而且代表的是相同的对象呢?

在linux中可以使用unix的域套接字方法来实现在不同的进程之间传递文件描述符, 需要使用socketpair函数创建一个套接字管道,该管道是双向的,每一端都是可读可写的。

socketpair的 函数原型:

int socketpair(int domain, int type, int protocol, int sv[2]);

参数:

Domain: 通信类型比如AF_UNIX

type:套接字类型比如 SOCK_STREAM、 SOCK_DGRAM

protol:只能为0

sv: 包含两个元素的数组名

函数执行完成之后会得到sv[0]和sv[1]两个套接字描述符。在不同的进程之间进行通信时可以使用如下的方法:

每个进程关闭一个描述符,然后使用一个描述符通信。那么有了管道后,如何传递文件描述符呢?那就得需要使用sendmsg、recvmsg函数。

sendmsg函数用来给一个特性的套接字描述符发送消息。

recvmsg 函数用来从一个特定的套接字中读取消息。

函数原型如下:

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

这两个函数的使用关键是struct msghder和 struct cmsghdr?两个结构体的使用。

首先, stuct msghdr结构体是用来发送和接收消息的结构体,成员如下:struct msghdr {

void *msg_name; //套接字的地址

socklen_t msg_namelen;//套接字地址长度

struct iovec *msg_iov;//消息结构体的地址

size_t msg_iovlen;//msg_iov结构体的个数

void *msg_control;//消息控制缓冲区

size_t msg_controllen;//消息控制缓冲区的长度

int msg_flags;//接收消息时的标志位

};

stcut cmsghdr结构体成员如下:

struct cmsghdr

{

cmsg_len // 附属数据的字节计数,这包含结构头的尺寸。这个值是由CMSG_LEN()宏计算的。

cmsg_level // 这个值表明了原始的协议级别(例如,SOL_SOCKET)。

cmsg_type // 这个值表明了控制信息类型(例如,SCM_RIGHTS)。

}

示例代码如下:

1)接收描述符代码

int my_recv();

int main(int argc, const char *argv[])

{

int fd;

char buf[32] = {0};

if ((fd = my_recv()) < 0)

{

printf("fail to my_recv\n");

return -1;

}

read(fd, buf, sizeof(buf));

puts(buf);

close(fd);

return 0;

}

int my_recv()

{

int sockfd[2];

int status = -1;

pid_t pid;

char itoa_fd[10] = {0};

if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd) < 0)

{

perror("fail to socketpair");

return -1;

}

pid = fork();

if (pid < 0)

{

perror("Fail to fork");

return -1;

}

else if (pid == 0)

{

close(sockfd[0]);

sprintf(itoa_fd, "%d", sockfd[1]);

if (execl("./sendmsg", "sendmsg", itoa_fd, NULL) < 0)

{

perror("fail to execl");

exit(-1);

}

}

else

{

close(sockfd[1]);

waitpid(pid, &status, 0);

if (WEXITSTATUS(status) != 0)

{

close(sockfd[0]);

printf("sendmsg fail to exit\n");

return -1;

}

struct msghdr msg;

struct cmsghdr *cmsg;

struct iovec iv;

char buf[CMSG_SPACE(sizeof(int))] = {0};

char recv_buf[32] = {0};

msg.msg_control = buf;

msg.msg_controllen = sizeof(buf);

//用来接收sendmsg发送的消息

iv.iov_base = recv_buf;

iv.iov_len = sizeof(recv_buf);

msg.msg_iov = &iv;

msg.msg_iovlen = 1;

msg.msg_name = NULL;

msg.msg_namelen = 0;

if (recvmsg(sockfd[0], &msg, 0) < 0)

{

perror("fail to recvmsg");

return -1;

}

if ((cmsg = CMSG_FIRSTHDR(&msg)) != NULL &&cmsg->cmsg_len == CMSG_LEN(sizeof(int)))

{

close(sockfd[0]);

return *(int *)CMSG_DATA(cmsg);

}

close(sockfd[0]);

return -1;

}

}

2)发送描述符代码

int my_send(int sockfd, int file);

int main(int argc, const char *argv[])

{

int fd;

if ((fd = open("file", O_RDONLY)) < 0)

{

perror("fail to open the file");

return -1;

}

if (my_send(atoi(argv[1]), fd) < 0)

{

puts("fail to my_send");

close(fd);

return -1;

}

return 0;

}

int my_send(int sockfd, int file)

{

struct msghdr msg;

struct cmsghdr *cmsg;

struct iovec iv;

char buf[CMSG_SPACE(sizeof(int))] = {0};

char send_buf[32] = "helloworld";

bzero(&msg, sizeof(msg));

msg.msg_control = buf;

msg.msg_controllen = sizeof(buf);

//必须要添加消息这一部分,否则sendmsg无法发送

iv.iov_base = send_buf;

iv.iov_len = sizeof(send_buf);

msg.msg_iov = &iv;

msg.msg_iovlen = 1;

msg.msg_name = NULL;

msg.msg_namelen = 0;

cmsg = CMSG_FIRSTHDR(&msg);

cmsg->cmsg_len = CMSG_LEN(sizeof(int));

cmsg->cmsg_level = SOL_SOCKET;

cmsg->cmsg_type = SCM_RIGHTS;

*(int*)CMSG_DATA(cmsg) = file;

return sendmsg(sockfd, &msg, 0);

}