ppoll 函数详解
1. 函数介绍
ppoll
是 Linux 系统中用于同时监视多个文件描述符并等待特定信号的系统调用。可以把 ppoll
想象成”多功能的等待管家”——它不仅能像 poll
一样监视文件描述符的状态变化,还能在等待期间处理特定的信号,就像一个既能看门又能接电话的管家。
与传统的 poll
函数相比,ppoll
提供了更好的信号处理机制,避免了信号处理函数中调用不可重入函数的问题。
相关文章:ppoll系统调用及示例-CSDN博客 epoll_create系统调用及示例 pselect系统调用及示例 epoll_create1系统调用及示例 poll系统调用及示例-CSDN博客
2. 函数原型
#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);
3. 功能
ppoll
函数用于同时监视多个文件描述符的状态变化,并可以选择性地阻塞指定的信号。它结合了 poll
的文件描述符监视功能和信号屏蔽功能。
4. 参数
- fds: 指向
pollfd
结构体数组的指针,描述要监视的文件描述符 - nfds:
fds
数组中的元素个数 - timeout_ts: 指向超时时间的指针(
NULL
表示无限等待) - sigmask: 指向信号屏蔽集的指针(
NULL
表示不改变信号屏蔽)
5. pollfd 结构体
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 请求的事件 */
short revents; /* 实际发生的事件 */
};
事件类型(events 和 revents 字段)
事件 | 值 | 说明 |
---|---|---|
POLLIN | 0x001 | 有数据可读 |
POLLPRI | 0x002 | 有紧急数据可读 |
POLLOUT | 0x004 | 文件描述符可写 |
POLLERR | 0x008 | 发生错误 |
POLLHUP | 0x010 | 连接挂起 |
POLLNVAL | 0x020 | 文件描述符无效 |
POLLRDNORM | 0x040 | 有普通数据可读 |
POLLRDBAND | 0x080 | 有优先数据可读 |
POLLWRNORM | 0x100 | 可以正常写入 |
POLLWRBAND | 0x200 | 可以优先写入 |
6. timespec 结构体
struct timespec {
time_t tv_sec; /* 秒数 */
long tv_nsec; /* 纳秒数 */
};
7. 返回值
- 成功: 返回准备就绪的文件描述符数量(0 表示超时)
- 失败: 返回 -1,并设置相应的 errno 错误码
8. 常见错误码
EBADF
: 一个或多个文件描述符无效EFAULT
: fds 指针无效EINTR
: 被未屏蔽的信号中断EINVAL
: 参数无效ENOMEM
: 内存不足
9. 相似函数或关联函数
- poll: 基本的文件描述符监视函数
- select: 传统的文件描述符监视函数
- pselect: 带信号屏蔽的 select
- epoll_wait: epoll 接口的等待函数
- read/write: 文件读写操作
- signal/sigaction: 信号处理函数
10. 示例代码
示例1:基础用法 – 监视标准输入和定时器
#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[2];
struct timespec timeout;
sigset_t sigmask;
int ready;
printf("=== ppoll 基础示例 ===\n\n");
// 设置信号处理
setup_signals();
// 初始化 pollfd 结构
fds[0].fd = STDIN_FILENO; // 标准输入
fds[0].events = POLLIN; // 监视可读事件
fds[0].revents = 0;
fds[1].fd = -1; // 模拟定时器文件描述符
fds[1].events = POLLIN; // 监视可读事件
fds[1].revents = 0;
// 设置超时时间 (5 秒)
timeout.tv_sec = 5;
timeout.tv_nsec = 0;
// 设置信号屏蔽集 (不屏蔽任何信号)
sigemptyset(&sigmask);
printf("监视设置:\n");
show_pollfd_status(&fds[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[0].revents & POLLIN) {
printf("标准输入有数据可读\n");
// 读取输入
char buffer[256];
ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("读取到: %s", buffer);
}
}
if (fds[0].revents & POLLERR) {
printf("标准输入发生错误\n");
}
if (fds[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:多文件描述符监视和信号处理
#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(" [%d] fd=%d events=0x%04x revents=0x%04x",
i, fds[i].fd, fds[i].events, fds[i].revents);
if (fds[i].revents != 0) {
printf(" (");
if (fds[i].revents & POLLIN) printf("IN ");
if (fds[i].revents & POLLOUT) printf("OUT ");
if (fds[i].revents & POLLPRI) printf("PRI ");
if (fds[i].revents & POLLERR) printf("ERR ");
if (fds[i].revents & POLLHUP) printf("HUP ");
if (fds[i].revents & POLLNVAL) printf("NVAL ");
printf(")");
}
printf("\n");
}
}
int main() {
struct pollfd fds[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[0].fd = STDIN_FILENO; // 标准输入
fds[0].events = POLLIN;
fds[0].revents = 0;
fds[1].fd = test_fd1; // 测试文件1
fds[1].events = POLLIN;
fds[1].revents = 0;
fds[2].fd = test_fd2; // 测试文件2
fds[2].events = POLLIN;
fds[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[i].revents & POLLIN) {
switch (i) {
case 0: // 标准输入
printf("标准输入有数据:\n");
{
char buffer[256];
ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf(" 读取到: %s", buffer);
}
}
break;
case 1: // 测试文件1
printf("测试文件1 有数据可读\n");
lseek(test_fd1, 0, SEEK_SET);
{
char buffer[128];
ssize_t bytes_read = read(test_fd1, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf(" 文件1内容: %s", buffer);
}
}
break;
case 2: // 测试文件2
printf("测试文件2 有数据可读\n");
lseek(test_fd2, 0, SEEK_SET);
{
char buffer[128];
ssize_t bytes_read = read(test_fd2, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf(" 文件2内容: %s", buffer);
}
}
break;
}
}
if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("文件描述符 %d 发生错误或异常\n", fds[i].fd);
if (fds[i].revents & POLLERR) printf(" 错误\n");
if (fds[i].revents & POLLHUP) printf(" 挂起\n");
if (fds[i].revents & POLLNVAL) printf(" 无效\n");
}
}
printf("\n");
}
// 重置 revents
for (int i = 0; i < 3; i++) {
fds[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:完整的事件驱动服务器模拟
#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[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[i].connected) {
state->clients[i].fd = client_fd;
state->clients[i].connected = 1;
state->clients[i].connect_time = time(NULL);
state->clients[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[client_index].connected) {
close(state->clients[client_index].fd);
memset(&state->clients[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[client_index];
// 模拟读取客户端数据
char buffer[256];
ssize_t bytes_read = read(client->fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[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[i].connected) {
printf(" [%d] fd=%d 连接时间: %s",
i, state->clients[i].fd,
ctime(&state->clients[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[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
fds[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[i].connected) {
fds[nfds].fd = state->clients[i].fd;
fds[nfds].events = POLLIN;
fds[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[i].revents & POLLIN) {
if (i == 0) {
// 标准输入事件
char input_buffer[256];
ssize_t bytes_read = read(STDIN_FILENO, input_buffer, sizeof(input_buffer) - 1);
if (bytes_read > 0) {
input_buffer[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[i].fd;
int client_index = -1;
// 查找对应的客户端
for (int j = 0; j < state->max_clients; j++) {
if (state->clients[j].connected &&
state->clients[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[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("文件描述符 %d 发生异常\n", fds[i].fd);
if (i > 0) {
// 客户端异常
int client_fd = fds[i].fd;
for (int j = 0; j < state->max_clients; j++) {
if (state->clients[j].connected &&
state->clients[j].fd == client_fd) {
remove_client(state, j);
break;
}
}
}
}
}
}
// 重置 revents
for (int i = 0; i < nfds; i++) {
fds[i].revents = 0;
}
}
// 清理资源
free(fds);
// 关闭所有客户端连接
for (int i = 0; i < state->max_clients; i++) {
if (state->clients[i].connected) {
remove_client(state, i);
}
}
printf("服务器模拟结束\n");
return 0;
}
// 显示帮助信息
void show_help(const char *program_name) {
printf("用法: %s [选项]\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[]) {
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[] = {
{"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[0]);
return 0;
default:
fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[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;
}
编译和运行说明
# 编译示例程序
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
系统要求检查
# 检查内核版本(需要 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 错误
- 资源清理: 及时关闭文件描述符
- 超时处理: 合理设置超时时间
与相关函数的比较
// 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 多路复用
最佳实践
// 安全的 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[i].revents & POLLIN) {
// 处理可读事件
handle_readable_fd(fds[i].fd);
}
if (fds[i].revents & POLLOUT) {
// 处理可写事件
handle_writable_fd(fds[i].fd);
}
if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
// 处理错误事件
handle_error_fd(fds[i].fd, fds[i].revents);
}
}
}
// 重置 revents
for (int i = 0; i < nfds; i++) {
fds[i].revents = 0;
}
}
return 0;
}
这些示例展示了 ppoll
函数的各种使用方法,从基础的文件描述符监视到完整的事件驱动服务器模拟,帮助你全面掌握 Linux 系统中的高级 I/O 多路复用机制。