ppoll系统调用及示例

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 结构体数组的指针,描述要监视的文件描述符
  • nfdsfds 数组中的元素个数
  • timeout_ts: 指向超时时间的指针(NULL 表示无限等待)
  • sigmask: 指向信号屏蔽集的指针(NULL 表示不改变信号屏蔽)

5. pollfd 结构体

struct pollfd {
    int   fd;         /* 文件描述符 */
    short events;     /* 请求的事件 */
    short revents;    /* 实际发生的事件 */
};

事件类型(events 和 revents 字段)

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

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 多路复用机制。

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注