进程间共享文件描述符主要由三个函数实现:
- socketpair():创建一对匿名域套接字;
- sendmsg():从套接字一端发送数据;
- recvmsg():从套接字另一端接受数据。
1. socketpair()
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(
int domain,
int type,
int protocol,
int sv[2]
);
参数说明:
- domain:表示要建立的套接字的协议族,只能为AF_LOCAL 或AF_UNIX;
- type:表示协议,可以是SOCK_STREAM或SOCK_DGRAM。用SOCK_STREAM建立的是管道流,与一般管道的区别是,套接字对建立的通道是双向的,每一端都可以进行读写;
- protocol:表示类型,只能为0;
- sv[2]:存储建立的套接字对的整数数组。
返回值:
函数调用成功返回0,否则返回-1,并且设置errno。
2. sendmsg()
#include <sys/socket.h>
ssize_t sendmsg(
int sockfd,
const struct msghdr *msg,
int flags
);
参数说明:
- sockfd:文件描述符(sockpair创建的sv[]);
- *msg:需要发送的消息的头部结构体,详见下文;
- flags:一般置0,其他值与send()相同。
参数*msg涉及到3个结构体:msghdr、iovec和cmasghdr,如下图:
msghdr
struct msghdr
{
void *msg_name;
socklen_t msg_namelen;
struct iovec *msg_iov;
size_t msg_iovlen;
void *msg_control;
size_t *msg_controllen;
int msg_flags;
};
成员说明:
-
*msg_name和msg_namelen表示要发送或接收数据的地址和地址长度,如果是面向连接的,这两个变量可以不用;
-
*msg_iov用于指定要发送或接收数据的缓冲区地址,可以是数组,如下:
struct iovec { void *iov_baes; //存放数据的缓冲区 size_t iov_len; //数据长度 };
-
msg_iovlen表示iovec类型元素的个数;
-
*msg_control表示控制信息,指向一个cmsghdr结构体, *msg_controllen表示控制信息的长度,详见下文;
-
msg_flages表示发送和接受消息的标识。
cmsghdr
struct cmsghdr
{
socklen_t cmsg_len;
int cmsg_level;
int cmsg_type;
/*unsigned char cmsg_data[]*/
};
成员说明:
- cmsg_len:控制信息的字节计数,包含结构头的数据。这个值是由CMSG_LEN()宏计算的;
- cmsg_level:原始的协议级别(例如,SOL_SOCKET);
- cmsg_type:控制信息类型(例如,SCM_RIGHTS);
- cmsg_data:控制信息的数据部分,实际上这个成员并不存在,数据是直接存储在cmsg_type之后的。
对于这些控制数据的访问,必须使用Linux提供的一些专用宏来完成。这些宏包括如下几个:
#include <sys/socket.h>
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);
struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg);
size_t CMSG_ALIGN(size_t length);
size_t CMSG_SPACE(size_t length);
size_t CMSG_LEN(size_t length);
unsigned char *CMSG_DATA(struct cmsghdr *cmsg);
其中:
- CMSG_FIRSTHDR()返回msg所指向的msghdr结构体中的第一个cmsghdr结构体指针;
- CMSG_NXTHDR()返回传入的cmsghdr类型的指针的下一个cmsghdr结构体的指针;
- CMSG_ALIGN()根据传入的length大小,返回一个包含了添加对齐作用的填充数据后的大小;
- CMSG_SPACE()中传入的参数length指的是一个控制信息元素(即一个cmsghdr结构体)后面数据部分的字节数,返回的是这个控制信息的总的字节数,即包含了头部(即cmsghdr各成员)、数据部分和填充数据的总和;
- CMSG_DATA根据传入的cmsghdr指针参数,返回其后面数据部分的指针;
- CMSG_LEN传入的参数是一个控制信息中的数据部分的大小,返回的是这个根据这个数据部分大小,需要配置的cmsghdr结构体中cmsg_len成员的值。这个大小将为对齐添加的填充数据也包含在内。
发送文件描述符的步骤:
- 创建一个msghdr结构体,并初始化;
- 创建一个cmsghdr结构体,将该结构体的cmsg_level设置为SOL_SOCKET,cmsg_type设置为SCM_RIGHTS,其中,SCM表示socket-level control message,SCM_RIGHTS表示我们要传递访问权限;
- 将要传递的文件描述符赋值给cmsghdr的数据部分;
- 将该cmsghdr结构体分配给msg_control;
- 调用sendmsg()。
具体实现:
static const int CONTROL_LEN = CMSG_LEN(sizeof(int));
//计算cmsg_len的值
/*发送文件描述符 sv参数是用来传递信息的UNIX域socket,
*fd参数是待发送的文件描述符 */
void send_fd(int sv, int fd)
{
struct msghdr msg;
struct iovec iov[1];
char buf[0];
iov[0].iov_base = buf;
iov[0].iov_len = 1;
//不需要传递其他数据,所以iov数据部分为空
msg.msg_name = NULL;
msg.msg_namelen = 0;
//面向连接,可以不用
msg.msg_iov = iov;
msg.msg_iovlen = 1;
cmsghdr cm;
cm.cmsg_len = CONTROL_LEN;
//用CMSG_LEN()宏获得的值
cm.cmsg_level = SOL_SOCKET;
cm.cmsg_type = SCM_RIGHTS;
//设置信息类型
*(int*)CMSG_DATA(&cm) = fd;
//将要传递的文件描述符赋值给cmsghdr的数据部分
msg.msg_control = &cm;
//给msg_control分配设置好的cmsghdr结构体
sendmsg(sv, &msg, 0);
//调用sendmsg()
};
3. recvmsg()
#include <sys/socket.h>
ssize_t recvmsg(
int sockfd,
const struct msghdr *msg,
int flags
);
接受函数与发送函数大致相同,最后一个参数flags在调用recvmsg之后,会被设置到到msg所指向的msghdr类型的msg_flags变量中。
接收文件描述符的步骤:
- 创建一个msghdr结构体,并初始化;
- 创建一个cmsghdr结构体,
将该结构体的cmsg_level设置为SOL_SOCKET,
cmsg_type设置为SCM_RIGHTS,
其中,SCM表示socket-level control message,SCM_RIGHTS表示我们要传递访问权限; - 将该cmsghdr结构体分配给msg_control;
- 调用recvmsg();
5.读取cmsghdr的数据部分获得文件描述符。
具体实现:
/*接收文件描述符
*sv参数是用来传递信息的UNIX域socket */
int recv_fd(int sv)
{
struct msghdr msg;
struct iovec iov[1];
char buf[0];
iov[0].iov_base = buf;
iov[0].iov_len = 1;
//没有其他要接受的数据,iovec数据部分设为空
msg.msg_name = NULL;
msg.msg_namelen = 0;
//面向连接,不需要
msg.msg_iov = iov;
msg.msg_iovlen = 1;
cmsghdr cm;
msg.msg_control = &cm;
msg.msg_controllen = CONTROL_LEN;
//创建一个cmsghdr结构体,并分配给msg_control
recvmsg(fd, &msg, 0);
//调用recvmsg()
int fd = *(int*)CMSG_DATA(&cm);
//从cmsghdr的数据部分获取文件描述符
return fd
}
4. 主函数
#include <sys/socket.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
int main(int argc, char* argv[])
{
int sv[2];
int fd= 0;
/*创建父子进程管道*/
int ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, sv);
assert(ret != -1);
//将创建的管道符存于sv[2]中
pid_t pid = fork();
if(pid == 0)//子进程
{
close(sv[0]);//关闭管道符一端
fd = open("test.txt", O_RDWR);
// 创建一个文件描述符
send_fd(sv[1], (fd > 0) ? fd: 0);
//在管道符另一端发送文件描述符
close(fd_to_pass);
//关闭文件
exit(0);
}
//父进程
close(sv[1]);//关闭管道符一端
fd= recv_fd(sv[0]);
//在管道符另一端发送文件描述符
char buf[1024];
memset(buf, '\0', 1024);
read(fd, buf, 1024);
printf("I got fd %d and data %s\n", fd, buf);
//从文件中读取数据
close(fd_to_pass);
return 0;
}
参考网页:
网友评论