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\n", fd);
char buf[128] = {0};
read(fd, buf, sizeof(buf));
printf("buf = %s\n", buf);
return 0;
}
else{
close(fds[0]);
int fd;
fd = open("file", O_RDWR);
printf("parent fd = %d\n", 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\n", fd); //输出为3
char buf[128] = {0};
read(fd, buf, sizeof(buf)); //fds[0]与fd同时为3,读阻塞
printf("buf = %s\n", buf);
return 0;
}
else{
close(fds[0]); //父进程关闭文件描述符3
int fd;
fd = open("file", O_RDWR); //打开文件的描述符为fd = 3
printf("parent fd = %d\n", fd);
write(fds[1], &fd, sizeof(fd)); //通过fds[1]写入管道内容
wait(NULL); //回收子进程
return 0;
}
}
所以我们必须借助内核传递文件描述符,sendmsg和recvmsg函数登场。
进程间传递文件描述符
步骤如下:
-
初始化socketpair类型描述符
-
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\n",fd,fds[0]);
char buf[128]={0};
read(fd,buf,sizeof(buf));
printf("buf=%s\n",buf);
close(fds[0]);
return 0;
}else{
close(fds[0]);
int fd;
fd=open("file",O_RDWR);
printf("parent fd=%d\n",fd);
sendFd(fds[1],fd);
close(fds[1]);
wait(NULL);
return 0;
}
}
- 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);
}