深入浅出: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。如果读取到数据,就将其打印出来,如果没有读取到数据(客户端已断开连接),就进行释放资源的操作。


数据运维技术 » 深入浅出:Linux man epoll操作手册 (linux man epoll)