epoll_ctl系统调用及示例

epoll_ctl – 控制epoll实例

函数介绍

epoll_ctl系统调用用于控制epoll实例,可以添加、修改或删除要监视的文件描述符。

函数原型

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

功能

对epoll实例进行控制操作,管理要监视的文件描述符。

参数

  • int epfd: epoll实例的文件描述符
  • int op: 操作类型
    • EPOLL_CTL_ADD: 添加文件描述符到监视集合
    • EPOLL_CTL_MOD: 修改已监视文件描述符的设置
    • EPOLL_CTL_DEL: 从监视集合中删除文件描述符
  • int fd: 要控制的文件描述符
  • struct epoll_event *event: 指向epoll_event结构体的指针

返回值

  • 成功时返回0
  • 失败时返回-1,并设置errno

特殊限制

  • 需要有效的epoll文件描述符
  • 文件描述符必须是有效的
  • 某些操作需要文件描述符已在监视集合中

相似函数

  • epoll_wait(): 等待epoll事件
  • poll(): 传统轮询控制

示例代码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

int main() {
    int epfd, sockfd;
    struct epoll_event ev;
    
    printf("=== Epoll_ctl 函数示例 ===\n");
    
    // 创建epoll实例
    epfd = epoll_create1(EPOLL_CLOEXEC);
    if (epfd == -1) {
        perror("epoll_create1 失败");
        exit(EXIT_FAILURE);
    }
    printf("创建epoll实例: %d\n", epfd);
    
    // 创建测试用的socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("创建socket失败");
        close(epfd);
        exit(EXIT_FAILURE);
    }
    printf("创建测试socket: %d\n", sockfd);
    
    // 设置socket为非阻塞模式
    int flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    printf("设置socket为非阻塞模式\n");
    
    // 示例1: 添加文件描述符到epoll监视集合
    printf("\n示例1: 添加文件描述符到epoll\n");
    
    ev.events = EPOLLIN | EPOLLOUT | EPOLLET; // 读事件、写事件、边缘触发
    ev.data.fd = sockfd;
    
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {
        perror("EPOLL_CTL_ADD 失败");
    } else {
        printf("成功添加socket %d 到epoll监视集合\n", sockfd);
        printf("监视事件: EPOLLIN | EPOLLOUT | EPOLLET\n");
    }
    
    // 示例2: 修改已监视的文件描述符
    printf("\n示例2: 修改已监视的文件描述符\n");
    
    ev.events = EPOLLIN | EPOLLET; // 只监视读事件和边缘触发
    ev.data.fd = sockfd;
    
    if (epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev) == -1) {
        if (errno == ENOENT) {
            printf("修改失败,文件描述符未在监视集合中: %s\n", strerror(errno));
        } else {
            perror("EPOLL_CTL_MOD 失败");
        }
    } else {
        printf("成功修改socket %d 的监视设置\n", sockfd);
        printf("新监视事件: EPOLLIN | EPOLLET\n");
    }
    
    // 示例3: 删除文件描述符
    printf("\n示例3: 删除文件描述符\n");
    
    if (epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL) == -1) {
        if (errno == ENOENT) {
            printf("删除失败,文件描述符不在监视集合中: %s\n", strerror(errno));
        } else {
            perror("EPOLL_CTL_DEL 失败");
        }
    } else {
        printf("成功从epoll监视集合中删除socket %d\n", sockfd);
    }
    
    // 示例4: 错误处理演示
    printf("\n示例4: 错误处理演示\n");
    
    // 使用无效的epoll文件描述符
    if (epoll_ctl(-1, EPOLL_CTL_ADD, sockfd, &ev) == -1) {
        if (errno == EBADF) {
            printf("无效epoll文件描述符错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用无效的操作类型
    if (epoll_ctl(epfd, 999, sockfd, &ev) == -1) {
        if (errno == EINVAL) {
            printf("无效操作类型错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用无效的文件描述符
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, -1, &ev) == -1) {
        if (errno == EBADF) {
            printf("无效文件描述符错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 示例5: epoll_event结构说明
    printf("\n示例5: epoll_event结构说明\n");
    printf("struct epoll_event 结构体:\n");
    printf("  uint32_t events;  // 事件类型\n");
    printf("  epoll_data_t data; // 用户数据\n\n");
    
    printf("常用事件类型:\n");
    printf("  EPOLLIN:    可读事件\n");
    printf("  EPOLLOUT:   可写事件\n");
    printf("  EPOLLPRI:   紧急数据可读\n");
    printf("  EPOLLERR:   错误条件\n");
    printf("  EPOLLHUP:   挂起事件\n");
    printf("  EPOLLET:    边缘触发模式\n");
    printf("  EPOLLONESHOT: 一次性事件\n");
    printf("  EPOLLRDHUP: 对端关闭连接\n\n");
    
    // 示例6: epoll_data_t联合体说明
    printf("epoll_data_t联合体(可存储不同类型的数据):\n");
    printf("  void *ptr;    // 指针\n");
    printf("  int fd;       // 文件描述符\n");
    printf("  uint32_t u32; // 32位无符号整数\n");
    printf("  uint64_t u64; // 64位无符号整数\n\n");
    
    // 演示使用用户数据
    printf("示例6: 使用用户数据\n");
    
    struct epoll_event event_with_data;
    event_with_data.events = EPOLLIN;
    event_with_data.data.fd = sockfd;
    
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event_with_data) == 0) {
        printf("使用文件描述符作为用户数据: %d\n", event_with_data.data.fd);
        
        // 删除文件描述符
        epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
    }
    
    // 示例7: 实际应用场景
    printf("\n示例7: 实际应用场景\n");
    
    printf("服务器程序中的典型使用流程:\n");
    printf("1. 创建epoll实例\n");
    printf("2. 添加监听socket到epoll\n");
    printf("3. 在事件循环中:\n");
    printf("   a. 调用epoll_wait等待事件\n");
    printf("   b. 处理就绪事件\n");
    printf("   c. 根据需要添加/修改/删除监视的文件描述符\n\n");
    
    printf("常见操作模式:\n");
    printf("监听socket: EPOLLIN | EPOLLET\n");
    printf("客户端socket: EPOLLIN | EPOLLOUT | EPOLLET\n");
    printf("一次性事件: EPOLLIN | EPOLLONESHOT\n\n");
    
    // 清理资源
    close(sockfd);
    close(epfd);
    
    printf("总结:\n");
    printf("epoll_ctl是管理epoll监视集合的核心函数\n");
    printf("支持添加、修改、删除三种基本操作\n");
    printf("正确使用事件类型和用户数据很重要\n");
    printf("需要妥善处理各种错误情况\n");
    
    return 0;
}
此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

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