Linux网络编程——进程间传递文件描述符

先引入一个例子,该程序的目的是子进程向父进程传递文件描述符,并通过该文件描述符读取buf。

#include <func.h>

int main(){
	int fds[2];
	pipe(fds);
	if(!fork()){
		close(fds[1]);
		int fd;
		read(fds[0], &fd, sizeof(fd));
		printf("child fd = %d
", fd);
		char buf[128] = {0};
		read(fd, buf, sizeof(buf));
		printf("buf = %s
", buf);
		return 0;
	}
	else{
		close(fds[0]);
		int fd;
		fd = open("file", O_RDWR);
		printf("parent fd = %d
", fd);
		write(fds[1], &fd, sizeof(fd));
		wait(NULL);
		return 0;
	}
}

编译测试,发现结果不正确,通过ps aux查看到程序卡在了等待管道写数据,原因是卡在了第二个read读取buf处。我们再来看一下程序(见注释):

#include <func.h>

int main(){
	int fds[2];
	pipe(fds);
	if(!fork()){
		close(fds[1]); //子进程关闭文件描述符4,但fds[0]为3
		int fd;
		read(fds[0], &fd, sizeof(fd)); //通过fds[0]读出管道内容,写入fd中
		printf("child fd = %d
", fd); //输出为3
		char buf[128] = {0};
		read(fd, buf, sizeof(buf)); //fds[0]与fd同时为3,读阻塞
		printf("buf = %s
", buf);
		return 0;
	}
	else{
		close(fds[0]); //父进程关闭文件描述符3
		int fd;
		fd = open("file", O_RDWR); //打开文件的描述符为fd = 3
		printf("parent fd = %d
", fd);
		write(fds[1], &fd, sizeof(fd));  //通过fds[1]写入管道内容
		wait(NULL); //回收子进程
		return 0;
	}
}

所以我们必须借助内核传递文件描述符,sendmsg和recvmsg函数登场。

进程间传递文件描述符

步骤如下:

  1. 初始化socketpair类型描述符

  2. sendmsg发送描述符

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

    参1:sockfd指socket创建的FILENO

    参2:结构体(见下)

    参3:The flags argument is the bitwise OR of zero or more of the following flags.这里暂时不需要参数,先填0

    使用的sockfd即sockpair初始化的描述符fds[1]

    • 结构体 struct msghdr msg;
    struct msghdr {
    		void         *msg_name;       /* optional address */
    		socklen_t     msg_namelen;    /* size of address */
    		struct iovec *msg_iov;        /* scatter/gather array */
    		size_t        msg_iovlen;     /* # elements in msg_iov */
    		void         *msg_control;    /* ancillary data, see below 关键,即下面cmsghdr结构体地址 */
    		size_t        msg_controllen; /* ancillary data buffer len cmsghdr结构体的长度*/
    		int           msg_flags;      /* flags (unused) */
    };
    
    • 结构体 struct cmsghdr
    struct cmsghdr{
        socklen_t cmsg_len; /* data byte count, including header */
    	int cmsg_level; /* originating protocol */
    	int cmsg_type; /* protocol-specific type */
    	/* followed by unsigned char cmsg_data[]; */
    }
    
    • 结构体msg_iov
     struct iovec {
         void  *iov_base;    /* Starting address */
         size_t iov_len;     /* Number of bytes to transfer */
     };
    

cmsghdr结构体的初始化

	int len = CMSG_LEN(sizeof(int));//通过CMSG_LEN计算cmsg_len,传递的fd的大小为整型四个字节
	cmsg = (struct cmsghdr *)calloc(1, len);
	cmsg->cmsg_len = len;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	int *fdptr;
	fdptr = (int*)CMSG_DATA(cmsg);
	*fdptr = fd;

msg_iov结构体初始化

	struct iovec iov[2];
	char buf1[10]="hello";
	char buf2[10]="world";
	iov[0].iov_base=buf1;
	iov[0].iov_len=5;
	iov[1].iov_base=buf2;
	iov[1].iov_len=5;

msghdr结构体初始化

	/* iovec必须赋值 */
	struct msghdr msg;
	memset(&msg,0,sizeof(msg));
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	msg.msg_control = cmsg;
	msg.msg_controllen = len;

最后就可以通过sendmsg来发送文件描述符,完整代码如下:

int sendFd(int sfd,int fd)
{
	struct msghdr msg;
	memset(&msg,0,sizeof(msg));
	struct iovec iov[2];
	char buf1[10]="hello";
	char buf2[10]="world";
	iov[0].iov_base=buf1;
	iov[0].iov_len=5;
	iov[1].iov_base=buf2;
	iov[1].iov_len=5;
	msg.msg_iov=iov;
	msg.msg_iovlen=2;
	struct cmsghdr *cmsg;
	int len=CMSG_LEN(sizeof(int));//只传递一个文件描述符
	cmsg=(struct cmsghdr *)calloc(1,len);
	cmsg->cmsg_len=len;
	cmsg->cmsg_level=SOL_SOCKET;
	cmsg->cmsg_type=SCM_RIGHTS;
	*(int*)CMSG_DATA(cmsg)=fd;
	msg.msg_control=cmsg;
	msg.msg_controllen=len;
	int ret;
	ret=sendmsg(sfd,&msg,0);
	ERROR_CHECK(ret,-1,"sendmsg");
	return 0;
}

3.recvmsg接受文件描述符,接收的msghdr结构体初始化和sendmsg类似。

int recvFd(int sfd,int *fd)
{
	struct msghdr msg;
	memset(&msg,0,sizeof(msg));
	struct iovec iov[2];
	char buf1[10];
	char buf2[10];
	iov[0].iov_base=buf1;
	iov[0].iov_len=5;
	iov[1].iov_base=buf2;
	iov[1].iov_len=5;
	msg.msg_iov=iov;
	msg.msg_iovlen=2;
	struct cmsghdr *cmsg;
	int len=CMSG_LEN(sizeof(int));
	cmsg=(struct cmsghdr *)calloc(1,len);
	cmsg->cmsg_len=len;
	cmsg->cmsg_level=SOL_SOCKET;
	cmsg->cmsg_type=SCM_RIGHTS;
	msg.msg_control=cmsg;
	msg.msg_controllen=len;
	int ret;
	ret=recvmsg(sfd,&msg,0);
	ERROR_CHECK(ret,-1,"sendmsg");
	*fd=*(int*)CMSG_DATA(cmsg);
	return 0;
}

完整代码:

#include <func.h>
int sendFd(int sfd,int fd)
{
	struct msghdr msg;
	memset(&msg,0,sizeof(msg));
	struct iovec iov[2];
	char buf1[10]="hello";
	char buf2[10]="world";
	iov[0].iov_base=buf1;
	iov[0].iov_len=5;
	iov[1].iov_base=buf2;
	iov[1].iov_len=5;
	msg.msg_iov=iov;
	msg.msg_iovlen=2;
	struct cmsghdr *cmsg;
	int len=CMSG_LEN(sizeof(int));
	cmsg=(struct cmsghdr *)calloc(1,len);
	cmsg->cmsg_len=len;
	cmsg->cmsg_level=SOL_SOCKET;
	cmsg->cmsg_type=SCM_RIGHTS;
	*(int*)CMSG_DATA(cmsg)=fd;
	msg.msg_control=cmsg;
	msg.msg_controllen=len;
	int ret;
	ret=sendmsg(sfd,&msg,0);
	ERROR_CHECK(ret,-1,"sendmsg");
	return 0;
}
int recvFd(int sfd,int *fd)
{
	struct msghdr msg;
	memset(&msg,0,sizeof(msg));
	struct iovec iov[2];
	char buf1[10];
	char buf2[10];
	iov[0].iov_base=buf1;
	iov[0].iov_len=5;
	iov[1].iov_base=buf2;
	iov[1].iov_len=5;
	msg.msg_iov=iov;
	msg.msg_iovlen=2;
	struct cmsghdr *cmsg;
	int len=CMSG_LEN(sizeof(int));
	cmsg=(struct cmsghdr *)calloc(1,len);
	cmsg->cmsg_len=len;
	cmsg->cmsg_level=SOL_SOCKET;
	cmsg->cmsg_type=SCM_RIGHTS;
	msg.msg_control=cmsg;
	msg.msg_controllen=len;
	int ret;
	ret=recvmsg(sfd,&msg,0);
	ERROR_CHECK(ret,-1,"sendmsg");
	*fd=*(int*)CMSG_DATA(cmsg);
	return 0;
}
int main()
{
	int fds[2];
	socketpair(AF_LOCAL,SOCK_STREAM,0,fds);
	if(!fork())
	{
		close(fds[1]);
		int fd;
		recvFd(fds[0],&fd);
		printf("child fd=%d,fds[0]=%d
",fd,fds[0]);
		char buf[128]={0};
		read(fd,buf,sizeof(buf));
		printf("buf=%s
",buf);
		close(fds[0]);
		return 0;
	}else{
		close(fds[0]);
		int fd;
		fd=open("file",O_RDWR);
		printf("parent fd=%d
",fd);
		sendFd(fds[1],fd);
		close(fds[1]);
		wait(NULL);
		return 0;
	}
}

Linux网络编程——进程间传递文件描述符

  • writev和readv
#include <func.h>

int main(){
	int fd = open("file", O_RDWR);
	struct iovec iov[2];
	char buf1[10] = "hello";
	char buf2[10] = "world";
	iov[0].iov_base = buf1;
	iov[0].iov_len = 5;
	iov[1].iov_base = buf2;
	iov[1].iov_len = 5;
	writev(fd, iov, 2);//注意这里是2
	close(fd);
}