epoll简单使用

epoll是Linux提供的一种高效I/O事件通知机制,它通过epoll_create、epoll_ctl、epoll_wait三个函数实现。epoll支持LT和ET两种模式,LT模式在事件发生后可以不立即处理,而ET模式则要求立即处理。LT模式关注的是事件状态,而ET模式关注的是事件变化。本文还提供了一个简单的epoll使用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

epoll把用户关心对文件描述符上的事件放在内核里的一个事件表中,使用一个文件描述符(epoll_create的返回值)来唯一标识内核中的这个事件表。

1.int epoll_create(int size)

	创建事件表。
	size:
		事件表大小
		the size argument is ignored

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

	op:
		EPOLL_CTL_ADD
		EPOLL_CTL_MOD
		EPOLL_CTL_DEL
	
	struct epoll_event
	{
		_uint32_t events;
		epoll_data_t data;
	};

	typedef union epoll_data
	{
		void* ptr;
		int fd;
		uint32_t u32;
		uint64_t u64;
	}epoll_data_t;

注:
epoll_data_t是一个联合体,其中fd是使用最多的。不能同时使用其ptr成员和fd成员,如果要将文件描述符和用户数据关联起来,放弃使用epoll_data_t的fd成员,在ptr指向的用户数据中包含fd。

3. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

	如果检测到事件,就将所有就绪的事件从内核事件表中复制到第二个参数events指向的数组中。

4. LT、ET模式

  • LT模式:(level trigger,水平触发)

采用LT模式的文件描述符,当epoll_wait检测到有事件发生并将此事件通知给应用程序后,应用程序可以不立即处理改事件
当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通知此事件,直到该事件被处理。

  • ET模式:(edge trigger,边沿触发)
    采用ET模式的文件描述符,当epoll_wait检测到有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件
    ET模式在很大程度上降低了同一个epoll事件被重复触发的次数。
注:使用ET模式的文件描述符应该是非阻塞的。
因为在ET模式下,触发事件必须立即处理,若文件描述符是阻塞的,那么读或写操作将会因为没有后续的事件一直处于阻塞状态。

LT:高电平触发
EPOLLIN事件产生:

  • 内核中socket接收缓冲区不为空

EPOLLOUT事件产生:

  • 内核中socket发送缓冲区不满

ET:高电平->低电平触发、低电平->高电平触发
EPOLLIN事件产生:

  • 内核中socket接收缓冲区从空到不空
  • 内核中socket接收缓冲区从不空到空

EPOLLOUT事件产生:

  • 内核中socket发送缓冲区从不满到满
  • 内核中socket发送缓冲区从满到不满

5. 简单例子

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>

#define MAXLINE 10
#define SERV_PORT 8000

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    int efd, flag;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 20);

    ///////////////////////////////////////////////////////////////////////
    struct epoll_event event;
    struct epoll_event resevent[10];
    int res, len;

    efd = epoll_create(10);

    event.events = EPOLLIN | EPOLLET;     /* ET 边沿触发,默认是水平触发 */

    //event.events = EPOLLIN;
    printf("Accepting connections ...\n");
    cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
            ntohs(cliaddr.sin_port));

    flag = fcntl(connfd, F_GETFL);          /* 修改connfd为非阻塞读 */
    flag |= O_NONBLOCK;
    fcntl(connfd, F_SETFL, flag);

    event.data.fd = connfd;
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);      //将connfd加入
    while (1) {
        printf("epoll_wait begin\n");
        res = epoll_wait(efd, resevent, 10, -1);        //最多10个, 阻塞监听
        printf("epoll_wait end res %d\n", res);

        if (resevent[0].data.fd == connfd) {
            while ((len = read(connfd, buf, MAXLINE/2)) >0 )    //非阻塞读, 轮询
                write(STDOUT_FILENO, buf, len);
        }
    }

    return 0;
}
<think>我们正在回答用户关于“Linuxepoll简单使用示例代码”的请求。根据引用,我们知道epoll是Linux下高效的I/O多路复用机制。引用中提到了epoll的基本使用方法,包括创建epoll实例、添加事件等。我们还可以参考引用中的线程池示例,但用户主要需要简单epoll示例,所以我们以基础使用为主。我们将提供一个简单epoll使用示例,包括:1.创建epoll实例2.添加需要监视的文件描述符(如socket)和事件3.进入循环等待事件发生4.处理事件(如可读、可写等)注意:本示例以TCP服务器为例,监听套接字,接受连接,并使用epoll管理多个连接。代码结构如下:-创建socket,绑定,监听-创建epoll实例-将监听socket添加到epoll-进入事件循环-当事件发生时,如果是监听socket则接受新连接并加入epoll,如果是客户端连接则处理数据由于用户要求简单示例,我们只实现基本框架,不涉及复杂的错误处理和资源释放。</think> 以下是 Linux `epoll` 的简单使用示例代码及分步解释: ### 基础使用步骤 1. **创建 epoll 实例** ```c int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1"); exit(EXIT_FAILURE); } ``` 2. **添加监听事件**(以 TCP 服务器为例) ```c struct epoll_event ev; ev.events = EPOLLIN; // 监听可读事件 ev.data.fd = server_socket; // 服务器套接字 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &ev) == -1) { perror("epoll_ctl"); exit(EXIT_FAILURE); } ``` 3. **事件循环处理** ```c #define MAX_EVENTS 10 struct epoll_event events[MAX_EVENTS]; while (1) { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); // 阻塞等待 if (nfds == -1) { perror("epoll_wait"); break; } for (int i = 0; i < nfds; i++) { if (events[i].data.fd == server_socket) { // 处理新连接 accept_connection(server_socket, epoll_fd); } else { // 处理客户端数据 handle_client(events[i].data.fd); } } } ``` ### 完整示例代码 ```c #include <sys/epoll.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define PORT 8080 #define MAX_EVENTS 10 void setnonblocking(int sockfd) { int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); } void handle_client(int client_fd) { char buffer[1024]; ssize_t bytes_read = read(client_fd, buffer, sizeof(buffer)-1); if (bytes_read > 0) { buffer[bytes_read] = '\0'; printf("Received: %s\n", buffer); write(client_fd, "HTTP/1.1 200 OK\r\n\r\nHello", 25); } close(client_fd); // 简化处理:响应后立即关闭 } int main() { int server_fd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addr = { .sin_family = AF_INET, .sin_addr.s_addr = INADDR_ANY, .sin_port = htons(PORT) }; bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)); listen(server_fd, SOMAXCONN); setnonblocking(server_fd); // 非阻塞模式 int epoll_fd = epoll_create1(0); struct epoll_event ev; ev.events = EPOLLIN | EPOLLET; // 边缘触发模式 ev.data.fd = server_fd; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev); while (1) { struct epoll_event events[MAX_EVENTS]; int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); for (int i = 0; i < nfds; i++) { if (events[i].data.fd == server_fd) { // 接受所有新连接 while (1) { struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len); if (client_fd < 0) break; setnonblocking(client_fd); ev.events = EPOLLIN | EPOLLET; ev.data.fd = client_fd; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev); } } else { handle_client(events[i].data.fd); } } } close(epoll_fd); close(server_fd); return 0; } ``` ### 关键说明 1. **边缘触发(EPOLLET)** - 事件仅通知一次,需一次性处理完所有数据 - 需搭配非阻塞套接字,避免 `read()` 阻塞 2. **水平触发(默认)** - 删除 `EPOLLET` 标志即可 - 只要缓冲区有数据会持续通知 3. **性能优势** `epoll` 避免了像 `select/poll` 的 O(n) 遍历,时间复杂度为 O(1) 级别,特别适合高并发场景[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值