recvmsg 函数详解
1. 函数介绍
recvmsg
是Linux网络编程中功能最强大的接收数据函数之一。它是 recv
和 recvfrom
的增强版本,支持接收控制信息(如文件描述符、时间戳等)和分散缓冲区(scatter-gather I/O)。recvmsg
特别适用于需要接收复杂网络数据包的应用场景。
2. 函数原型
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
3. 功能
recvmsg
从套接字接收数据,并可以同时接收额外的控制信息。它支持分散缓冲区接收、接收发送者地址信息、接收辅助数据(如文件描述符传递)等功能。
4. 参数
- int sockfd: 套接字文件描述符
- *struct msghdr msg: 消息头结构,描述接收缓冲区和控制信息
- int flags: 接收标志,控制接收行为
5. 返回值
- 成功: 返回实际接收到的字节数
- 连接关闭: 返回0
- 失败: 返回-1,并设置errno
6. 相似函数,或关联函数
- recv: 基本接收函数
- recvfrom: 带地址信息的接收函数
- sendmsg: 对应的发送函数
- read: 基本读取函数
7. 示例代码
示例1:基础recvmsg使用
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
/**
* 演示recvmsg的基本使用方法
*/
int demo_recvmsg_basic() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
struct msghdr msg;
struct iovec iov[1];
char buffer[1024];
char control_buffer[1024];
ssize_t bytes_received;
printf("=== recvmsg 基本使用示例 ===\n");
// 创建TCP服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("创建服务器套接字失败");
return -1;
}
// 设置服务器地址
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(8080);
// 绑定套接字
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定套接字失败");
close(server_fd);
return -1;
}
// 监听连接
if (listen(server_fd, 1) == -1) {
perror("监听失败");
close(server_fd);
return -1;
}
printf("服务器监听在端口 8080\n");
// 在后台启动客户端(简化演示)
if (fork() == 0) {
// 客户端代码
sleep(1); // 等待服务器启动
int client_sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(client_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
const char *message = "Hello from client!";
send(client_sock, message, strlen(message), 0);
printf("客户端发送消息: %s\n", message);
}
close(client_sock);
exit(0);
}
// 接受客户端连接
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("接受连接失败");
close(server_fd);
return -1;
}
printf("客户端连接来自: %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 准备msghdr结构
memset(&msg, 0, sizeof(msg));
// 设置接收缓冲区
iov[0].iov_base = buffer;
iov[0].iov_len = sizeof(buffer) - 1;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
// 设置控制缓冲区(用于接收辅助数据)
msg.msg_control = control_buffer;
msg.msg_controllen = sizeof(control_buffer);
// 设置地址信息缓冲区
msg.msg_name = &client_addr;
msg.msg_namelen = client_len;
// 接收消息
bytes_received = recvmsg(client_fd, &msg, 0);
if (bytes_received == -1) {
perror("recvmsg 失败");
close(client_fd);
close(server_fd);
return -1;
}
buffer[bytes_received] = '\0';
printf("recvmsg 接收到 %zd 字节数据: %s\n", bytes_received, buffer);
printf("消息标志: %d\n", msg.msg_flags);
// 显示地址信息
if (msg.msg_namelen > 0) {
struct sockaddr_in *addr = (struct sockaddr_in*)msg.msg_name;
printf("发送者地址: %s:%d\n",
inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
}
close(client_fd);
close(server_fd);
return 0;
}
int main() {
return demo_recvmsg_basic();
}
示例2:分散缓冲区接收
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
/**
* 演示recvmsg的分散缓冲区接收功能
*/
int demo_recvmsg_scatter_gather() {
int server_fd, client_fd;
struct sockaddr_in server_addr;
struct msghdr msg;
struct iovec iov[3];
char buffer1[100], buffer2[100], buffer3[100];
ssize_t bytes_received;
printf("=== recvmsg 分散缓冲区接收示例 ===\n");
// 创建UDP套接字
server_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (server_fd == -1) {
perror("创建UDP套接字失败");
return -1;
}
// 设置服务器地址
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(8081);
// 绑定套接字
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定套接字失败");
close(server_fd);
return -1;
}
printf("UDP服务器监听在端口 8081\n");
// 启动UDP客户端
if (fork() == 0) {
// 客户端代码
sleep(1);
int client_sock = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8081);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
// 发送长消息
const char *long_message = "This is a very long message that will be split across multiple buffers during scatter-gather reception.";
sendto(client_sock, long_message, strlen(long_message), 0,
(struct sockaddr*)&serv_addr, sizeof(serv_addr));
printf("客户端发送长消息 (%zu 字节)\n", strlen(long_message));
close(client_sock);
exit(0);
}
// 准备分散缓冲区
memset(&msg, 0, sizeof(msg));
// 设置三个分散的缓冲区
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;
msg.msg_iov = iov;
msg.msg_iovlen = 3;
// 接收消息
bytes_received = recvmsg(server_fd, &msg, 0);
if (bytes_received == -1) {
perror("recvmsg 失败");
close(server_fd);
return -1;
}
printf("recvmsg 接收到 %zd 字节数据\n", bytes_received);
printf("消息被分散到 %d 个缓冲区\n", (int)msg.msg_iovlen);
printf("实际使用的缓冲区数: %d\n", (int)msg.msg_iovlen);
// 添加字符串终止符
size_t total_len = 0;
for (int i = 0; i < 3 && total_len < (size_t)bytes_received; i++) {
size_t buf_len = iov[i].iov_len;
if (total_len + buf_len > (size_t)bytes_received) {
buf_len = bytes_received - total_len;
}
((char*)iov[i].iov_base)[buf_len] = '\0';
total_len += buf_len;
}
printf("缓冲区1内容 (%zu 字节): %s\n", strlen(buffer1), buffer1);
printf("缓冲区2内容 (%zu 字节): %s\n", strlen(buffer2), buffer2);
printf("缓冲区3内容 (%zu 字节): %s\n", strlen(buffer3), buffer3);
// 合并显示完整消息
char full_message[512];
snprintf(full_message, sizeof(full_message), "%s%s%s", buffer1, buffer2, buffer3);
printf("完整消息: %s\n", full_message);
close(server_fd);
return 0;
}
int main() {
return demo_recvmsg_scatter_gather();
}
示例3:接收控制信息
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
/**
* 演示recvmsg接收控制信息(时间戳)
*/
int demo_recvmsg_control_data() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
struct msghdr msg;
struct iovec iov[1];
char buffer[1024];
char control_buffer[1024];
ssize_t bytes_received;
printf("=== recvmsg 控制信息接收示例 ===\n");
// 创建TCP服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("创建服务器套接字失败");
return -1;
}
// 启用时间戳选项
int timestamp_on = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_TIMESTAMP,
×tamp_on, sizeof(timestamp_on)) == -1) {
printf("警告: 无法启用时间戳选项: %s\n", strerror(errno));
}
// 设置服务器地址
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(8082);
// 绑定套接字
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定套接字失败");
close(server_fd);
return -1;
}
// 监听连接
if (listen(server_fd, 1) == -1) {
perror("监听失败");
close(server_fd);
return -1;
}
printf("带时间戳的服务器监听在端口 8082\n");
// 启动客户端
if (fork() == 0) {
// 客户端代码
sleep(1);
int client_sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8082);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(client_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
const char *message = "Message with timestamp";
send(client_sock, message, strlen(message), 0);
printf("客户端发送消息: %s\n", message);
}
close(client_sock);
exit(0);
}
// 接受客户端连接
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("接受连接失败");
close(server_fd);
return -1;
}
printf("客户端连接建立\n");
// 准备msghdr结构用于接收控制信息
memset(&msg, 0, sizeof(msg));
// 设置接收缓冲区
iov[0].iov_base = buffer;
iov[0].iov_len = sizeof(buffer) - 1;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
// 设置控制缓冲区
msg.msg_control = control_buffer;
msg.msg_controllen = sizeof(control_buffer);
// 接收消息和控制信息
bytes_received = recvmsg(client_fd, &msg, 0);
if (bytes_received == -1) {
perror("recvmsg 失败");
close(client_fd);
close(server_fd);
return -1;
}
buffer[bytes_received] = '\0';
printf("接收到消息: %s\n", buffer);
printf("接收时间戳信息:\n");
// 解析控制信息
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMP) {
struct timeval *tv = (struct timeval*)CMSG_DATA(cmsg);
printf(" 时间戳: %ld.%06ld\n", tv->tv_sec, tv->tv_usec);
// 转换为可读格式
char time_str[64];
time_t sec = tv->tv_sec;
struct tm *tm_info = localtime(&sec);
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
printf(" 可读时间: %s.%06ld\n", time_str, tv->tv_usec);
} else {
printf(" 其他控制信息: level=%d, type=%d\n",
cmsg->cmsg_level, cmsg->cmsg_type);
}
}
if (msg.msg_controllen == 0) {
printf(" 没有接收到控制信息\n");
}
close(client_fd);
close(server_fd);
return 0;
}
int main() {
return demo_recvmsg_control_data();
}
示例4:文件描述符传递
#define _GNU_SOURCE
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
/**
* 演示通过recvmsg传递文件描述符
*/
int demo_recvmsg_fd_passing() {
int sv[2]; // socket pair
struct msghdr msg;
struct iovec iov[1];
char buffer[256];
char control_buffer[CMSG_SPACE(sizeof(int))];
ssize_t bytes_received;
printf("=== recvmsg 文件描述符传递示例 ===\n");
// 创建socket pair用于进程间通信
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
perror("创建socket pair失败");
return -1;
}
printf("创建了socket pair: %d, %d\n", sv[0], sv[1]);
if (fork() == 0) {
// 子进程:发送文件描述符
close(sv[0]); // 关闭接收端
// 创建一个临时文件
const char *temp_filename = "/tmp/fd_pass_test.txt";
int temp_fd = open(temp_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (temp_fd == -1) {
perror("创建临时文件失败");
close(sv[1]);
exit(1);
}
// 写入一些数据
const char *test_data = "This data is in the passed file descriptor";
write(temp_fd, test_data, strlen(test_data));
printf("子进程创建了文件: %s\n", temp_filename);
printf("子进程准备传递文件描述符 %d\n", temp_fd);
// 准备发送消息和文件描述符
struct msghdr send_msg;
struct iovec send_iov[1];
struct cmsghdr *cmsg;
char send_buffer[] = "File descriptor passed";
char send_control[CMSG_SPACE(sizeof(int))];
// 设置消息内容
send_iov[0].iov_base = send_buffer;
send_iov[0].iov_len = strlen(send_buffer);
memset(&send_msg, 0, sizeof(send_msg));
send_msg.msg_iov = send_iov;
send_msg.msg_iovlen = 1;
send_msg.msg_control = send_control;
send_msg.msg_controllen = sizeof(send_control);
// 设置控制信息(文件描述符)
cmsg = CMSG_FIRSTHDR(&send_msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(cmsg), &temp_fd, sizeof(int));
send_msg.msg_controllen = cmsg->cmsg_len;
// 发送消息和文件描述符
if (sendmsg(sv[1], &send_msg, 0) == -1) {
perror("sendmsg 失败");
close(temp_fd);
close(sv[1]);
exit(1);
}
printf("子进程发送了消息和文件描述符\n");
// 关闭原始文件描述符
close(temp_fd);
unlink(temp_filename);
close(sv[1]);
exit(0);
} else {
// 父进程:接收文件描述符
close(sv[1]); // 关闭发送端
// 准备接收消息和文件描述符
memset(&msg, 0, sizeof(msg));
iov[0].iov_base = buffer;
iov[0].iov_len = sizeof(buffer) - 1;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = control_buffer;
msg.msg_controllen = sizeof(control_buffer);
// 接收消息和文件描述符
bytes_received = recvmsg(sv[0], &msg, 0);
if (bytes_received == -1) {
perror("recvmsg 失败");
close(sv[0]);
return -1;
}
buffer[bytes_received] = '\0';
printf("父进程接收到消息: %s\n", buffer);
// 解析接收到的文件描述符
int received_fd = -1;
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));
printf("父进程接收到文件描述符: %d\n", received_fd);
break;
}
}
if (received_fd != -1) {
// 使用接收到的文件描述符读取数据
char read_buffer[256];
lseek(received_fd, 0, SEEK_SET); // 重置文件位置
ssize_t read_bytes = read(received_fd, read_buffer, sizeof(read_buffer) - 1);
if (read_bytes > 0) {
read_buffer[read_bytes] = '\0';
printf("从传递的文件描述符读取数据: %s\n", read_buffer);
}
// 关闭接收到的文件描述符
close(received_fd);
} else {
printf("没有接收到文件描述符\n");
}
close(sv[0]);
// 等待子进程结束
int status;
wait(&status);
}
return 0;
}
int main() {
return demo_recvmsg_fd_passing();
}
示例5:完整的网络服务器示例
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#include <time.h>
/**
* 网络服务器结构
*/
typedef struct {
int server_fd;
int port;
struct pollfd *clients;
int max_clients;
int client_count;
} network_server_t;
/**
* 初始化网络服务器
*/
int server_init(network_server_t *server, int port, int max_clients) {
struct sockaddr_in server_addr;
memset(server, 0, sizeof(network_server_t));
server->port = port;
server->max_clients = max_clients;
server->client_count = 0;
// 分配客户端数组
server->clients = calloc(max_clients + 1, sizeof(struct pollfd));
if (!server->clients) {
perror("分配客户端数组失败");
return -1;
}
// 创建服务器套接字
server->server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server->server_fd == -1) {
perror("创建服务器套接字失败");
free(server->clients);
return -1;
}
// 设置套接字选项
int opt = 1;
if (setsockopt(server->server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
perror("设置套接字选项失败");
close(server->server_fd);
free(server->clients);
return -1;
}
// 设置服务器地址
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->server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定套接字失败");
close(server->server_fd);
free(server->clients);
return -1;
}
// 监听连接
if (listen(server->server_fd, 10) == -1) {
perror("监听失败");
close(server->server_fd);
free(server->clients);
return -1;
}
// 设置服务器套接字为poll监听
server->clients[0].fd = server->server_fd;
server->clients[0].events = POLLIN;
printf("网络服务器初始化完成,监听端口 %d\n", port);
return 0;
}
/**
* 接受新客户端连接
*/
int server_accept_client(network_server_t *server) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd;
client_fd = accept(server->server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("接受连接失败");
return -1;
}
if (server->client_count >= server->max_clients) {
printf("客户端数量已达上限,拒绝连接\n");
close(client_fd);
return -1;
}
// 添加到客户端数组
int index = server->client_count + 1;
server->clients[index].fd = client_fd;
server->clients[index].events = POLLIN;
server->client_count++;
printf("新客户端连接: %s:%d (fd=%d)\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client_fd);
return 0;
}
/**
* 使用recvmsg处理客户端消息
*/
int server_handle_client_message(network_server_t *server, int client_index) {
int client_fd = server->clients[client_index].fd;
struct msghdr msg;
struct iovec iov[2];
char buffer1[512], buffer2[512];
char control_buffer[1024];
ssize_t bytes_received;
// 准备msghdr结构
memset(&msg, 0, sizeof(msg));
// 设置分散缓冲区
iov[0].iov_base = buffer1;
iov[0].iov_len = sizeof(buffer1) - 1;
iov[1].iov_base = buffer2;
iov[1].iov_len = sizeof(buffer2) - 1;
msg.msg_iov = iov;
msg.msg_iovlen = 2;
// 设置控制缓冲区
msg.msg_control = control_buffer;
msg.msg_controllen = sizeof(control_buffer);
// 接收消息
bytes_received = recvmsg(client_fd, &msg, 0);
if (bytes_received == -1) {
if (errno == ECONNRESET) {
printf("客户端 %d 连接重置\n", client_fd);
} else {
perror("recvmsg 失败");
}
return -1;
}
if (bytes_received == 0) {
printf("客户端 %d 关闭连接\n", client_fd);
return -1;
}
printf("从客户端 %d 接收到 %zd 字节数据\n", client_fd, bytes_received);
// 处理接收到的数据
size_t total_copied = 0;
char full_message[1024];
full_message[0] = '\0';
for (int i = 0; i < 2 && total_copied < (size_t)bytes_received; i++) {
size_t to_copy = iov[i].iov_len;
if (total_copied + to_copy > (size_t)bytes_received) {
to_copy = bytes_received - total_copied;
}
strncat(full_message, (char*)iov[i].iov_base, to_copy);
total_copied += to_copy;
}
printf(" 消息内容: %s\n", full_message);
printf(" 使用缓冲区数: %d\n", (int)msg.msg_iovlen);
printf(" 消息标志: %d\n", msg.msg_flags);
// 回显消息
char response[1024];
snprintf(response, sizeof(response), "Echo: %s", full_message);
send(client_fd, response, strlen(response), 0);
return 0;
}
/**
* 运行服务器主循环
*/
int server_run(network_server_t *server) {
printf("服务器开始运行,等待客户端连接...\n");
while (1) {
// 使用poll等待事件
int nfds = server->client_count + 1;
int activity = poll(server->clients, nfds, 1000); // 1秒超时
if (activity == -1) {
if (errno == EINTR) continue; // 被信号中断
perror("poll 失败");
break;
}
if (activity == 0) {
// 超时,继续循环
continue;
}
// 检查服务器套接字(新连接)
if (server->clients[0].revents & POLLIN) {
server_accept_client(server);
activity--;
}
// 检查客户端套接字
for (int i = 1; i <= server->client_count && activity > 0; i++) {
if (server->clients[i].revents & POLLIN) {
if (server_handle_client_message(server, i) == -1) {
// 客户端断开连接,移除客户端
close(server->clients[i].fd);
// 将最后一个客户端移到当前位置
if (i < server->client_count) {
server->clients[i] = server->clients[server->client_count];
}
server->client_count--;
i--; // 重新检查当前位置
}
activity--;
}
}
}
return 0;
}
/**
* 清理服务器资源
*/
void server_cleanup(network_server_t *server) {
// 关闭所有客户端连接
for (int i = 1; i <= server->client_count; i++) {
close(server->clients[i].fd);
}
// 关闭服务器套接字
if (server->server_fd != -1) {
close(server->server_fd);
}
// 释放内存
if (server->clients) {
free(server->clients);
}
printf("服务器资源清理完成\n");
}
/**
* 演示完整的网络服务器
*/
int demo_complete_network_server() {
network_server_t server;
printf("=== 完整网络服务器示例 ===\n");
// 初始化服务器
if (server_init(&server, 8083, 10) != 0) {
return -1;
}
// 启动测试客户端
if (fork() == 0) {
sleep(2); // 等待服务器启动
// 创建多个客户端进行测试
for (int i = 0; i < 3; i++) {
if (fork() == 0) {
int client_sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8083);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(client_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
char message[256];
snprintf(message, sizeof(message), "Hello from client %d", i + 1);
send(client_sock, message, strlen(message), 0);
printf("客户端 %d 发送: %s\n", i + 1, message);
// 接收回显
char response[1024];
ssize_t bytes = recv(client_sock, response, sizeof(response) - 1, 0);
if (bytes > 0) {
response[bytes] = '\0';
printf("客户端 %d 接收回显: %s\n", i + 1, response);
}
sleep(1);
}
close(client_sock);
exit(0);
}
}
// 等待所有客户端完成
for (int i = 0; i < 3; i++) {
int status;
wait(&status);
}
exit(0);
}
// 运行服务器30秒
printf("服务器将运行30秒...\n");
sleep(30);
// 清理资源
server_cleanup(&server);
// 等待测试客户端结束
int status;
wait(&status);
return 0;
}
int main() {
return demo_complete_network_server();
}
recvmsg 标志参数详解
常用标志:
- MSG_OOB: 接收带外数据
- MSG_PEEK: 查看数据但不从队列中移除
- MSG_WAITALL: 等待接收完整的消息
- MSG_TRUNC: 返回数据包的实际长度(UDP)
- MSG_CTRUNC: 控制数据被截断
高级标志:
- MSG_DONTWAIT: 非阻塞操作
- MSG_ERRQUEUE: 接收错误队列中的数据
- MSG_NOSIGNAL: 接收时不产生SIGPIPE信号
使用注意事项
性能考虑:
- 缓冲区管理: 合理设置缓冲区大小避免频繁分配
- 分散缓冲区: 适当使用scatter-gather I/O提高效率
- 控制信息: 只在需要时启用控制信息接收
错误处理:
- 部分接收: 处理数据被截断的情况
- 连接状态: 检查连接是否正常关闭
- 资源清理: 及时关闭文件描述符和释放内存
安全考虑:
- 缓冲区溢出: 确保缓冲区大小足够且正确处理
- 权限检查: 验证传递的文件描述符权限
- 输入验证: 验证接收到的数据内容
总结
recvmsg
是Linux网络编程中最强大的接收函数,提供了:
- 基本数据接收功能
- 分散缓冲区接收(scatter-gather I/O)
- 控制信息接收(时间戳、文件描述符等)
- 地址信息接收
- 灵活的标志控制
通过合理使用 recvmsg
,可以构建高性能、功能丰富的网络应用程序。