我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 recvmsg 和 sendmsg 函数,它们是功能最强大、最通用的套接字 I/O 函数,可以处理 read/write、send/recv 以及 sendto/recvfrom 的所有功能,并且还支持更高级的特性,如传输文件描述符、**发送和接收访问控制列表 **(ancillary data)。
data-ad-format="fluid"
data-ad-layout-key="-7k+ex-4a-9w+4a">
sendmsg系统调用及示例-CSDN博客
https://www.calcguide.tech/2025/08/23/sendmsg系统调用及示例/
1. 函数介绍 recvmsg 和 sendmsg 是 Linux 系统调用,它们提供了最灵活和最完整的套接字数据传输接口。它们是 read/write、send/recv、sendto/recvfrom 的超集。
sendmsg: 通过套接字发送数据。它允许你指定:
recvmsg: 通过套接字接收数据。它允许你:
你可以把它们想象成一个多功能的包裹处理系统:
2. 函数原型 1 2 3 4 5 6 7 8 #include <sys/socket.h> // 必需 // 发送消息 ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); // 接收消息 ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
3. 功能
4. 参数 这两个函数都通过一个核心的 struct msghdr 结构体来传递所有必要的信息。
struct msghdr 结构体 这是两个函数的核心,定义了消息的完整属性:
1 2 3 4 5 6 7 8 9 10 struct msghdr { void *msg_name; // 可选的地址 (struct sockaddr*) socklen_t msg_namelen; // 地址长度 struct iovec *msg_iov; // 缓冲区向量 (scatter/gather) size_t msg_iovlen; // 缓冲区向量的元素个数 void *msg_control; // 辅助数据 (cmsghdr*) size_t msg_controllen; // 辅助数据缓冲区大小 int msg_flags; // 接收消息时的标志 (输出) };
void *msg_name:
socklen_t msg_namelen:
struct iovec *msg_iov: 这是一个 struct iovec 数组的指针,用于实现分散-聚集 I/O(scatter-gather I/O),即数据可以来自多个不连续的缓冲区(类似 readv/writev)。struct iovec 定义如下:struct iovec { void *iov_base; // 缓冲区起始地址 size_t iov_len; // 缓冲区长度 };
size_t msg_iovlen: msg_iov 数组中元素的个数。
void *msg_control: 指向用于发送或接收辅助数据(ancillary data)的缓冲区。辅助数据可以包含文件描述符、网络接口索引、IP 选项等。
size_t msg_controllen:
int msg_flags:
函数参数
int flags: 控制发送或接收行为的标志位,与 send/recv/sendto/recvfrom 的 flags 参数类似。
常见标志:MSG_DONTWAIT, MSG_PEEK, MSG_WAITALL, MSG_NOSIGNAL 等。
5. 返回值 成功时:
返回实际传输的字节数(即所有 msg_iov 缓冲区中数据的总和)。
失败时:
返回 -1,并设置全局变量 errno 来指示具体的错误原因。
6. 相似函数,或关联函数
send / sendto / recv / recvfrom: sendmsg/recvmsg 是这些函数的通用形式。
writev / readv: sendmsg/recvmsg 结合 iovec 实现了类似的功能。
sendmmsg / recvmmsg: (Linux 特有) 可以在一次系统调用中发送或接收多个消息,性能更高。
CMSG_* 宏: 用于处理 msg_control 中的辅助数据(如 CMSG_FIRSTHDR, CMSG_NXTHDR, CMSG_DATA)。
7. 示例代码 示例 1:使用 sendmsg/recvmsg 替代 sendto/recvfrom (UDP) 这个例子展示了如何用 sendmsg 和 recvmsg 实现与 sendto 和 recvfrom 相同的功能(发送和接收 UDP 数据报)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 // msg_udp_client.c #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define SERVER_PORT 8082 #define SERVER_IP "127.0.0.1" #define BUFFER_SIZE 1024 int main() { int sock; struct sockaddr_in server_addr; struct msghdr msg; struct iovec iov[1]; // 只使用一个缓冲区 char *message = "Hello from sendmsg/recvmsg client!"; char buffer[BUFFER_SIZE]; ssize_t bytes_sent, bytes_received; // 1. 创建 UDP 套接字 sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } // 2. 配置服务器地址 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) { fprintf(stderr, "Invalid address\n"); close(sock); exit(EXIT_FAILURE); } // --- 使用 sendmsg 发送 --- printf("Sending message using sendmsg...\n"); // 准备 iovec iov[0].iov_base = message; iov[0].iov_len = strlen(message); // 准备 msghdr memset(&msg, 0, sizeof(msg)); msg.msg_name = &server_addr; // 目标地址 msg.msg_namelen = sizeof(server_addr); msg.msg_iov = iov; // 数据缓冲区向量 msg.msg_iovlen = 1; // 向量元素个数 // 发送消息 bytes_sent = sendmsg(sock, &msg, 0); if (bytes_sent < 0) { perror("sendmsg failed"); close(sock); exit(EXIT_FAILURE); } else { printf("sendmsg sent %zd bytes.\n", bytes_sent); } // --- 使用 recvmsg 接收 --- printf("Receiving reply using recvmsg...\n"); struct sockaddr_in src_addr; socklen_t src_addr_len = sizeof(src_addr); // 准备 iovec for recv iov[0].iov_base = buffer; iov[0].iov_len = BUFFER_SIZE - 1; // 准备 msghdr for recv memset(&msg, 0, sizeof(msg)); msg.msg_name = &src_addr; // 用于存储源地址 msg.msg_namelen = src_addr_len; // 输入:缓冲区大小 msg.msg_iov = iov; msg.msg_iovlen = 1; // 接收消息 bytes_received = recvmsg(sock, &msg, 0); if (bytes_received < 0) { perror("recvmsg failed"); close(sock); exit(EXIT_FAILURE); } else { buffer[bytes_received] = '\0'; printf("recvmsg received %zd bytes from %s:%d: %s\n", bytes_received, inet_ntoa(src_addr.sin_addr), ntohs(src_addr.sin_port), buffer); // msg.msg_namelen 现在包含实际的地址大小 } close(sock); return 0; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 // msg_udp_server.c #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define PORT 8082 #define BUFFER_SIZE 1024 int main() { int server_fd; struct sockaddr_in server_addr, client_addr; struct msghdr msg; struct iovec iov[1]; char buffer[BUFFER_SIZE]; char reply[] = "Echo via sendmsg: "; char reply_buffer[BUFFER_SIZE]; ssize_t bytes_received, bytes_sent; server_fd = socket(AF_INET, SOCK_DGRAM, 0); if (server_fd < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); if (bind(server_fd, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("bind failed"); close(server_fd); exit(EXIT_FAILURE); } printf("UDP server using sendmsg/recvmsg listening on port %d\n", PORT); while (1) { printf("Waiting for datagram...\n"); socklen_t client_addr_len = sizeof(client_addr); iov[0].iov_base = buffer; iov[0].iov_len = BUFFER_SIZE - 1; memset(&msg, 0, sizeof(msg)); msg.msg_name = &client_addr; msg.msg_namelen = client_addr_len; msg.msg_iov = iov; msg.msg_iovlen = 1; bytes_received = recvmsg(server_fd, &msg, 0); if (bytes_received < 0) { perror("recvmsg failed"); continue; } buffer[bytes_received] = '\0'; printf("Received %zd bytes from %s:%d: %s\n", bytes_received, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buffer); // 构造回复 int reply_len = snprintf(reply_buffer, BUFFER_SIZE, "%s%s", reply, buffer); if (reply_len >= BUFFER_SIZE) reply_len = BUFFER_SIZE - 1; // 使用 sendmsg 发送回复 iov[0].iov_base = reply_buffer; iov[0].iov_len = reply_len; memset(&msg, 0, sizeof(msg)); msg.msg_name = &client_addr; // 发送到刚才接收数据的客户端 msg.msg_namelen = sizeof(client_addr); msg.msg_iov = iov; msg.msg_iovlen = 1; bytes_sent = sendmsg(server_fd, &msg, 0); if (bytes_sent < 0) { perror("sendmsg reply failed"); } else { printf("Sent %zd bytes reply using sendmsg.\n", bytes_sent); } } return 0; }
代码解释:
客户端和服务器都创建 UDP 套接字并进行必要的设置(服务器需要 bind)。
发送数据:
准备一个 struct iovec 数组。这里只用一个元素指向消息缓冲区。
准备一个 struct msghdr 结构体 msg。
设置 msg.msg_name 为目标地址,msg.msg_iov 为缓冲区向量,msg.msg_iovlen 为向量长度。
调用 sendmsg(sock, &msg, 0) 发送数据。
接收数据:
准备 struct iovec 和 struct msghdr。
设置 msg.msg_name 为用于存储源地址的缓冲区,msg.msg_namelen 为该缓冲区大小。
设置 msg.msg_iov 和 msg.msg_iovlen。
调用 recvmsg(sock, &msg, 0) 接收数据。
接收后,msg.msg_name 中包含了发送方的地址,msg.msg_namelen 被更新为实际地址大小。
示例 2:使用 sendmsg/recvmsg 进行 Scatter-Gather I/O 这个例子展示了如何使用 iovec 数组通过 sendmsg 发送来自多个缓冲区的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 // scatter_gather_client.c #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define SERVER_PORT 8083 #define SERVER_IP "127.0.0.1" int main() { int sock; struct sockaddr_in server_addr; struct msghdr msg; struct iovec iov[3]; // 使用 3 个缓冲区 char part1[] = "Part1-"; char part2[] = "Part2-"; char part3[] = "Part3-END"; char recv_buffer[1024]; ssize_t bytes_sent, bytes_received; sock = socket(AF_INET, SOCK_STREAM, 0); // 使用 TCP if (sock < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) { fprintf(stderr, "Invalid address\n"); close(sock); exit(EXIT_FAILURE); } // 连接到服务器 if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("connect failed"); close(sock); exit(EXIT_FAILURE); } // --- 使用 sendmsg 发送分散的数据 --- printf("Sending scattered data using sendmsg...\n"); // 准备 iovec 数组 iov[0].iov_base = part1; iov[0].iov_len = strlen(part1); iov[1].iov_base = part2; iov[1].iov_len = strlen(part2); iov[2].iov_base = part3; iov[2].iov_len = strlen(part3); // 准备 msghdr (TCP 不需要地址) memset(&msg, 0, sizeof(msg)); msg.msg_iov = iov; msg.msg_iovlen = 3; bytes_sent = sendmsg(sock, &msg, 0); if (bytes_sent < 0) { perror("sendmsg failed"); close(sock); exit(EXIT_FAILURE); } else { printf("sendmsg sent %zd bytes (should be sum of parts: %zu).\n", bytes_sent, strlen(part1) + strlen(part2) + strlen(part3)); } // 接收服务器的确认 bytes_received = recv(sock, recv_buffer, sizeof(recv_buffer) - 1, 0); if (bytes_received > 0) { recv_buffer[bytes_received] = '\0'; printf("Received confirmation: %s\n", recv_buffer); } close(sock); return 0; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 // scatter_gather_server.c #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define PORT 8083 #define BACKLOG 10 int main() { int server_fd, client_fd; struct sockaddr_in address, client_address; socklen_t client_addr_len = sizeof(client_address); struct msghdr msg; struct iovec iov[3]; char buffer1[50], buffer2[50], buffer3[50]; // 接收缓冲区 char confirmation[] = "Data received successfully!"; int opt = 1; server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == 0) { perror("socket failed"); exit(EXIT_FAILURE); } if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("setsockopt failed"); close(server_fd); exit(EXIT_FAILURE); } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("bind failed"); close(server_fd); exit(EXIT_FAILURE); } if (listen(server_fd, BACKLOG) < 0) { perror("listen failed"); close(server_fd); exit(EXIT_FAILURE); } printf("Scatter-Gather TCP server listening on port %d\n", PORT); client_fd = accept(server_fd, (struct sockaddr *)&client_address, &client_addr_len); if (client_fd < 0) { perror("accept failed"); close(server_fd); exit(EXIT_FAILURE); } printf("Client connected. Waiting for scattered data...\n"); // --- 使用 recvmsg 接收数据到多个缓冲区 --- iov[0].iov_base = buffer1; iov[0].iov_len = sizeof(buffer1) - 1; iov[1].iov_base = buffer2; iov[1].iov_len = sizeof(buffer2) - 1; iov[2].iov_base = buffer3; iov[2].iov_len = sizeof(buffer3) - 1; memset(&msg, 0, sizeof(msg)); msg.msg_iov = iov; msg.msg_iovlen = 3; ssize_t bytes_received = recvmsg(client_fd, &msg, 0); if (bytes_received < 0) { perror("recvmsg failed"); close(client_fd); close(server_fd); exit(EXIT_FAILURE); } else { printf("recvmsg received %zd bytes.\n", bytes_received); // 确保字符串结束 (实际应用中需要更仔细地处理) size_t total_copied = 0; for (int i = 0; i < 3 && total_copied < (size_t)bytes_received; ++i) { size_t to_copy = iov[i].iov_len < (size_t)(bytes_received - total_copied) ? iov[i].iov_len : (size_t)(bytes_received - total_copied); ((char*)iov[i].iov_base)[to_copy] = '\0'; total_copied += to_copy; } printf("Data in buffers:\n Buffer1: '%s'\n Buffer2: '%s'\n Buffer3: '%s'\n", buffer1, buffer2, buffer3); } // 发送确认 send(client_fd, confirmation, strlen(confirmation), 0); close(client_fd); close(server_fd); return 0; }
代码解释:
客户端使用 TCP 连接到服务器。
客户端将一个消息分割成三个部分,存放在 part1, part2, part3 三个不同的缓冲区中。
发送: 使用 sendmsg 和包含三个元素的 iovec 数组,将这三个缓冲区的数据一次性发送出去。这避免了三次 send 调用。
服务器 accept 连接。
接收: 服务器使用 recvmsg 和包含三个元素的 iovec 数组,将接收到的数据分散存储到 buffer1, buffer2, buffer3 三个不同的缓冲区中。
服务器打印出存储在各个缓冲区中的数据。
示例 3:通过 Unix 域套接字传递文件描述符 (辅助数据) 这个例子展示了 sendmsg/recvmsg 最强大的功能之一:传递文件描述符。这是进程间传递打开文件的一种高级方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 // fd_passing.c // 需要编译为两个程序: sender 和 receiver // gcc -o sender fd_passing.c -DSENDER // gcc -o receiver fd_passing.c -DRECEIVER #include <sys/socket.h> #include <sys/un.h> // Unix domain sockets #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <fcntl.h> #define SOCKET_PATH "/tmp/fd_pass_socket" #ifdef SENDER int main() { int sock, file_fd; struct sockaddr_un addr; struct msghdr msg; struct cmsghdr *cmsg; struct iovec iov[1]; char ctrl_buf[CMSG_SPACE(sizeof(int))]; // 为一个 int (fd) 分配控制消息空间 char data[] = "FD"; int data_len = strlen(data); // 1. 创建要传递的文件 file_fd = open("testfile.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (file_fd == -1) { perror("open testfile.txt"); exit(EXIT_FAILURE); } write(file_fd, "This is data in the passed file.\n", 33); printf("Created and wrote to 'testfile.txt' (fd: %d)\n", file_fd); // 2. 创建 Unix 域套接字 sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock == -1) { perror("socket"); close(file_fd); exit(EXIT_FAILURE); } // 3. 连接到接收方 memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1); if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) { perror("connect"); close(file_fd); close(sock); exit(EXIT_FAILURE); } printf("Connected to receiver.\n"); // 4. 准备消息结构 iov[0].iov_base = data; iov[0].iov_len = data_len; memset(&msg, 0, sizeof(msg)); msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = ctrl_buf; msg.msg_controllen = sizeof(ctrl_buf); // 5. 准备辅助数据 (控制消息) cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; // 传递权限 (文件描述符) cmsg->cmsg_len = CMSG_LEN(sizeof(int)); memcpy(CMSG_DATA(cmsg), &file_fd, sizeof(int)); // 将文件描述符复制到控制消息数据部分 msg.msg_controllen = cmsg->cmsg_len; // 更新控制消息长度 // 6. 发送消息 (包含文件描述符) if (sendmsg(sock, &msg, 0) == -1) { perror("sendmsg"); close(file_fd); close(sock); exit(EXIT_FAILURE); } printf("Sent message with file descriptor %d.\n", file_fd); // 注意:发送后,发送方通常应该 close 掉这个 fd // 但在此例中我们不 close,以演示 fd 已被传递 close(sock); // close(file_fd); // 通常在这里关闭,但我们想演示它已被传递 printf("Sender finished. Check if 'testfile.txt' is closed (lsof `pwd`/testfile.txt).\n"); return 0; } #elif defined(RECEIVER) int main() { int listen_sock, conn_sock, received_fd = -1; struct sockaddr_un addr, client_addr; socklen_t client_len; struct msghdr msg; struct cmsghdr *cmsg; struct iovec iov[1]; char ctrl_buf[CMSG_SPACE(sizeof(int))]; char data[10]; ssize_t data_len; // 1. 创建 Unix 域套接字 listen_sock = socket(AF_UNIX, SOCK_STREAM, 0); if (listen_sock == -1) { perror("socket"); exit(EXIT_FAILURE); } // 清理可能存在的旧 socket 文件 unlink(SOCKET_PATH); // 2. 绑定套接字 memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1); if (bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) { perror("bind"); close(listen_sock); exit(EXIT_FAILURE); } // 3. 监听 if (listen(listen_sock, 5) == -1) { perror("listen"); close(listen_sock); exit(EXIT_FAILURE); } printf("Receiver listening on %s\n", SOCKET_PATH); // 4. 接受连接 client_len = sizeof(client_addr); conn_sock = accept(listen_sock, (struct sockaddr*)&client_addr, &client_len); if (conn_sock == -1) { perror("accept"); close(listen_sock); exit(EXIT_FAILURE); } printf("Connection accepted.\n"); // 5. 准备接收消息 iov[0].iov_base = data; iov[0].iov_len = sizeof(data) - 1; memset(&msg, 0, sizeof(msg)); msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = ctrl_buf; msg.msg_controllen = sizeof(ctrl_buf); // 6. 接收消息 data_len = recvmsg(conn_sock, &msg, 0); if (data_len == -1) { perror("recvmsg"); close(conn_sock); close(listen_sock); exit(EXIT_FAILURE); } data[data_len] = '\0'; printf("Received data: %s\n", data); // 7. 解析辅助数据 (控制消息) 以获取文件描述符 cmsg = CMSG_FIRSTHDR(&msg); if (cmsg && cmsg->cmsg_len == CMSG_LEN(sizeof(int)) && cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int)); printf("Received file descriptor: %d\n", received_fd); // 8. 使用接收到的文件描述符 lseek(received_fd, 0, SEEK_SET); // 移到文件开头 char buf[100]; ssize_t bytes_read = read(received_fd, buf, sizeof(buf) - 1); if (bytes_read > 0) { buf[bytes_read] = '\0'; printf("Read from received fd: %s", buf); } printf("Closing received file descriptor.\n"); close(received_fd); // 关闭接收到的文件描述符 } else { printf("No file descriptor received.\n"); } close(conn_sock); close(listen_sock); unlink(SOCKET_PATH); // 清理 socket 文件 printf("Receiver finished.\n"); return 0; } #endif
编译和运行:
1 2 3 4 5 6 7 8 # Terminal 1 gcc -o receiver fd_passing.c -DRECEIVER ./receiver # Terminal 2 (在 receiver 运行后) gcc -o sender fd_passing.c -DSENDER ./sender
代码解释:
该代码通过宏定义 SENDER 和 RECEIVER 编译成两个不同的程序。
**发送方 **(SENDER)
关键: 准备辅助数据(msg_control)。
使用 CMSG_SPACE(sizeof(int)) 来分配足够的控制缓冲区。
使用 CMSG_FIRSTHDR 获取第一个控制消息头指针。
设置 cmsg_level 为 SOL_SOCKET,cmsg_type 为 SCM_RIGHTS(表示传递文件描述符)。
使用 CMSG_DATA(cmsg) 获取数据部分的指针,并将 file_fd 复制进去。
调用 sendmsg 发送包含普通数据和辅助数据(文件描述符)的消息。
**接收方 **(RECEIVER)
关键: 解析接收到的辅助数据。
使用 CMSG_FIRSTHDR 获取第一个控制消息头。
检查 cmsg_level, cmsg_type, cmsg_len 是否正确。
如果正确,使用 CMSG_DATA(cmsg) 获取数据部分,并将文件描述符复制到 received_fd 变量中。
现在,接收方可以像使用自己打开的文件一样使用 received_fd。
最后关闭接收到的文件描述符和套接字。
重要提示与注意事项:
通用性: sendmsg/recvmsg 是最通用的套接字 I/O 函数,可以替代所有其他基本的发送和接收函数。
Scatter-Gather: 通过 iovec 数组,可以高效地处理不连续的数据缓冲区,减少系统调用次数。
地址处理: 对于面向连接的套接字(如 TCP),msg_name 通常为 NULL。对于无连接的(如 UDP),它用于指定目标或获取源地址。
辅助数据: 这是 sendmsg/recvmsg 独有的强大功能。正确处理辅助数据需要使用 CMSG_* 系列宏,这比较复杂但非常有用(如传递文件描述符、设置网络接口等)。
性能: 在需要处理大量数据或复杂消息结构时,sendmsg/recvmsg 可能比多次调用 send/recv 更高效。
sendmmsg/recvmmsg: 对于需要在一次系统调用中处理多个消息的高性能应用(如网络服务器),Linux 提供了这两个函数作为 sendmsg/recvmsg 的扩展。
总结:
sendmsg 和 recvmsg 是 Linux 套接字编程中最强大和灵活的 I/O 函数。它们不仅包含了其他所有基本套接字函数的功能,还引入了处理辅助数据的能力,这使得它们在高级进程间通信(如传递文件描述符)中不可或缺。虽然使用起来比 send/recv 复杂,但掌握它们对于编写高效、功能丰富的网络应用程序至关重要。