epoll_ctl系统调用及示例

epoll_ctl - 控制epoll实例

函数介绍

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

函数原型

1
2
3
4
#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: 要控制的文件描述符

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

struct epoll_event *event: 指向epoll_event结构体的指针

返回值

  • 成功时返回0

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

特殊限制

  • 需要有效的epoll文件描述符

  • 文件描述符必须是有效的

  • 某些操作需要文件描述符已在监视集合中

相似函数

  • epoll_wait(): 等待epoll事件

  • poll(): 传统轮询控制

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#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;
}

https://www.calcguide.tech/2025/08/09/epoll_ctl系统调用及示例/

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