recvmsg系统调用及示例

recvmsg 函数详解

  1. 函数介绍

recvmsg 是Linux网络编程中功能最强大的接收数据函数之一。它是 recv 和 recvfrom 的增强版本,支持接收控制信息(如文件描述符、时间戳等)和分散缓冲区(scatter-gather I/O)。recvmsg 特别适用于需要接收复杂网络数据包的应用场景。

  1. 函数原型
1
2
3
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

  1. 功能

recvmsg 从套接字接收数据,并可以同时接收额外的控制信息。它支持分散缓冲区接收、接收发送者地址信息、接收辅助数据(如文件描述符传递)等功能。

  1. 参数
  • int sockfd: 套接字文件描述符

  • *struct msghdr msg: 消息头结构,描述接收缓冲区和控制信息

  • int flags: 接收标志,控制接收行为

  1. 返回值
  • 成功: 返回实际接收到的字节数

  • 连接关闭: 返回0

  • 失败: 返回-1,并设置errno

  1. 相似函数,或关联函数
  • recv: 基本接收函数

  • recvfrom: 带地址信息的接收函数

  • sendmsg: 对应的发送函数

  • read: 基本读取函数

  1. 示例代码

示例1:基础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
#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&#91;1];
char buffer&#91;1024];
char control_buffer&#91;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&#91;0].iov_base = buffer;
iov&#91;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&#91;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:分散缓冲区接收

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
#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&#91;3];
char buffer1&#91;100], buffer2&#91;100], buffer3&#91;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&#91;0].iov_base = buffer1;
iov&#91;0].iov_len = sizeof(buffer1) - 1;
iov&#91;1].iov_base = buffer2;
iov&#91;1].iov_len = sizeof(buffer2) - 1;
iov&#91;2].iov_base = buffer3;
iov&#91;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&#91;i].iov_len;
if (total_len + buf_len > (size_t)bytes_received) {
buf_len = bytes_received - total_len;
}
((char*)iov&#91;i].iov_base)&#91;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&#91;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:接收控制信息

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
#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&#91;1];
char buffer&#91;1024];
char control_buffer&#91;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,
&timestamp_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&#91;0].iov_base = buffer;
iov&#91;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&#91;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&#91;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:文件描述符传递

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
#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&#91;2]; // socket pair
struct msghdr msg;
struct iovec iov&#91;1];
char buffer&#91;256];
char control_buffer&#91;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&#91;0], sv&#91;1]);

if (fork() == 0) {
// 子进程:发送文件描述符
close(sv&#91;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&#91;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&#91;1];
struct cmsghdr *cmsg;
char send_buffer&#91;] = "File descriptor passed";
char send_control&#91;CMSG_SPACE(sizeof(int))];

// 设置消息内容
send_iov&#91;0].iov_base = send_buffer;
send_iov&#91;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&#91;1], &send_msg, 0) == -1) {
perror("sendmsg 失败");
close(temp_fd);
close(sv&#91;1]);
exit(1);
}

printf("子进程发送了消息和文件描述符\n");

// 关闭原始文件描述符
close(temp_fd);
unlink(temp_filename);
close(sv&#91;1]);

exit(0);
} else {
// 父进程:接收文件描述符
close(sv&#91;1]); // 关闭发送端

// 准备接收消息和文件描述符
memset(&msg, 0, sizeof(msg));

iov&#91;0].iov_base = buffer;
iov&#91;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&#91;0], &msg, 0);
if (bytes_received == -1) {
perror("recvmsg 失败");
close(sv&#91;0]);
return -1;
}

buffer&#91;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&#91;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&#91;read_bytes] = '\0';
printf("从传递的文件描述符读取数据: %s\n", read_buffer);
}

// 关闭接收到的文件描述符
close(received_fd);
} else {
printf("没有接收到文件描述符\n");
}

close(sv&#91;0]);

// 等待子进程结束
int status;
wait(&status);
}

return 0;
}

int main() {
return demo_recvmsg_fd_passing();
}

示例5:完整的网络服务器示例

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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
#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&#91;0].fd = server->server_fd;
server->clients&#91;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&#91;index].fd = client_fd;
server->clients&#91;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&#91;client_index].fd;
struct msghdr msg;
struct iovec iov&#91;2];
char buffer1&#91;512], buffer2&#91;512];
char control_buffer&#91;1024];
ssize_t bytes_received;

// 准备msghdr结构
memset(&msg, 0, sizeof(msg));

// 设置分散缓冲区
iov&#91;0].iov_base = buffer1;
iov&#91;0].iov_len = sizeof(buffer1) - 1;
iov&#91;1].iov_base = buffer2;
iov&#91;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&#91;1024];
full_message&#91;0] = '\0';

for (int i = 0; i < 2 && total_copied < (size_t)bytes_received; i++) {
size_t to_copy = iov&#91;i].iov_len;
if (total_copied + to_copy > (size_t)bytes_received) {
to_copy = bytes_received - total_copied;
}

strncat(full_message, (char*)iov&#91;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&#91;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&#91;0].revents & POLLIN) {
server_accept_client(server);
activity--;
}

// 检查客户端套接字
for (int i = 1; i <= server->client_count && activity > 0; i++) {
if (server->clients&#91;i].revents & POLLIN) {
if (server_handle_client_message(server, i) == -1) {
// 客户端断开连接,移除客户端
close(server->clients&#91;i].fd);
// 将最后一个客户端移到当前位置
if (i < server->client_count) {
server->clients&#91;i] = server->clients&#91;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&#91;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&#91;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&#91;1024];
ssize_t bytes = recv(client_sock, response, sizeof(response) - 1, 0);
if (bytes > 0) {
response&#91;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信号

使用注意事项

性能考虑:

缓冲区管理: 合理设置缓冲区大小避免频繁分配

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

分散缓冲区: 适当使用scatter-gather I/O提高效率

控制信息: 只在需要时启用控制信息接收

错误处理:

部分接收: 处理数据被截断的情况

连接状态: 检查连接是否正常关闭

资源清理: 及时关闭文件描述符和释放内存

安全考虑:

缓冲区溢出: 确保缓冲区大小足够且正确处理

权限检查: 验证传递的文件描述符权限

输入验证: 验证接收到的数据内容

总结

recvmsg 是Linux网络编程中最强大的接收函数,提供了:

  • 基本数据接收功能

  • 分散缓冲区接收(scatter-gather I/O)

  • 控制信息接收(时间戳、文件描述符等)

  • 地址信息接收

  • 灵活的标志控制

通过合理使用 recvmsg,可以构建高性能、功能丰富的网络应用程序。

recvmsg系统调用及示例-CSDN博客

data-ad-format="auto" data-full-width-responsive="true">