在之前学习进程间通信时,我们只接触了一些基础的进程间通信知识。例如管道通信、内存映射等,这些通信方式传值时没有任何问题,当时当传送一些特殊的内存指针或是变量就有可能出问题。由于进程之间彼此是内存隔离的,不能直接访问其他进程的内存空间,每个进程都有自己打开的文件对象或是网络套接字。当进程想把自己已经打开的文件对象分享给其他进程使用,仅通过管道传递一个文件描述符肯定是不行的,它只适用于自己。这个时候我们就可以使用一些特殊的进程通信系统调用,使其可以共享进程内的对象。
本地套接字
父进程和子进程的地址空间是隔离的,如果两个进程之间需要进行通信,那就要选择一种合适的进程间通信的手段,在本项目中,比较合适的方法是管道。除了之前所使用的 pipe 系统调用可以在父子进程间创建管道以外,还有一种方法是本地套接字。使用系统调用 socketpair 可以在父子进程间利用 socket 创建一个全双工的管道。
除此以外,本地套接字可以在同一个操作系统的两个进程之间传递文件描述符。一般 socketpair 之后会配合 fork 函数一起使用,从而实现父子进程之间的通信。从数据传递使用上面来看,本地套接字和网络套接字是完全一致的,但是本地套接字的效率更高,因为它在拷贝数据的时候不需要处理协议相关内容。
socketpair
在 Linux 中,socketpair()
函数用于创建一对相互连接的套接字。这对套接字可以用于进程间通信(IPC)。
cpp
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);
参数说明
-
domain
:- 指定套接字的通信域(协议族)。对于
socketpair()
,通常使用AF_UNIX
或AF_LOCAL
,表示本地通信。对于父子进程通信必须选 AF_LOCAL。
- 指定套接字的通信域(协议族)。对于
-
type
:-
指定套接字的类型。常见的类型包括:
-
SOCK_STREAM
:流式套接字,提供可靠的双向字节流。 -
SOCK_DGRAM
:数据报套接字,提供无连接的、不可靠的、固定大小的数据报。 -
SOCK_SEQPACKET
:有序的、可靠的、固定大小的数据报。 -
SOCK_RAW
:原始套接字,用于直接访问协议层。
-
-
-
protocol
:- 指定使用的协议。对于
AF_UNIX
,通常设置为0
,表示默认协议。
- 指定使用的协议。对于
-
sv[2]
:- 一个数组,用于存储创建的两个套接字的文件描述符。
sv[0]
和sv[1]
是一对相互连接的套接字。
- 一个数组,用于存储创建的两个套接字的文件描述符。
返回值
-
成功 :返回
0
,并通过sv
参数返回两个套接字的文件描述符。 -
失败 :返回
-1
,并通过errno
设置错误码。
示例:使用socketpair进行简单进程通信
cpp
int main() {
int sv[2];
pid_t pid;
// 创建一对套接字
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
perror("socketpair");
exit(EXIT_FAILURE);
}
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// 子进程
close(sv[0]); // 关闭父进程端的套接字
char buffer[100];
int n;
// 从父进程接收数据
if ((n = read(sv[1], buffer, sizeof(buffer))) == -1) {
perror("read");
exit(EXIT_FAILURE);
}
buffer[n] = '\0'; // 确保字符串以空字符结尾
printf("Child received: %s\n", buffer);
// 向父进程发送数据
const char *msg = "Hello from child\n";
if (write(sv[1], msg, strlen(msg)) == -1) {
perror("write");
exit(EXIT_FAILURE);
}
close(sv[1]); // 关闭子进程端的套接字
exit(EXIT_SUCCESS);
} else {
// 父进程
close(sv[1]); // 关闭子进程端的套接字
const char *msg = "Hello from parent\n";
// 向子进程发送数据
if (write(sv[0], msg, strlen(msg)) == -1) {
perror("write");
exit(EXIT_FAILURE);
}
char buffer[100];
int n;
// 从子进程接收数据
if ((n = read(sv[0], buffer, sizeof(buffer))) == -1) {
perror("read");
exit(EXIT_FAILURE);
}
buffer[n] = '\0'; // 确保字符串以空字符结尾
printf("Parent received: %s\n", buffer);
close(sv[0]); // 关闭父进程端的套接字
wait(NULL); // 等待子进程退出
}
return 0;
}
父子进程共享文件描述符
那么父进程向子进程到底需要传递哪些信息呢?除了传递一般的控制信息和文本信息(比如
上传)以外,需要特别注意的是需要传递已连接套接字的文件描述符。
父进程会监听特定某个 IP:PORT ,如果有某个客户端连接之后,子进程需要能够连上 accept 得到的已连接套接字的文件描述符,这样子进程才能和客户端进行通信。这种文件描述符的传递不是简单地传输一个整型数字就行了,而是需要让父子进程共享一个套接字文件对象。
但是这里会遇到麻烦,因为 accept 调用是在 fork 之后的,所以父子进程之间并不是天然地共享文件对象。倘若想要在父子进程之间共享 acccept 调用返回的已连接套接字,需要采用一些特别的手段:一方面,父子进程之间需要使用本地套接字来通信数据。另一方面需要使用 sendmsg 和 recvmsg 函数来传递数据。

相关数据类型
struct iovec
iovec
是一个结构体,用于描述分散(scatter)或聚集(gather)I/O 操作中的内存区域。它通常与 readv()
和 writev()
等系统调用一起使用,允许程序一次性从多个内存区域读取或写入数据,而无需将数据先拷贝到一个连续的缓冲区中。这种方式可以显著提高 I/O 操作的效率。
cpp
struct iovec {
void *iov_base; // 缓冲区的起始地址
size_t iov_len; // 缓冲区的长度
};
字段说明
-
iov_base
:-
类型为
void *
,指向缓冲区的起始地址。这个地址可以是任意类型的指针,指向存储数据的内存区域。 -
例如,可以是一个字符数组的地址,用于存储字符串数据。
-
-
iov_len
:-
类型为
size_t
,表示缓冲区的长度(以字节为单位)。 -
这个值必须与
iov_base
指向的内存区域的实际大小一致,以避免缓冲区溢出。
-
使用场景
iovec
结构体通常用于以下场景:
-
高效 I/O 操作:
-
使用
readv()
和writev()
等系统调用时,iovec
用于描述多个分散的内存区域。 -
这种方式可以减少系统调用的次数和内存拷贝的开销,从而提高 I/O 操作的效率。
-
-
网络编程:
-
在处理网络数据时,可以将数据直接写入或从多个缓冲区中读取,避免中间拷贝。
-
例如,将多个消息片段一次性发送到网络套接字中,或从套接字中一次性读取多个消息片段。
-
-
文件操作:
- 在处理文件时,可以将文件内容分散到多个缓冲区中,避免一次性读取或写入大块数据。
struct msghdr
msghdr
结构体是用于描述消息头的通用结构,通常与 sendmsg()
和 recvmsg()
系统调用一起使用。它封装了消息的各个组成部分,包括数据缓冲区、辅助数据(控制信息)以及目标地址等。通过 msghdr
,程序可以灵活地处理复杂的 I/O 操作,例如散列(scatter/gather)I/O 和辅助数据的传递。
cpp
#include <sys/socket.h>
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
:-
类型为
void *
,指向目标地址或源地址。对于sendmsg()
,它是目标地址;对于recvmsg()
,它是源地址。 -
如果套接字是已连接的(如 TCP 套接字),通常设置为
NULL
和0
。
-
-
msg_namelen
:-
类型为
socklen_t
,表示msg_name
指向的地址的长度。 -
如果
msg_name
为NULL
,则msg_namelen
也应为0
。
-
-
msg_iov
:- 类型为
struct iovec *
,指向一个iovec
数组。每个iovec
描述了一个数据缓冲区。
- 类型为
-
msg_iovlen
:- 类型为
size_t
,表示msg_iov
数组中的元素数量。
- 类型为
-
msg_control
:-
类型为
void *
,指向辅助数据(控制信息)的缓冲区。 -
辅助数据可以包含文件描述符、时间戳等协议特定的信息。
-
-
msg_controllen
:-
类型为
size_t
,表示辅助数据的长度。 -
在发送时,必须正确设置
msg_controllen
,以确保接收方能够正确解析辅助数据。 -
在接收时,
msg_controllen
由系统设置,表示接收到的辅助数据的长度。
-
-
msg_flags
:-
类型为
int
,表示消息的标志。 -
在发送时,通常设置为
0
。 -
在接收时,
msg_flags
由系统设置,表示消息的实际状态(如是否为带外数据)。
-
使用场景
msghdr
结构体通常用于以下场景:
-
散列(scatter/gather)I/O:
-
通过
msg_iov
和msg_iovlen
,可以将数据分散到多个缓冲区中,避免一次性拷贝到一个大缓冲区。 -
提高 I/O 操作的效率,特别适用于处理大量数据。
-
-
辅助数据传递:
-
通过
msg_control
和msg_controllen
,可以传递文件描述符、时间戳等辅助数据。 -
例如,使用
SCM_RIGHTS
传递文件描述符,或使用SO_TIMESTAMP
获取时间戳。
-
-
网络编程:
-
在处理网络数据时,可以将数据直接写入或从多个缓冲区中读取,避免中间拷贝。
-
例如,将多个消息片段一次性发送到网络套接字中,或从套接字中一次性读取多个消息片段。
-
struct cmsghdr
cmsghdr
结构体用于描述辅助数据(控制信息),通常与 sendmsg()
和 recvmsg()
系统调用一起使用。辅助数据可以包含各种协议特定的信息,例如文件描述符(通过 SCM_RIGHTS
)、时间戳(通过 SO_TIMESTAMP
)等。cmsghdr
结构体是这些辅助数据的通用头部。
cpp
#include <sys/socket.h>
struct cmsghdr {
size_t cmsg_len; // 控制信息的长度(包括头部和数据)
int cmsg_level; // 协议级别(例如 SOL_SOCKET)
int cmsg_type; // 控制信息的类型(例如 SCM_RIGHTS)
};
字段说明
-
cmsg_len
:-
类型为
size_t
,表示控制信息的总长度,包括cmsghdr
头部和后续的数据部分。 -
在发送时,必须正确设置
cmsg_len
,以确保接收方能够正确解析控制信息。 -
在接收时,
cmsg_len
由系统设置,表示接收到的控制信息的长度。
-
-
cmsg_level
:-
类型为
int
,表示控制信息的协议级别。 -
常见的值包括:
-
SOL_SOCKET
:表示控制信息与套接字协议相关(例如SCM_RIGHTS
)。 -
其他协议级别(如
IPPROTO_IP
或SOL_TCP
)可能用于特定协议的控制信息。
-
-
-
cmsg_type
:-
类型为
int
,表示控制信息的具体类型。 -
常见的值包括:
-
SCM_RIGHTS
:用于传递文件描述符。 -
SCM_CREDENTIALS
:用于传递进程凭证(用户ID、组ID等)。 -
其他协议特定的类型。
-
-
辅助宏
如果存在多个控制信息,会构成一个控制信息序列,规范要求使用者绝不能直接操作控制信息序列,而是需要用一系列的 cmsg 宏来间接操作。
为了方便操作 cmsghdr
结构体,Linux 提供了一些辅助宏:
CMSG_SPACE
:
-
用于计算包含控制信息的总空间大小(包括头部和数据)。
-
CMSG_SPACE(size_t length)
计算的是cmsghdr
头部加上length
大小(实际的数据部分)的数据部分所需的总空间。用于设置 msg->msg_controllen,传入 cmsghdr 中数据字段大小,返回数据字段和cmsghdr
头部大小的和。
cpp
size_t CMSG_SPACE(size_t length);
CMSG_LEN
:
- 用于计算控制信息的长度(包括头部和数据)。用于设置 cmsg->cmsg_len
cpp
size_t CMSG_LEN(size_t length);
CMSG_FIRSTHDR
:
-
用于获取第一个控制信息的头部指针。
-
如果
msg_controllen
小于sizeof(struct cmsghdr)
,则返回NULL
,表示没有控制信息。 -
如果缓冲区大小足够,返回
msg_control
指向的缓冲区的起始地址,强制转换为struct cmsghdr
类型。
cpp
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mhdr);
示例:设置单个控制信息
cpp
struct msghdr msg = {0};
struct iovec iov[1];
char buffer[BUFFER_SIZE] = "Hello from parent";
struct cmsghdr *cmsg; //注意控制信息这是一个指针,未申请内存
int *fd_ptr;
// 初始化 msghdr 结构
msg.msg_iov = iov;
msg.msg_iovlen = 1;
iov[0].iov_base = buffer;
iov[0].iov_len = strlen(buffer) + 1;
// 初始化控制信息
msg.msg_control = malloc(CMSG_SPACE(sizeof(int))); // 分配总空间大小
msg.msg_controllen = CMSG_SPACE(sizeof(int)); // 设置控制信息的总长度
cmsg = CMSG_FIRSTHDR(&msg); // 设置存储控制信息的位置,获取第一个控制信息头部
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int)); // 设置控制信息的长度
fd_ptr = (int *)CMSG_DATA(cmsg); // 获取数据部分
*fd_ptr = fd_to_send;
CMSG_NXTHDR
:
- 用于获取下一个控制信息的头部指针。
cpp
struct cmsghdr *CMSG_NXTHDR(struct msghdr *mhdr, struct cmsghdr *cmsg);
示例:设置多个控制信息
cpp
struct msghdr msg = {0};
struct iovec iov[1];
char buffer[BUFFER_SIZE] = "Hello from parent";
struct cmsghdr *cmsg1, *cmsg2;
int *fd_ptr;
// 初始化 msghdr 结构
msg.msg_iov = iov;
msg.msg_iovlen = 1;
iov[0].iov_base = buffer;
iov[0].iov_len = strlen(buffer) + 1;
// 分配足够的空间以存储两个控制信息
msg.msg_control = malloc(CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(int)));
msg.msg_controllen = CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(int));
// 初始化第一个控制信息
cmsg1 = CMSG_FIRSTHDR(&msg);
cmsg1->cmsg_level = SOL_SOCKET;
cmsg1->cmsg_type = SCM_RIGHTS;
cmsg1->cmsg_len = CMSG_LEN(sizeof(int));
fd_ptr = (int *)CMSG_DATA(cmsg1);
*fd_ptr = fd1;
// 初始化第二个控制信息
cmsg2 = (struct cmsghdr *)((char *)cmsg1 + CMSG_LEN(sizeof(int)));
cmsg2->cmsg_level = SOL_SOCKET;
cmsg2->cmsg_type = SCM_RIGHTS;
cmsg2->cmsg_len = CMSG_LEN(sizeof(int));
fd_ptr = (int *)CMSG_DATA(cmsg2);
*fd_ptr = fd2;
CMSG_DATA
:
-
用于获取控制信息的数据部分。传入 cmsghdr 的首地址,返回 cmsghdr 中的 data 字段首地址。
-
CMSG_DATA
宏的作用是计算cmsghdr
结构体中数据部分的起始地址。它通过将cmsghdr
结构体的地址加上cmsghdr
头部的大小,得到数据部分的地址。 -
CMSG_DATA
宏返回一个指向数据部分的指针,类型为void *
。你可以将这个指针强制转换为所需的数据类型(例如int *
或struct timeval *
),然后读取或设置数据。
cpp
void *CMSG_DATA(struct cmsghdr *cmsg);
使用场景
cmsghdr
结构体通常用于以下场景:
-
传递文件描述符:
- 使用
SCM_RIGHTS
,可以在进程间传递文件描述符。
- 使用
-
传递进程凭证:
- 使用
SCM_CREDENTIALS
,可以在进程间传递用户ID、组ID等信息。
- 使用
-
协议特定的控制信息:
- 例如,传递 IP 选项或 TCP 选项。
msghdr
和 cmsghdr
的关系
-
msghdr
是消息头的总体描述:-
msghdr
结构体描述了消息的整体结构,包括数据缓冲区、目标地址、辅助数据等。 -
它是一个高层的描述符,用于封装消息的所有组成部分。
-
-
cmsghdr
是辅助数据的描述:-
cmsghdr
结构体描述了辅助数据(控制信息)的具体内容,例如文件描述符、时间戳等。 -
它是
msghdr
中msg_control
指向的缓冲区的一部分,用于描述每一块辅助数据。
-
具体关系
-
msghdr
包含cmsghdr
:-
msghdr
中的msg_control
字段指向一个缓冲区,这个缓冲区中包含了多个cmsghdr
结构体。 -
每个
cmsghdr
结构体描述了一块辅助数据,这些辅助数据可以是文件描述符、时间戳等。
-
-
CMSG_FIRSTHDR
和CMSG_NXTHDR
宏:-
CMSG_FIRSTHDR
宏用于从msghdr
中获取第一个cmsghdr
。 -
CMSG_NXTHDR
宏用于从当前cmsghdr
获取下一个cmsghdr
。 -
这些宏帮助程序遍历
msg_control
缓冲区中的所有cmsghdr
结构体。
-
bash
msghdr
+-----------------------------+
| msg_name | | <- 目标地址或源地址
| msg_namelen | | <- 地址长度
| msg_iov | | <- 数据缓冲区数组
| msg_iovlen | | <- 数据缓冲区数组的长度
| msg_control |------->| <- 辅助数据缓冲区
| msg_controllen | | <- 辅助数据缓冲区的长度
| msg_flags | | <- 消息标志
+-----------------------------+
msg_control
+-----------------------------------------------+
| cmsghdr1 | cmsghdr2 | ... | cmsghdrN | |
+-----------------------------------------------+
cmsghdr
+-----------------------------------------------+
| cmsg_len | cmsg_level | cmsg_type | cmsg_data |
+-----------------------------------------------+
sendmsg
sendmsg()
是一个用于发送消息的系统调用,它比 send()
或 write()
更为通用和强大。它允许发送带有多种附加信息(如文件描述符)的消息,通常用于高级的套接字编程。
cpp
#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
参数说明
-
sockfd
:- 套接字文件描述符,表示要发送消息的套接字。
-
msg
:- 指向
struct msghdr
结构的指针,该结构定义了要发送的消息的内容和格式。struct msghdr
的定义如下:
- 指向
cpp
struct msghdr {
void *msg_name; // 可选的地址
socklen_t msg_namelen; // 地址长度
struct iovec *msg_iov; // 散列(scatter/gather)数组
size_t msg_iovlen; // 散列数组中的元素数量
void *msg_control; // 可选的控制信息
size_t msg_controllen; // 控制信息的长度
int msg_flags; // 标志(通常为 0)
};
-
msg_name
和msg_namelen
:用于指定目标地址(例如在 UDP 套接字中)。对于已连接的套接字(如 TCP),通常设置为NULL
和0
。 -
msg_iov
和msg_iovlen
:定义了要发送的数据。msg_iov
是一个指向struct iovec
数组的指针,每个struct iovec
包含以下内容:
cpp
struct iovec {
void *iov_base; // 数据的起始地址
size_t iov_len; // 数据的长度
};
-
flags
:-
用于控制消息发送的行为。常见的标志包括:
-
MSG_DONTWAIT
:非阻塞模式,即使套接字设置为阻塞模式,也会立即返回。 -
MSG_NOSIGNAL
:防止发送SIGPIPE
信号(当对方关闭连接时)。 -
MSG_EOR
:表示消息结束(仅适用于某些协议)。
-
-
返回值
-
成功:返回发送的字节数。
-
失败 :返回
-1
,并通过errno
设置错误码。
使用场景
sendmsg()
的主要优势在于它可以同时发送多个数据块(通过 msg_iov
和 msg_iovlen
)和控制信息(通过 msg_control
和 msg_controllen
)。这使得它特别适合以下场景:
-
发送文件描述符 :通过
SCM_RIGHTS
,可以在进程间传递文件描述符。 -
发送大量数据:通过散列(scatter/gather)I/O,可以高效地发送多个内存块。
-
高级协议控制:支持协议特定的控制信息。
recvmsg
recvmsg()
是一个用于接收消息的系统调用,与 sendmsg()
配合使用,支持从套接字接收复杂的消息。它不仅可以接收普通的数据,还可以接收控制信息(如文件描述符)。
cpp
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
参数说明
-
sockfd
:- 套接字文件描述符,表示要接收消息的套接字。
-
msg
:- 指向
struct msghdr
结构的指针,该结构定义了接收消息的格式和内容。
- 指向
-
msg_name
和msg_namelen
:用于接收发送方的地址(例如在 UDP 套接字中)。对于已连接的套接字(如 TCP),通常设置为NULL
和0
。 -
msg_iov
和msg_iovlen
:定义了接收数据的缓冲区。msg_iov
是一个指向struct iovec
数组的指针。
-
msg_control
和msg_controllen
:用于接收控制信息(如辅助数据)。控制信息通常用于接收文件描述符(通过SCM_RIGHTS
)或其他协议特定的信息。 -
msg_flags
:由recvmsg()
设置,表示消息的接收状态(如是否为带外数据)。 -
flags
:-
用于控制消息接收的行为。常见的标志包括:
-
MSG_DONTWAIT
:非阻塞模式,即使套接字设置为阻塞模式,也会立即返回。 -
MSG_PEEK
:查看消息但不消耗它(消息仍然保留在套接字接收队列中)。 -
MSG_WAITALL
:等待直到所有请求的数据都被接收(仅适用于阻塞套接字)。
-
-
返回值
-
成功:返回接收到的字节数。
-
失败 :返回
-1
,并通过errno
设置错误码。
注意事项
-
控制信息大小 :
msg_controllen
必须足够大,以容纳控制信息。使用CMSG_SPACE()
宏来计算所需的空间。 -
文件描述符传递 :通过
SCM_RIGHTS
传递文件描述符时,接收端会获得一个有效的文件描述符,但发送端的文件描述符不会被关闭。 -
协议支持 :
recvmsg()
和sendmsg()
主要用于套接字编程,但某些协议(如 TCP)可能不支持某些控制信息。 -
msg_flags
:接收完成后,msg_flags
会被设置为消息的实际状态(如是否为带外数据)。如果需要检查这些标志,可以在调用后检查msg_flags
的值。
示例:父子进程使用 sendmsg 和 readmsg 进行通信
cpp
int sendFd(int sockfd, int fdToSend){
struct msghdr msg;
memset(&msg, 0, sizeof(msg)); //name->NULL namelen->0 flag->0
char *buf = "hello";
struct iovec vec[1]; //数组记录离散区域
vec[0].iov_base = buf;
vec[0].iov_len = 5;
msg.msg_iov = vec;
msg.msg_iovlen = 1;
//以下为控制字段
struct cmsghdr *cmsg = (struct cmsghdr*)calloc(1, CMSG_LEN(sizeof(int)));
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
*(int *)CMSG_DATA(cmsg) = fdToSend;
msg.msg_control = cmsg;
msg.msg_controllen = CMSG_LEN(sizeof(int));
int ret = sendmsg(sockfd, &msg, 0);
ERROR_CHECK(ret, -1, "sendmsg");
return 0;
}
int recvFd(int sockfd, int *pfdToRecv){
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
char buf[6] = {0};
struct iovec vec[1];
vec[0].iov_base = buf;
vec[0].iov_len = 5;
msg.msg_iov = vec;
msg.msg_iovlen = 1;
struct cmsghdr *cmsg = (struct cmsghdr *)malloc(CMSG_LEN(sizeof(int)));
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
msg.msg_control = cmsg;
msg.msg_controllen = CMSG_LEN(sizeof(int));
msg.msg_iov = vec;
int ret = recvmsg(sockfd, &msg, 0);
ERROR_CHECK(ret, -1, "recvmsg");
printf("buf = %s,fd = %d\n", buf, *(int *)CMSG_DATA(cmsg));
*pfdToRecv = *(int *)CMSG_DATA(cmsg);
return 0;
}
int main(int argc, char const *argv[])
{
int fds[2];
char *buf = "hello world";
socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
if(fork()){
close(fds[0]);
int fdfile = open(argv[1], O_RDWR);
printf("child fdfile = %d\n", fdfile);
ERROR_CHECK(fdfile, -1, "open");
write(fdfile, buf, strlen(buf));
sendFd(fds[1], fdfile);
wait(NULL);
}else{
close(fds[1]);
int fdfile;
recvFd(fds[0], &fdfile);
printf("parent file = %d\n", fdfile);
lseek(fdfile, 0, SEEK_SET);
char buf[20];
read(fdfile, buf, sizeof(buf));
printf("buf is :%s\n",buf);
}
return 0;
}
为什么 sendmsg()
和 recvmsg()
可以共享文件描述符,而管道不行?
-
sendmsg()
:-
sendmsg()
使用SCM_RIGHTS
机制,内核会为接收进程创建一个新的文件描述符,这个文件描述符指向与发送进程相同的文件对象。 -
接收进程可以直接使用这个新的文件描述符访问文件对象。
-
-
管道:
-
管道只能传递字节流数据,不能直接传递文件描述符。
-
传递文件描述符的数值没有意义,因为文件描述符的数值在不同的进程中没有直接关联。
-
我们看一下上面程序的输出结果:
cpp
(base) ubuntu@ubuntu:~/MyProject/processPool$ gcc shareFd.c -o shareFd
(base) ubuntu@ubuntu:~/MyProject/processPool$ ./shareFd file1
child fdfile = 3
buf = hello,fd = 4
parent file = 4
buf is :hello world
可以看到,父进程和子进程对于同一文件对象所打开的文件描述符是不同的,原因如下:
父进程首先通过 socketpair 创建了本地套接字 fds[0] 和 fds[1],创建子进程后,父进程关闭了fds[0],3号文件描述符空闲,子进程关闭了fds[1],4号文件描述符空闲。随后父进程打开了file文件,并将其共享给子进程,此时内核把空闲的3号和4号文件描述符分配了父子进程。如下图所示:
