epoll使用实例说明

本文详细介绍Epoll机制,并通过示例代码展示如何利用Epoll高效处理大量并发连接,包括epoll_create、epoll_ctl及epoll_wait等核心函数的使用。

之前一直在讲如何epoll如何好用,但是并没有实例来演示epoll的使用,下面我们就看一个服务器端使用epoll监听大量并发链接的例子。
首先看一下epoll的几个函数的介绍。
1、epoll_create函数

/** 
 * @brief    该函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。
 * 
 * @param    size    size就是你在这个epoll fd上能关注的最大socket fd数
 * 
 * @return   生成的文件描述符
 */

int epoll_create(int size);

2、epoll_ctl函数

/** 
 * @brief    该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。
 * 
 * @param    epfd    由 epoll_create 生成的epoll专用的文件描述符
 * @param    op      要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除
 * @param    fd      关联的文件描述符
 * @param    event   指向epoll_event的指针
 * 
 * @return   0       succ
 *           -1      fail
 */

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

其中用到的数据结构结构如下:

typedef union epoll_data { 
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t; 
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};

常用的事件类型:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 表示对应的文件描述符有事件发生;

例:

struct epoll_event ev;
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);

3、epoll_wait函数

/** 
 * @brief    该函数用于轮询I/O事件的发生
 * 
 * @param    epfd        由epoll_create 生成的epoll专用的文件描述符
 * @param    events      用于回传代处理事件的数组
 * @param    maxevents   每次能处理的事件数
 * @param    timeout     等待I/O事件发生的超时值;-1相当于阻塞,0相当于非阻塞。一般用-1即可
 * 
 * @return   >=0         返回发生事件数
 *           -1          错误
 */

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

好了,其实在epoll的使用中无非就用到了上面介绍的几个函数,下面贴一段用epoll实现的服务器代码:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/resource.h>
#define MAXBUF 1024
#define MAXEPOLLSIZE 10000
/*
   setnonblocking - 设置句柄为非阻塞方式
   */

int setnonblocking(int sockfd)
{
    if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1)
    {
        return -1;
    }
    return 0;
}
/*
   handle_message - 处理每个 socket 上的消息收发
   */

int handle_message(int new_fd)
{
    char buf[MAXBUF + 1];
    int len;
    /* 开始处理每个新连接上的数据收发 */
    bzero(buf, MAXBUF + 1);
    /* 接收客户端的消息 */
    len = recv(new_fd, buf, MAXBUF, 0);
    if (len > 0)
    {
        printf("%d接收消息成功:'%s',共%d个字节的数据\n",
             new_fd, buf, len);
    }
    else
    {
        if (len < 0)
            printf
                ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
                 errno, strerror(errno));
        close(new_fd);
        return -1;
    }
    /* 处理每个新连接上的数据收发结束 */
    return len;
}
int main(int argc, char **argv)
{
    int listener, new_fd, kdpfd, nfds, n, ret, curfds;
    socklen_t len;
    struct sockaddr_in my_addr, their_addr;
    unsigned int myport, lisnum;
    struct epoll_event ev;
    struct epoll_event events[MAXEPOLLSIZE];
    struct rlimit rt;
    myport = 5000;
    lisnum = 2
    /* 设置每个进程允许打开的最大文件数 */
    rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
    if (setrlimit(RLIMIT_NOFILE, &rt) == -1
    {
        perror("setrlimit");
        exit(1);
    }
    else 
    {
        printf("设置系统资源参数成功!\n");
    }
    /* 开启 socket 监听 */
    if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    else
    {
        printf("socket 创建成功!\n");
    }
    setnonblocking(listener);
    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = PF_INET;
    my_addr.sin_port = htons(myport);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(listener, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1
    {
        perror("bind");
        exit(1);
    } 
    else
    {
        printf("IP 地址和端口绑定成功\n");
    }
    if (listen(listener, lisnum) == -1
    {
        perror("listen");
        exit(1);
    }
    else
    {
        printf("开启服务成功!\n");
    }
    /* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */
    kdpfd = epoll_create(MAXEPOLLSIZE);
    len = sizeof(struct sockaddr_in);
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = listener;
    if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0
    {
        fprintf(stderr, "epoll set insertion error: fd=%d\n", listener);
        return -1;
    }
    else
    {
        printf("监听 socket 加入 epoll 成功!\n");
    }
    curfds = 1;
    while (1
    {
        /* 等待有事件发生 */
        nfds = epoll_wait(kdpfd, events, curfds, -1);
        if (nfds == -1)
        {
            perror("epoll_wait");
            break;
        }
        /* 处理所有事件 */
        for (n = 0; n < nfds; ++n)
        {
            if (events[n].data.fd == listener) 
            {
                new_fd = accept(listener, (struct sockaddr *) &their_addr,&len);
                if (new_fd < 0
                {
                    perror("accept");
                    continue;
                } 
                else
                {
                    printf("有连接来自于: %d:%d, 分配的 socket 为:%d\n",
                            inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);
                }
                setnonblocking(new_fd);
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = new_fd;
                if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_fd, &ev) < 0)
                {
                    fprintf(stderr, "把 socket '%d' 加入 epoll 失败!%s\n",
                            new_fd, strerror(errno));
                    return -1;
                }
                curfds++;
            } 
            else
            {
                ret = handle_message(events[n].data.fd);
                if (ret < 1 && errno != 11)
                {
                    epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,&ev);
                    curfds--;
                }
            }
        }
    }
    close(listener);
    return 0;
}

 

本文出处: http://www.vimer.cn/2009/11/epoll%e4%bd%bf%e7%94%a8%e5%ae%9e%e4%be%8b%e8%af%b4%e6%98%8e.html
<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、付费专栏及课程。

余额充值