ppoll 函数详解 見出しへのリンク

1. 函数介绍 見出しへのリンク

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

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

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

重要注意事项 見出しへのリンク

  1. 内核版本: 需要 Linux 2.6.16+ 内核支持
  2. glibc 版本: 需要 glibc 2.4+ 支持
  3. 编译标志: 需要定义 _GNU_SOURCE
  4. 错误处理: 始终检查返回值和 errno
  5. 信号安全: 正确处理 EINTR 错误
  6. 资源清理: 及时关闭文件描述符
  7. 超时处理: 合理设置超时时间

与相关函数的比较 見出しへのリンク

// 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);

实际应用场景 見出しへのリンク

  1. 网络服务器: HTTP、WebSocket、TCP 服务器
  2. 代理服务: 反向代理、负载均衡器
  3. 实时应用: 游戏服务器、聊天应用
  4. 监控系统: 系统监控、日志收集
  5. 数据处理: 流数据处理、ETL 系统
  6. 文件系统: 文件监控、备份系统
  7. 设备驱动: 设备事件处理、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 多路复用机制。