深入浅出:Linux man epoll操作手册 (linux man epoll)
简介
在 Linux 中,epoll 是一个非常高效的 I/O 多路复用机制。它可以监测多个文件描述符,一旦其中某个文件描述符由事件(读写等)发生,就会触发相应的回调函数,非常适用于高并发网络编程。本篇文章将围绕 epoll 的使用方法、常用参数以及代码实例进行详细介绍,旨在帮助读者更好地掌握这一重要的网络编程技术。
epoll 的使用方法
使用 epoll 首先需要调用 `epoll_create` 函数创建一个 epoll 对象,其函数原型如下:
“`c
int epoll_create(int size);
“`
`size` 参数指定 epoll 可以监听的文件描述符的更大数量,实际上并不会限制 epoll 监听的数量,仅仅用于给内核分配相应的资源。函数返回的是 epoll 对象的文件描述符(非负整数),在后续的操作中需要用到它。
创建了 epoll 对象之后,就需要将需要监听的文件描述符添加到 epoll 对象中,使用 `epoll_ctl` 函数来完成添加、删除等工作,其函数原型如下:
“`c
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
“`
其中,`epfd` 是 epoll 对象的文件描述符,`op` 参数指定 epoll 的操作类型,`fd` 参数是要监听的文件描述符,`event` 参数描述了需要监听的事件类型。`op` 的取值如下:
– `EPOLL_CTL_ADD`:将 `fd` 添加到监听事件的列表中。
– `EPOLL_CTL_MOD`:修改已经添加的 `fd` 的监听事件类型。
– `EPOLL_CTL_DEL`:将 `fd` 从监听事件列表中删除。
下面是一个使用 epoll 监听网络事件的代码示例:
“`c
#include
#include
#include
#define MAX_EVENTS 10
#define PORT 6666
int mn() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(PORT);
bind(sockfd, (const struct sockaddr *) &addr, sizeof(addr));
listen(sockfd, 5);
int epollfd = epoll_create(1);
struct epoll_event event;
event.data.fd = sockfd;
event.events = EPOLLIN;
epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &event);
while (1) {
struct epoll_event events[MAX_EVENTS];
int n = epoll_wt(epollfd, events, MAX_EVENTS, -1);
for (int i = 0; i
if (events[i].data.fd == sockfd) {
// 有新连接
int client_fd = accept(sockfd, NULL, NULL);
event.data.fd = client_fd;
event.events = EPOLLIN;
epoll_ctl(epollfd, EPOLL_CTL_ADD, client_fd, &event);
} else {
// 有数据可读
char buf[1024];
int len = recv(events[i].data.fd, buf, sizeof(buf), 0);
if (len == -1 || len == 0) {
// 连接已断开
epoll_ctl(epollfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
close(events[i].data.fd);
continue;
}
// 处理数据
// …
}
}
}
}
“`
本示例中使用了一个无限循环来持续监听网络事件。使用 `epoll_wt` 函数等待事件发生,并将事件保存在 `struct epoll_event` 数组中,函数原型如下:
“`c
int epoll_wt(int epfd, struct epoll_event *events, int maxevents, int timeout);
“`
各参数含义如下:
– `epfd`:epoll 对象的文件描述符。
– `events`:用于存储事件的数组。
– `maxevents`:events 数组最多能存储的事件数量。
– `timeout`:等待的超时时间,单位是毫秒,取值为 -1 表示永远等待,取值为 0 表示立即返回。
常用参数
epoll 的常用参数有如下几种:
– `EPOLLIN`:表示对应的文件描述符可以读取(包括对端关闭连接)。
– `EPOLLOUT`:表示对应的文件描述符可以写入。
– `EPOLLRDHUP`:表示对端关闭连接,会触发该事件,但是仅在添加了 `EPOLLIN` 的情况下才会生效。
– `EPOLLPRI`:表示有紧急数据需要读取。
– `EPOLLERR`:表示发生错误。
– `EPOLLHUP`:表示连接已经关闭。
– `EPOLLET`:表示以边缘触发的方式监听事件。
代码实例
下面是一个使用 epoll 实现的简易网络服务器示例:
“`python
import socket
import select
IP_ADDR = ‘127.0.0.1’
PORT = 6666
BUFFER_SIZE = 1024
sockfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sockfd.bind((IP_ADDR, PORT))
sockfd.listen(5)
epollfd = select.epoll()
epollfd.register(sockfd.fileno(), select.EPOLLIN)
addr_dict = {}
while True:
events = epollfd.poll(1)
for fileno, event in events:
if fileno == sockfd.fileno():
connfd, addr = sockfd.accept()
epollfd.register(connfd.fileno(), select.EPOLLIN | select.EPOLLET)
addr_dict[connfd.fileno()] = addr
print(f”New connection: {addr[0]}:{addr[1]}”)
else:
data = b”
while True:
try:
chunk = socket.recv(fileno, BUFFER_SIZE)
if not chunk:
break
data += chunk
except ConnectionAbortedError:
break
if data:
print(f”[{addr_dict[fileno][0]}:{addr_dict[fileno][1]}] {data.decode()}”, end=”)
else:
epollfd.unregister(fileno)
socket.close(fileno)
print(f”Disconnect: {addr_dict[fileno][0]}:{addr_dict[fileno][1]}”)
del addr_dict[fileno]
“`
本示例中使用了 `select.epoll()` 函数创建了一个 epoll 对象,`epollfd.register()` 对监听事件进行了注册,`epollfd.poll()` 用于获取事件。使用 `socket.recv()` 函数从 established 状态的 socket 上读取数据(非阻塞),注意这里 must be a non-blocking socket。如果读取到数据,就将其打印出来,如果没有读取到数据(客户端已断开连接),就进行释放资源的操作。