ppoll系统调用及示例

ppoll 函数详解

  1. 函数介绍

ppoll 是 Linux 系统中用于同时监视多个文件描述符并等待特定信号的系统调用。可以把 ppoll 想象成”多功能的等待管家”——它不仅能像 poll 一样监视文件描述符的状态变化,还能在等待期间处理特定的信号,就像一个既能看门又能接电话的管家。

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

与传统的 poll 函数相比,ppoll 提供了更好的信号处理机制,避免了信号处理函数中调用不可重入函数的问题。

相关文章:ppoll系统调用及示例-CSDN博客 epoll_create系统调用及示例 pselect系统调用及示例 epoll_create1系统调用及示例 poll系统调用及示例-CSDN博客

  1. 函数原型
1
2
3
4
5
6
7
8
9
#define _GNU_SOURCE
#include <poll.h>
#include <signal.h>
#include <time.h>

int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts,
const sigset_t *sigmask);

  1. 功能

ppoll 函数用于同时监视多个文件描述符的状态变化,并可以选择性地阻塞指定的信号。它结合了 poll 的文件描述符监视功能和信号屏蔽功能。

  1. 参数
  • fds: 指向 pollfd 结构体数组的指针,描述要监视的文件描述符

  • nfds: fds 数组中的元素个数

  • timeout_ts: 指向超时时间的指针(NULL 表示无限等待)

  • sigmask: 指向信号屏蔽集的指针(NULL 表示不改变信号屏蔽)

  1. pollfd 结构体
1
2
3
4
5
6
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 请求的事件 */
short revents; /* 实际发生的事件 */
};

事件类型(events 和 revents 字段)

事件值说明POLLIN0x001有数据可读POLLPRI0x002有紧急数据可读POLLOUT0x004文件描述符可写POLLERR0x008发生错误POLLHUP0x010连接挂起POLLNVAL0x020文件描述符无效POLLRDNORM0x040有普通数据可读POLLRDBAND0x080有优先数据可读POLLWRNORM0x100可以正常写入POLLWRBAND0x200可以优先写入

  1. timespec 结构体
1
2
3
4
5
struct timespec {
time_t tv_sec; /* 秒数 */
long tv_nsec; /* 纳秒数 */
};

  1. 返回值
  • 成功: 返回准备就绪的文件描述符数量(0 表示超时)

  • 失败: 返回 -1,并设置相应的 errno 错误码

  1. 常见错误码
  • EBADF: 一个或多个文件描述符无效

  • EFAULT: fds 指针无效

  • EINTR: 被未屏蔽的信号中断

  • EINVAL: 参数无效

  • ENOMEM: 内存不足

  1. 相似函数或关联函数
  • poll: 基本的文件描述符监视函数

  • select: 传统的文件描述符监视函数

  • pselect: 带信号屏蔽的 select

  • epoll_wait: epoll 接口的等待函数

  • read/write: 文件读写操作

  • signal/sigaction: 信号处理函数

  1. 示例代码

示例1:基础用法 - 监视标准输入和定时器

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>

// 信号处理标志
volatile sig_atomic_t signal_received = 0;

// 信号处理函数
void signal_handler(int sig) {
signal_received = sig;
printf("\n收到信号 %d\n", sig);
}

// 设置信号处理
void setup_signals() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;

sigaction(SIGINT, &sa, NULL); // Ctrl+C
sigaction(SIGTERM, &sa, NULL); // 终止信号
sigaction(SIGALRM, &sa, NULL); // 定时器信号
}

// 显示 pollfd 状态
void show_pollfd_status(struct pollfd *pfd, const char *description) {
printf("%s: fd=%d", description, pfd->fd);
printf(" events=");
if (pfd->events & POLLIN) printf("POLLIN ");
if (pfd->events & POLLOUT) printf("POLLOUT ");
if (pfd->events & POLLPRI) printf("POLLPRI ");
printf("\n");

printf(" revents=");
if (pfd->revents & POLLIN) printf("POLLIN ");
if (pfd->revents & POLLOUT) printf("POLLOUT ");
if (pfd->revents & POLLPRI) printf("POLLPRI ");
if (pfd->revents & POLLERR) printf("POLLERR ");
if (pfd->revents & POLLHUP) printf("POLLHUP ");
if (pfd->revents & POLLNVAL) printf("POLLNVAL ");
printf("\n");
}

int main() {
struct pollfd fds&#91;2];
struct timespec timeout;
sigset_t sigmask;
int ready;

printf("=== ppoll 基础示例 ===\n\n");

// 设置信号处理
setup_signals();

// 初始化 pollfd 结构
fds&#91;0].fd = STDIN_FILENO; // 标准输入
fds&#91;0].events = POLLIN; // 监视可读事件
fds&#91;0].revents = 0;

fds&#91;1].fd = -1; // 模拟定时器文件描述符
fds&#91;1].events = POLLIN; // 监视可读事件
fds&#91;1].revents = 0;

// 设置超时时间 (5 秒)
timeout.tv_sec = 5;
timeout.tv_nsec = 0;

// 设置信号屏蔽集 (不屏蔽任何信号)
sigemptyset(&sigmask);

printf("监视设置:\n");
show_pollfd_status(&fds&#91;0], "标准输入");
printf("超时时间: %ld 秒\n", (long)timeout.tv_sec);
printf("按 Enter 键或等待超时...\n");
printf("按 Ctrl+C 发送信号\n\n");

// 使用 ppoll 监视文件描述符
ready = ppoll(fds, 1, &timeout, &sigmask);

if (ready == -1) {
if (errno == EINTR) {
printf("ppoll 被信号中断\n");
if (signal_received) {
printf("收到信号: %d\n", signal_received);
}
} else {
perror("ppoll 失败");
}
} else if (ready == 0) {
printf("超时: 没有文件描述符准备就绪\n");
} else {
printf("准备就绪的文件描述符数量: %d\n", ready);

if (fds&#91;0].revents & POLLIN) {
printf("标准输入有数据可读\n");

// 读取输入
char buffer&#91;256];
ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("读取到: %s", buffer);
}
}

if (fds&#91;0].revents & POLLERR) {
printf("标准输入发生错误\n");
}

if (fds&#91;0].revents & POLLHUP) {
printf("标准输入连接挂起\n");
}
}

printf("\n=== ppoll 特点 ===\n");
printf("1. 原子操作: 等待和信号屏蔽是原子的\n");
printf("2. 精确超时: 支持纳秒级超时\n");
printf("3. 信号安全: 避免信号处理中的竞态条件\n");
printf("4. 灵活屏蔽: 可以精确控制信号屏蔽\n");
printf("5. 高效监视: 同时监视多个文件描述符\n");

return 0;
}

示例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
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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>

// 全局变量
volatile sig_atomic_t terminate_flag = 0;
volatile sig_atomic_t alarm_count = 0;

// 信号处理函数
void termination_handler(int sig) {
printf("\n收到终止信号 %d\n", sig);
terminate_flag = 1;
}

void alarm_handler(int sig) {
alarm_count++;
printf("\n收到定时器信号 %d (计数: %d)\n", sig, alarm_count);
}

// 设置信号处理
void setup_signal_handlers() {
struct sigaction sa;

// 设置终止信号处理
sa.sa_handler = termination_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);

// 设置定时器信号处理
sa.sa_handler = alarm_handler;
sigaction(SIGALRM, &sa, NULL);
}

// 创建临时文件用于测试
int create_test_file(const char *filename) {
int fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
return -1;
}

const char *content = "这是测试文件的内容\n用于演示 ppoll 功能\n";
write(fd, content, strlen(content));
lseek(fd, 0, SEEK_SET);

printf("创建测试文件: %s\n", filename);
return fd;
}

// 显示文件描述符状态
void show_fds_status(struct pollfd *fds, int nfds) {
printf("文件描述符状态:\n");
for (int i = 0; i < nfds; i++) {
printf(" &#91;%d] fd=%d events=0x%04x revents=0x%04x",
i, fds&#91;i].fd, fds&#91;i].events, fds&#91;i].revents);

if (fds&#91;i].revents != 0) {
printf(" (");
if (fds&#91;i].revents & POLLIN) printf("IN ");
if (fds&#91;i].revents & POLLOUT) printf("OUT ");
if (fds&#91;i].revents & POLLPRI) printf("PRI ");
if (fds&#91;i].revents & POLLERR) printf("ERR ");
if (fds&#91;i].revents & POLLHUP) printf("HUP ");
if (fds&#91;i].revents & POLLNVAL) printf("NVAL ");
printf(")");
}
printf("\n");
}
}

int main() {
struct pollfd fds&#91;3];
struct timespec timeout;
sigset_t sigmask;
int test_fd1, test_fd2;
int ready;
int running = 1;

printf("=== ppoll 多文件描述符监视示例 ===\n\n");

// 设置信号处理
setup_signal_handlers();

// 创建测试文件
test_fd1 = create_test_file("test1.txt");
test_fd2 = create_test_file("test2.txt");

if (test_fd1 == -1 || test_fd2 == -1) {
if (test_fd1 != -1) close(test_fd1);
if (test_fd2 != -1) close(test_fd2);
unlink("test1.txt");
unlink("test2.txt");
return 1;
}

// 初始化 pollfd 结构
fds&#91;0].fd = STDIN_FILENO; // 标准输入
fds&#91;0].events = POLLIN;
fds&#91;0].revents = 0;

fds&#91;1].fd = test_fd1; // 测试文件1
fds&#91;1].events = POLLIN;
fds&#91;1].revents = 0;

fds&#91;2].fd = test_fd2; // 测试文件2
fds&#91;2].events = POLLIN;
fds&#91;2].revents = 0;

// 设置超时时间 (3 秒)
timeout.tv_sec = 3;
timeout.tv_nsec = 0;

// 设置信号屏蔽集 (不屏蔽任何信号)
sigemptyset(&sigmask);

printf("监视设置完成:\n");
show_fds_status(fds, 3);
printf("超时时间: %ld 秒\n", (long)timeout.tv_sec);
printf("按 Enter 键测试标准输入\n");
printf("按 Ctrl+C 终止程序\n\n");

// 启动定时器
alarm(2); // 2 秒后发送 SIGALRM

// 主循环
while (running && !terminate_flag) {
printf("等待事件... (按 Ctrl+C 退出)\n");

ready = ppoll(fds, 3, &timeout, &sigmask);

if (ready == -1) {
if (errno == EINTR) {
printf("ppoll 被信号中断\n");
if (alarm_count > 0) {
printf("定时器触发次数: %d\n", alarm_count);
alarm(2); // 重新启动定时器
}
continue;
} else {
perror("ppoll 失败");
break;
}
} else if (ready == 0) {
printf("超时: 没有文件描述符准备就绪\n");
alarm(2); // 重新启动定时器
} else {
printf("准备就绪的文件描述符数量: %d\n", ready);
show_fds_status(fds, 3);

// 处理各个文件描述符
for (int i = 0; i < 3; i++) {
if (fds&#91;i].revents & POLLIN) {
switch (i) {
case 0: // 标准输入
printf("标准输入有数据:\n");
{
char buffer&#91;256];
ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf(" 读取到: %s", buffer);
}
}
break;

case 1: // 测试文件1
printf("测试文件1 有数据可读\n");
lseek(test_fd1, 0, SEEK_SET);
{
char buffer&#91;128];
ssize_t bytes_read = read(test_fd1, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf(" 文件1内容: %s", buffer);
}
}
break;

case 2: // 测试文件2
printf("测试文件2 有数据可读\n");
lseek(test_fd2, 0, SEEK_SET);
{
char buffer&#91;128];
ssize_t bytes_read = read(test_fd2, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf(" 文件2内容: %s", buffer);
}
}
break;
}
}

if (fds&#91;i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("文件描述符 %d 发生错误或异常\n", fds&#91;i].fd);
if (fds&#91;i].revents & POLLERR) printf(" 错误\n");
if (fds&#91;i].revents & POLLHUP) printf(" 挂起\n");
if (fds&#91;i].revents & POLLNVAL) printf(" 无效\n");
}
}

printf("\n");
}

// 重置 revents
for (int i = 0; i < 3; i++) {
fds&#91;i].revents = 0;
}
}

// 清理资源
printf("\n清理资源...\n");
close(test_fd1);
close(test_fd2);
unlink("test1.txt");
unlink("test2.txt");

printf("程序正常退出\n");

printf("\n=== ppoll 高级特性 ===\n");
printf("1. 原子操作: 等待和信号处理是原子的\n");
printf("2. 精确控制: 可以精确指定信号屏蔽\n");
printf("3. 灵活超时: 支持纳秒级超时控制\n");
printf("4. 多路复用: 同时监视多个文件描述符\n");
printf("5. 事件驱动: 基于事件的通知机制\n");
printf("\n");
printf("优势对比:\n");
printf(" select: 有文件描述符数量限制\n");
printf(" poll: 无文件描述符数量限制\n");
printf(" ppoll: 增强的信号处理能力\n");
printf(" epoll: 更高的性能 (Linux 特有)\n");

return 0;
}

示例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
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
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <getopt.h>

// 服务器配置结构体
struct server_config {
int port;
int max_clients;
int timeout_seconds;
int verbose;
int use_signals;
char *log_file;
};

// 客户端信息结构体
struct client_info {
int fd;
int connected;
time_t connect_time;
char buffer&#91;1024];
size_t buffer_pos;
};

// 服务器状态结构体
struct server_state {
int running;
int client_count;
struct client_info *clients;
int max_clients;
FILE *log_fp;
};

// 全局变量
volatile sig_atomic_t server_terminate = 0;
volatile sig_atomic_t server_reload = 0;

// 信号处理函数
void signal_handler(int sig) {
switch (sig) {
case SIGINT:
case SIGTERM:
printf("\n收到终止信号,准备关闭服务器...\n");
server_terminate = 1;
break;
case SIGHUP:
printf("\n收到重载信号,重新加载配置...\n");
server_reload = 1;
break;
case SIGUSR1:
printf("\n收到用户信号 1\n");
break;
case SIGUSR2:
printf("\n收到用户信号 2\n");
break;
}
}

// 设置信号处理
void setup_server_signals() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;

sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);
}

// 初始化服务器状态
int init_server_state(struct server_state *state, int max_clients) {
state->running = 1;
state->client_count = 0;
state->max_clients = max_clients;

state->clients = calloc(max_clients, sizeof(struct client_info));
if (!state->clients) {
perror("分配客户端数组失败");
return -1;
}

state->log_fp = stderr; // 默认输出到标准错误
return 0;
}

// 添加客户端
int add_client(struct server_state *state, int client_fd) {
if (state->client_count >= state->max_clients) {
fprintf(stderr, "客户端数量已达上限: %d\n", state->max_clients);
return -1;
}

for (int i = 0; i < state->max_clients; i++) {
if (!state->clients&#91;i].connected) {
state->clients&#91;i].fd = client_fd;
state->clients&#91;i].connected = 1;
state->clients&#91;i].connect_time = time(NULL);
state->clients&#91;i].buffer_pos = 0;
state->client_count++;

printf("添加客户端 %d (fd: %d),当前客户端数: %d\n",
i, client_fd, state->client_count);
return i;
}
}

fprintf(stderr, "找不到空闲的客户端槽位\n");
return -1;
}

// 移除客户端
void remove_client(struct server_state *state, int client_index) {
if (client_index >= 0 && client_index < state->max_clients &&
state->clients&#91;client_index].connected) {

close(state->clients&#91;client_index].fd);
memset(&state->clients&#91;client_index], 0, sizeof(struct client_info));
state->client_count--;

printf("移除客户端 %d,剩余客户端数: %d\n",
client_index, state->client_count);
}
}

// 处理客户端数据
void handle_client_data(struct server_state *state, int client_index) {
struct client_info *client = &state->clients&#91;client_index];

// 模拟读取客户端数据
char buffer&#91;256];
ssize_t bytes_read = read(client->fd, buffer, sizeof(buffer) - 1);

if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("客户端 %d 发送数据: %s", client_index, buffer);

// 回显数据
write(client->fd, "Echo: ", 6);
write(client->fd, buffer, bytes_read);

} else if (bytes_read == 0) {
printf("客户端 %d 断开连接\n", client_index);
remove_client(state, client_index);
} else {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("读取客户端数据失败");
remove_client(state, client_index);
}
}
}

// 显示服务器状态
void show_server_status(const struct server_state *state) {
printf("=== 服务器状态 ===\n");
printf("运行状态: %s\n", state->running ? "运行中" : "已停止");
printf("客户端数量: %d/%d\n", state->client_count, state->max_clients);
printf("当前时间: %s", ctime(&(time_t){time(NULL)}));

if (state->client_count > 0) {
printf("连接的客户端:\n");
for (int i = 0; i < state->max_clients; i++) {
if (state->clients&#91;i].connected) {
printf(" &#91;%d] fd=%d 连接时间: %s",
i, state->clients&#91;i].fd,
ctime(&state->clients&#91;i].connect_time));
}
}
}
printf("\n");
}

// 模拟服务器主循环
int run_server_simulation(struct server_state *state, const struct server_config *config) {
struct pollfd *fds = NULL;
struct timespec timeout;
sigset_t sigmask;
int ready;

printf("启动服务器模拟...\n");
printf("最大客户端数: %d\n", config->max_clients);
printf("超时时间: %d 秒\n", config->timeout_seconds);
printf("使用信号处理: %s\n", config->use_signals ? "是" : "否");
printf("\n");

// 分配 pollfd 数组 (1个用于标准输入 + 最大客户端数)
fds = calloc(1 + config->max_clients, sizeof(struct pollfd));
if (!fds) {
perror("分配 pollfd 数组失败");
return -1;
}

// 初始化标准输入监视
fds&#91;0].fd = STDIN_FILENO;
fds&#91;0].events = POLLIN;
fds&#91;0].revents = 0;

// 设置超时时间
timeout.tv_sec = config->timeout_seconds;
timeout.tv_nsec = 0;

// 设置信号屏蔽集
sigemptyset(&sigmask);
if (!config->use_signals) {
// 如果不使用信号,可以屏蔽一些信号
sigaddset(&sigmask, SIGUSR1);
sigaddset(&sigmask, SIGUSR2);
}

// 显示初始状态
show_server_status(state);

// 主循环
while (state->running && !server_terminate) {
// 更新 pollfd 数组
int nfds = 1; // 至少包含标准输入

// 添加连接的客户端
for (int i = 0; i < state->max_clients; i++) {
if (state->clients&#91;i].connected) {
fds&#91;nfds].fd = state->clients&#91;i].fd;
fds&#91;nfds].events = POLLIN;
fds&#91;nfds].revents = 0;
nfds++;
}
}

if (config->verbose) {
printf("监视 %d 个文件描述符...\n", nfds);
}

// 使用 ppoll 等待事件
ready = ppoll(fds, nfds, &timeout, &sigmask);

if (ready == -1) {
if (errno == EINTR) {
if (config->verbose) {
printf("ppoll 被信号中断\n");
}

if (server_reload) {
printf("重新加载配置...\n");
server_reload = 0;
}

continue;
} else {
perror("ppoll 失败");
break;
}
} else if (ready == 0) {
if (config->verbose) {
printf("超时: 没有事件发生\n");
}
} else {
if (config->verbose) {
printf("准备就绪的文件描述符数量: %d\n", ready);
}

// 处理事件
for (int i = 0; i < nfds; i++) {
if (fds&#91;i].revents & POLLIN) {
if (i == 0) {
// 标准输入事件
char input_buffer&#91;256];
ssize_t bytes_read = read(STDIN_FILENO, input_buffer, sizeof(input_buffer) - 1);
if (bytes_read > 0) {
input_buffer&#91;bytes_read] = '\0';

// 处理特殊命令
if (strncmp(input_buffer, "quit", 4) == 0 ||
strncmp(input_buffer, "exit", 4) == 0) {
printf("收到退出命令\n");
state->running = 0;
server_terminate = 1;
} else if (strncmp(input_buffer, "status", 6) == 0) {
show_server_status(state);
} else if (strncmp(input_buffer, "help", 4) == 0) {
printf("可用命令:\n");
printf(" quit/exit - 退出服务器\n");
printf(" status - 显示服务器状态\n");
printf(" help - 显示帮助信息\n");
printf(" Ctrl+C - 发送终止信号\n");
} else {
printf("收到输入: %s", input_buffer);

// 模拟添加客户端
if (strncmp(input_buffer, "connect", 7) == 0) {
if (state->client_count < state->max_clients) {
// 模拟创建客户端连接
int fake_fd = 1000 + state->client_count;
int client_index = add_client(state, fake_fd);
if (client_index != -1) {
printf("模拟客户端连接成功 (fd: %d)\n", fake_fd);
}
} else {
printf("客户端数量已达上限\n");
}
}
}
}
} else {
// 客户端事件
int client_fd = fds&#91;i].fd;
int client_index = -1;

// 查找对应的客户端
for (int j = 0; j < state->max_clients; j++) {
if (state->clients&#91;j].connected &&
state->clients&#91;j].fd == client_fd) {
client_index = j;
break;
}
}

if (client_index != -1) {
handle_client_data(state, client_index);
} else {
printf("未知客户端事件 (fd: %d)\n", client_fd);
}
}
}

if (fds&#91;i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("文件描述符 %d 发生异常\n", fds&#91;i].fd);

if (i > 0) {
// 客户端异常
int client_fd = fds&#91;i].fd;
for (int j = 0; j < state->max_clients; j++) {
if (state->clients&#91;j].connected &&
state->clients&#91;j].fd == client_fd) {
remove_client(state, j);
break;
}
}
}
}
}
}

// 重置 revents
for (int i = 0; i < nfds; i++) {
fds&#91;i].revents = 0;
}
}

// 清理资源
free(fds);

// 关闭所有客户端连接
for (int i = 0; i < state->max_clients; i++) {
if (state->clients&#91;i].connected) {
remove_client(state, i);
}
}

printf("服务器模拟结束\n");
return 0;
}

// 显示帮助信息
void show_help(const char *program_name) {
printf("用法: %s &#91;选项]\n", program_name);
printf("\n选项:\n");
printf(" -p, --port=PORT 监听端口 (默认 8080)\n");
printf(" -c, --clients=NUM 最大客户端数 (默认 10)\n");
printf(" -t, --timeout=SECONDS 超时时间 (默认 30 秒)\n");
printf(" -v, --verbose 详细输出\n");
printf(" -s, --signals 启用信号处理\n");
printf(" -l, --log=FILE 日志文件\n");
printf(" -h, --help 显示此帮助信息\n");
printf("\n示例:\n");
printf(" %s # 使用默认设置运行\n", program_name);
printf(" %s -c 20 -t 60 -v # 20个客户端,60秒超时,详细输出\n", program_name);
printf(" %s -s -l server.log # 启用信号处理,记录日志\n", program_name);
printf(" %s --help # 显示帮助信息\n", program_name);
}

int main(int argc, char *argv&#91;]) {
struct server_config config = {
.port = 8080,
.max_clients = 10,
.timeout_seconds = 30,
.verbose = 0,
.use_signals = 0,
.log_file = NULL
};

struct server_state server_state_struct;

printf("=== ppoll 事件驱动服务器模拟器 ===\n\n");

// 解析命令行参数
static struct option long_options&#91;] = {
{"port", required_argument, 0, 'p'},
{"clients", required_argument, 0, 'c'},
{"timeout", required_argument, 0, 't'},
{"verbose", no_argument, 0, 'v'},
{"signals", no_argument, 0, 's'},
{"log", required_argument, 0, 'l'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};

int opt;
while ((opt = getopt_long(argc, argv, "p:c:t:vsl:h", long_options, NULL)) != -1) {
switch (opt) {
case 'p':
config.port = atoi(optarg);
break;
case 'c':
config.max_clients = atoi(optarg);
if (config.max_clients <= 0) config.max_clients = 10;
break;
case 't':
config.timeout_seconds = atoi(optarg);
if (config.timeout_seconds <= 0) config.timeout_seconds = 30;
break;
case 'v':
config.verbose = 1;
break;
case 's':
config.use_signals = 1;
break;
case 'l':
config.log_file = optarg;
break;
case 'h':
show_help(argv&#91;0]);
return 0;
default:
fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv&#91;0]);
return 1;
}
}

// 设置信号处理
if (config.use_signals) {
setup_server_signals();
printf("✓ 信号处理已启用\n");
}

// 初始化服务器状态
if (init_server_state(&server_state_struct, config.max_clients) == -1) {
return 1;
}

// 设置日志文件
if (config.log_file) {
server_state_struct.log_fp = fopen(config.log_file, "a");
if (server_state_struct.log_fp) {
printf("✓ 日志文件: %s\n", config.log_file);
} else {
perror("打开日志文件失败");
config.log_file = NULL;
server_state_struct.log_fp = stderr;
}
}

printf("服务器配置:\n");
printf(" 监听端口: %d\n", config.port);
printf(" 最大客户端数: %d\n", config.max_clients);
printf(" 超时时间: %d 秒\n", config.timeout_seconds);
printf(" 详细输出: %s\n", config.verbose ? "是" : "否");
printf(" 信号处理: %s\n", config.use_signals ? "是" : "否");
printf(" 日志文件: %s\n", config.log_file ? config.log_file : "标准错误");
printf("\n");

printf("启动服务器模拟...\n");
printf("可用命令:\n");
printf(" 输入 'status' 查看服务器状态\n");
printf(" 输入 'connect' 模拟客户端连接\n");
printf(" 输入 'quit' 或 'exit' 退出服务器\n");
printf(" 输入 'help' 显示帮助信息\n");
printf(" 按 Ctrl+C 发送终止信号\n");
printf("\n");

// 运行服务器模拟
int result = run_server_simulation(&server_state_struct, &config);

// 清理资源
if (server_state_struct.clients) {
free(server_state_struct.clients);
}

if (config.log_file && server_state_struct.log_fp != stderr) {
fclose(server_state_struct.log_fp);
}

printf("\n=== ppoll 服务器应用说明 ===\n");
printf("核心技术:\n");
printf("1. 多路复用: 同时监视多个文件描述符\n");
printf("2. 事件驱动: 基于事件的通知机制\n");
printf("3. 信号安全: 原子的信号处理\n");
printf("4. 超时控制: 精确的超时管理\n");
printf("5. 资源管理: 动态的客户端管理\n");
printf("\n");
printf("ppoll 优势:\n");
printf("1. 原子性: 等待和信号处理是原子操作\n");
printf("2. 灵活性: 可以精确控制信号屏蔽\n");
printf("3. 精确性: 纳秒级超时控制\n");
printf("4. 可扩展: 无文件描述符数量限制\n");
printf("5. 安全性: 避免信号处理中的竞态条件\n");
printf("\n");
printf("实际应用场景:\n");
printf("1. 网络服务器: HTTP/WebSocket 服务器\n");
printf("2. 代理服务: 反向代理、负载均衡\n");
printf("3. 实时应用: 游戏服务器、聊天应用\n");
printf("4. 监控系统: 系统监控、日志收集\n");
printf("5. 数据处理: 流数据处理、ETL 系统\n");

return result;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 编译示例程序
gcc -o ppoll_example1 example1.c
gcc -o ppoll_example2 example2.c
gcc -o ppoll_example3 example3.c

# 运行示例
./ppoll_example1
./ppoll_example2
./ppoll_example3 --help
./ppoll_example3 -c 5 -t 10 -v -s

# 测试信号处理
./ppoll_example3 -s &
PID=$!
sleep 2
kill -USR1 $PID
sleep 2
kill -TERM $PID

系统要求检查

1
2
3
4
5
6
7
8
9
10
11
12
# 检查内核版本(需要 2.6.16+)
uname -r

# 检查 glibc 版本(需要 2.4+)
ldd --version

# 检查 ppoll 系统调用支持
grep -w ppoll /usr/include/asm/unistd_64.h

# 查看系统调用表
cat /proc/kallsyms | grep ppoll

重要注意事项

内核版本: 需要 Linux 2.6.16+ 内核支持

glibc 版本: 需要 glibc 2.4+ 支持

编译标志: 需要定义 _GNU_SOURCE

错误处理: 始终检查返回值和 errno

信号安全: 正确处理 EINTR 错误

资源清理: 及时关闭文件描述符

超时处理: 合理设置超时时间

与相关函数的比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// select - 传统的文件描述符监视
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);

// poll - 基本的文件描述符监视
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

// ppoll - 增强的文件描述符监视(带信号处理)
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts,
const sigset_t *sigmask);

// epoll - Linux 特有的高性能接口
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);

实际应用场景

网络服务器: HTTP、WebSocket、TCP 服务器

代理服务: 反向代理、负载均衡器

实时应用: 游戏服务器、聊天应用

监控系统: 系统监控、日志收集

数据处理: 流数据处理、ETL 系统

文件系统: 文件监控、备份系统

设备驱动: 设备事件处理、I/O 多路复用

最佳实践

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
// 安全的 ppoll 封装函数
int safe_ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts,
const sigset_t *sigmask) {
// 验证参数
if (!fds || nfds == 0) {
errno = EINVAL;
return -1;
}

// 执行 ppoll
int result = ppoll(fds, nfds, timeout_ts, sigmask);

// 处理常见错误
if (result == -1) {
switch (errno) {
case EINTR:
// 被信号中断是正常的
break;
case EBADF:
fprintf(stderr, "错误: 包含无效的文件描述符\n");
break;
case EFAULT:
fprintf(stderr, "错误: 参数指针无效\n");
break;
case EINVAL:
fprintf(stderr, "错误: 参数无效\n");
break;
}
}

return result;
}

// 事件处理循环模板
int event_loop_template(struct pollfd *fds, int nfds, int timeout_seconds) {
struct timespec timeout;
sigset_t sigmask;

// 设置超时
timeout.tv_sec = timeout_seconds;
timeout.tv_nsec = 0;

// 设置信号屏蔽
sigemptyset(&sigmask);

while (1) {
int ready = safe_ppoll(fds, nfds, &timeout, &sigmask);

if (ready == -1) {
if (errno == EINTR) {
// 被信号中断,继续循环
continue;
} else {
perror("ppoll 失败");
return -1;
}
} else if (ready == 0) {
// 超时处理
printf("超时: 没有事件发生\n");
continue;
} else {
// 处理准备就绪的文件描述符
for (int i = 0; i < nfds; i++) {
if (fds&#91;i].revents & POLLIN) {
// 处理可读事件
handle_readable_fd(fds&#91;i].fd);
}
if (fds&#91;i].revents & POLLOUT) {
// 处理可写事件
handle_writable_fd(fds&#91;i].fd);
}
if (fds&#91;i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
// 处理错误事件
handle_error_fd(fds&#91;i].fd, fds&#91;i].revents);
}
}
}

// 重置 revents
for (int i = 0; i < nfds; i++) {
fds&#91;i].revents = 0;
}
}

return 0;
}

这些示例展示了 ppoll 函数的各种使用方法,从基础的文件描述符监视到完整的事件驱动服务器模拟,帮助你全面掌握 Linux 系统中的高级 I/O 多路复用机制。

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