湛江网站建设公司哪家好,诚信网站建设,wordpress性能差,宁波外贸公司招聘要求在上一篇文章Unix套接字编程及通信例子中#xff0c;我们对Unix套接字编程有一个基本的了解。但在Unix套接字编程的领域中#xff0c;有一组特殊而强大的工具#xff1a;socketpair、sendmsg 和 recvmsg#xff0c;它们为实现本地进程间通信提供了便捷的方式。 文章目录 1 …在上一篇文章Unix套接字编程及通信例子中我们对Unix套接字编程有一个基本的了解。但在Unix套接字编程的领域中有一组特殊而强大的工具socketpair、sendmsg 和 recvmsg它们为实现本地进程间通信提供了便捷的方式。 文章目录 1 socketpair2 sendmsg和recvmsg2.1 函数原型2.2 msghdr结构体2.3 cmsghdr结构体2.4 实例2.4.1 初始化2.4.2 子进程实现2.4.3 父进程实现 2.4.4 实验结果2.4.5 完整代码 1 socketpair
socketpair是一个用于在同一台计算机上创建一对相互连接的套接字的系统调用。这对套接字可以用于进程间的本地通信通常用于父子进程或兄弟进程之间。它创建的套接字对是相互连接的因此数据可以直接在这两个套接字之间传递而无需经过内核缓冲区从而提高了通信的效率。
int socketpair(int domain, int type, int protocol, int sv[2]);domain地址族通常设置为 AF_UNIX表示使用Unix域套接字。type套接字类型通常设置为 SOCK_STREAM或SOCK_DGRAM。protocol指定使用的协议通常设置为 0表示使用默认协议。sv一个包含两个整数的数组用于存储创建的套接字描述符。
这和匿名管道(pipe)很像但匿名管道中的文件描述符是单方向的只能支持一个方向的数据流其中描述符0固定用于读描述符1固定用于写。而socketpair是一个全双工通信通道它同时支持双向的数据流。两个文件描述符都支持双向通信下面来看一个例子
#include sys/types.h
#include sys/socket.h
#include stdio.h
#include unistd.h
#include string.hvoid send_message(int sockfd, const char* message) {send(sockfd, message, strlen(message), 0);
}void receive_message(int sockfd, char* buffer, size_t buffer_size) {ssize_t received_bytes recv(sockfd, buffer, buffer_size - 1, 0);if (received_bytes 0) {buffer[received_bytes] \0; // Null-terminate the received dataprintf(Received: %s\n, buffer);} else {perror(Error receiving message);}
}int main() {int sv[2];if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) -1) {perror(Error creating socket pair);return -1;}pid_t pid fork();if (pid -1) {perror(Error forking);return -1;}if (pid 0) {// 子进程close(sv[0]);char buffer[1024];receive_message(sv[1], buffer, sizeof(buffer));send_message(sv[1], get a message from father);close(sv[1]); // 关闭写端} else {// 父进程close(sv[1]);send_message(sv[0], 123);char buffer[1024];receive_message(sv[0], buffer, sizeof(buffer));close(sv[0]); // 关闭读端}return 0;
}在这个例子中创建了一个子进程其中sv[0]用于表示子进程的套接字sv[1]用于表示父进程的套接字。在父进程中向子进程发送123后开始接收数据而子进程收到123后发送get a message from father给父进程然后退出程序。父进程收到后也退出程序。实验结果如下 在这里sv[0]和sv[1]既用来读也用来写表明这两个套接字都是全双工的。
2 sendmsg和recvmsg
2.1 函数原型
sendmsg函数向套接字发送消息允许同时发送多个缓冲区的数据以及附带文件描述符等辅助信息。
recvmsg函数用于接收通过套接字传输的消息并允许接收辅助数据如控制信息、文件描述符等。
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);sockfd套接字描述符msg指向 struct msghdr结构的指针该结构定义了消息的各个部分包括数据缓冲区、控制信息等flags标志参数通常设置为 0
2.2 msghdr结构体
struct msghdr结构的定义如下
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 */size_t msg_controllen; /* ancillary data buffer len */int msg_flags; /* flags on received message */
};msg_name(消息地址) 用于指定消息的目标地址通常在发送消息时为sendmsg提供目标地址信息而在接收消息时可以存储发送方的地址。通常设置为NULL表示不指定目标地址。 msg_namelen(地址长度) 用于指定msg_name指向的地址结构的长度。通常在发送消息时为sendmsg提供地址结构的长度而在接收消息时则用于存储实际接收到的地址的长度。 msg_iov(I/O 向量) msg_iov是一个指向struct iovec结构的指针该结构用于指定消息中的数据缓冲区可以是多个缓冲区。通过msg_iovlen来指定缓冲区数组的长度。 struct iovec {void *iov_base; /* 指向缓冲区的起始地址 */size_t iov_len; /* 缓冲区的大小 */
};msg_iovlen(I/O 向量长度) 用于指定msg_iov指向的缓冲区数组的长度即消息中包含多少个缓冲区。 所以sendmsg/recvmsg与sendto/recvfrom最明显的不同是前者可以通过msg_iov和msg_iovlen发送/接收多个缓冲区而后者只能发送/接收一个。 msg_control(控制信息) msg_control用于传递辅助信息通常是控制信息或者辅助数据。这可以包括在套接字编程中使用的辅助信息如辅助文件描述符等。通常设置为NULL表示不传递控制信息。 msg_controllen(控制信息长度) 用于指定 msg_control 指向的控制信息的长度。在发送消息时为sendmsg提供控制信息的长度而在接收消息时用于存储实际接收到的控制信息的长度。 msg_flags(消息标志) 用于存储消息的标志包括一些操作的状态信息。在recvmsg函数中可以通过msg_flags获取一些接收消息时的状态信息。
2.3 cmsghdr结构体
在使用msg_control时通常会搭配使用struct cmsghdr结构该结构定义了一种通用的、可扩展的辅助数据头部。
struct cmsghdr {socklen_t cmsg_len; /* 辅助数据的总长度 */int cmsg_level; /* 源层协议一般设置为 SOL_SOCKET */int cmsg_type; /* 辅助数据的类型 *//* 后续紧随辅助数据 *///unsigned char cmsg_data[];
};cmsg_level常见取值
SOL_SOCKET 表示这是与套接字相关的辅助数据。自定义层级 除了 SOL_SOCKET还可以定义其他自定义的层级用于特定的应用或协议
cmsg_type 常见取值(对于SOL_SOCKET层级)
SCM_RIGHTS 表示辅助数据用于传递文件描述符。SCM_CREDENTIALS 表示辅助数据用于传递进程凭证(例如用户标识)。 在Linux中提供了一些宏定义来使用这个结构体 CMSG_FIRSTHDR宏 获取消息头的第一个辅助数据块。如果消息头中没有足够的空间来存储一个struct cmsghdr则返回 NULL。 #define CMSG_FIRSTHDR(mhdr) ((mhdr)-msg_controllen sizeof(struct cmsghdr) ? (struct cmsghdr *)(mhdr)-msg_control : NULL)CMSG_NXTHDR宏 获取下一个辅助数据块。通过传递当前的辅助数据块可以获取下一个辅助数据块的指针。如果没有下一个块返回 NULL。 #define CMSG_NXTHDR(mhdr, cmsg) ((char *)(cmsg) CMSG_ALIGN((cmsg)-cmsg_len) sizeof(struct cmsghdr) (char *)(mhdr)-msg_control (mhdr)-msg_controllen ? NULL : (struct cmsghdr *)((char *)(cmsg) CMSG_ALIGN((cmsg)-cmsg_len)))CMSG_DATA宏 获取辅助数据块中实际数据的指针。通过传递辅助数据块的指针可以获取实际数据的起始位置。 #define CMSG_DATA(cmsg) ((unsigned char *)(cmsg) CMSG_ALIGN(sizeof(struct cmsghdr)))CMSG_LEN宏 计算一个辅助数据块的总长度包括头部和实际数据。 它的值等于结构体cmsghdr中的cmsg_len字段的值 #define CMSG_LEN(len) (_CMSG_HDR_ALIGN(sizeof(struct cmsghdr)) (len))CMSG_SPACE宏 计算辅助数据块所需的总空间包括头部和实际数据并进行对齐 #define CMSG_SPACE(len) (_CMSG_HDR_ALIGN(sizeof(struct cmsghdr)) _CMSG_DATA_ALIGN(len))如下图所示为msg_control字段的示意图 图中的pad是为了字节对齐的填充部分
2.4 实例
上面的理论挺复杂的理论还是得通过实践才能更好的理解。
目的使用多个struct iovec来发送和接收一个缓冲区的数据并在msg_control字段中传递文件描述符作为辅助数据。
2.4.1 初始化
首先声明两个buffer这里设置buffer1的初始值为0xab而buffer2的初始值为0xcd为了后续判断内容是否成功接收。然后创建用于父子进程通信的套接字并fork子进程。
#define BUF_SIZE 1024
unsigned char buffer1[BUF_SIZE], buffer2[BUF_SIZE];
memset(buffer1, 0xab, sizeof(buffer1));
memset(buffer2, 0xcd, sizeof(buffer2));int sockfd[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
pid_t pid fork();2.4.2 子进程实现
子进程发送buffer1的内容给父进程同时在辅助信息中传递一个文件描述符。
(1)声明msghdr结构体
struct msghdr message {0};(2)填充发送缓冲区
首先填充我们要发送的字段struct iovec *msg_iov这里声明一个字段iov[1]内容为buffer1。
struct iovec iov[1];
iov[0].iov_base buffer1;
iov[0].iov_len sizeof(buffer1);message.msg_iov iov;
message.msg_iovlen 1;(3)填充辅助信息
我们希望通过辅助信息传递文件描述符首先声明辅助信息字段
char control_data[CMSG_SPACE(sizeof(int))];message.msg_control control_data;
message.msg_controllen sizeof(control_data);control_data用于存储辅助数据。CMSG_SPACE(sizeof(int))用于计算辅助数据所需的总空间包括头部和实际数据的空间并进行对齐。我们现在想传递一个文件描述符(int类型)所以使用 sizeof(int) 计算其大小。
填充辅助信息字段
struct cmsghdr *cmsg CMSG_FIRSTHDR(message);
cmsg-cmsg_len CMSG_LEN(sizeof(int));
cmsg-cmsg_level SOL_SOCKET;
cmsg-cmsg_type SCM_RIGHTS;int file_descriptor open(example.txt, O_RDONLY);
*((int *)CMSG_DATA(cmsg)) file_descriptor;这里的CMSG_FIRSTHDR(message)实际上就指向message.msg_control通过这个宏定义强制转换然后填充cmsg_len、cmsg_level和cmsg_type字段。
cmsg_level设置为SOL_SOCKET表示这是一个与套接字相关的辅助数据cmsg_type设置为SCM_RIGHTS 表示这是一个用于传递文件描述符的辅助数据块*((int *)CMSG_DATA(cmsg))设置cmsg的data字段为文件描述符这里打开目录下的example.txt文件
(4)发送数据
sendmsg(sockfd[1], message, 0);2.4.3 父进程实现
父进程则接收子进程发来的消息
(1)声明接收缓冲区和辅助信息结构体
接收和发送的数据大小要匹配这里设置接收的iov_base为buffer2
struct iovec iov[1];
iov[0].iov_base buffer2;
iov[0].iov_len sizeof(buffer2);char control_data[CMSG_SPACE(sizeof(int))];
struct msghdr message {0};
message.msg_iov iov;
message.msg_iovlen 1;
message.msg_control control_data;
message.msg_controllen sizeof(control_data);(2)接收消息
recvmsg(sockfd[0], message, 0);(3)打印接收缓冲区内容
这里就打印前4字节的内容如果接收成功buffer2的内容应该为0xab而不是0xcd。
printf(buffer2[0]~buffer2[4] %x %x %x %x\n, buffer2[0], buffer2[1], buffer2[2], buffer2[3]);(4)使用辅助信息中的文件描述符
这里得到子进程传过来的文件描述符然后打开这个文件并读取到buffer2中然后输出文件的内容。
// 从辅助数据中获取文件描述符
struct cmsghdr *cmsg CMSG_FIRSTHDR(message);
int received_fd;
memcpy(received_fd, CMSG_DATA(cmsg), sizeof(int));ssize_t bytes_read read(received_fd, buffer2, sizeof(buffer2));2.4.4 实验结果
首先我们需要在目录下创建一个example.txt文件随便输入一点内容 接着我们运行程序实验结果如下 符合我们的预期。
2.4.5 完整代码
#include sys/socket.h
#include sys/types.h
#include unistd.h
#include fcntl.h
#include stdio.h
#include stdlib.h
#include string.h#define BUF_SIZE 1024int main() {int sockfd[2];unsigned char buffer1[BUF_SIZE], buffer2[BUF_SIZE];memset(buffer1, 0xab, sizeof(buffer1));memset(buffer2, 0xcd, sizeof(buffer2));if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd) -1) {perror(Error creating socket pair);return -1;}pid_t pid fork();if (pid -1) {perror(Error forking);return -1;}if (pid 0) {// 子进程 (发送方)close(sockfd[0]); // 关闭子进程中不需要的读端// 打开文件并获取文件描述符int file_descriptor open(example.txt, O_RDONLY);if (file_descriptor -1) {perror(Error opening file);return -1;}// 准备消息struct iovec iov[1];iov[0].iov_base buffer1;iov[0].iov_len sizeof(buffer1);char control_data[CMSG_SPACE(sizeof(int))];struct msghdr message {0};message.msg_iov iov;message.msg_iovlen 1;message.msg_control control_data;message.msg_controllen sizeof(control_data);// 构建控制信息头部struct cmsghdr *cmsg CMSG_FIRSTHDR(message);cmsg-cmsg_len CMSG_LEN(sizeof(int));cmsg-cmsg_level SOL_SOCKET;cmsg-cmsg_type SCM_RIGHTS;// 将文件描述符复制到辅助数据中*((int *)CMSG_DATA(cmsg)) file_descriptor;// 发送消息if (sendmsg(sockfd[1], message, 0) -1) {perror(Error sending message);close(file_descriptor);return -1;}close(file_descriptor); // 不再需要文件描述符close(sockfd[1]); // 关闭写端} else {// 父进程 (接收方)close(sockfd[1]); // 关闭父进程中不需要的写端struct iovec iov[1];iov[0].iov_base buffer2;iov[0].iov_len sizeof(buffer2);char control_data[CMSG_SPACE(sizeof(int))];struct msghdr message {0};message.msg_iov iov;message.msg_iovlen 1;message.msg_control control_data;message.msg_controllen sizeof(control_data);// 接收消息if (recvmsg(sockfd[0], message, 0) -1) {perror(Error receiving message);return -1;}printf(buffer2[0]~buffer2[4] %x %x %x %x\n, buffer2[0], buffer2[1], buffer2[2], buffer2[3]);// 从辅助数据中获取文件描述符struct cmsghdr *cmsg CMSG_FIRSTHDR(message);int received_fd;memcpy(received_fd, CMSG_DATA(cmsg), sizeof(int));// 读取文件内容printf(Received file descriptor: %d\n, received_fd);ssize_t bytes_read read(received_fd, buffer1, sizeof(buffer1));if (bytes_read -1) {perror(Error reading file);return -1;}// 打印文件内容printf(Received data from file: %.*s\n, (int)bytes_read, buffer1);close(received_fd); // 关闭接收到的文件描述符close(sockfd[0]); // 关闭读端}return 0;
}