`
san_yun
  • 浏览: 2663743 次
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

Linux Epoll介绍和程序实例

 
阅读更多

1. Epoll 是何方神圣?

Epoll 可是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll Linux2.6 内核中正式引入,和 select 相似,其实都 I/O 多路复用技术而已 ,并没有什么神秘的。

 

其实在 Linux 下设计并发网络程序,向来不缺少方法,比如典型的 Apache 模型( Process Per Connection ,简称 PPC ), TPC Thread Per Connection )模型,以及 select 模型和 poll 模型,那为何还要再引入 Epoll 这个东东呢?那还是有得说说的

 

2. 常用模型的缺点

如果不摆出来其他模型的缺点,怎么能对比出 Epoll 的优点呢。

 

2.1 PPC/TPC 模型

这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我 。只是 PPC 是为它开了一个进程,而 TPC 开了一个线程。可是别烦我是有代价的,它要时间和空间啊,连接多了之后,那么多的进程 / 线程切换,这开销就上来了;因此这类模型能接受的最大连接数都不会高,一般在几百个左右。

 

2.2 select 模型

1. 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Select 模型的最大并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧

2. 效率问题, select 每次调用都会线性扫描全部的 FD 集合,这样效率就会呈现线性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了??!!

3. 内核 / 用户空间 内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了内存拷贝方法。

 

2.3 poll 模型

基本上效率和 select 是相同的, select 缺点的 2 3 它都没有改掉。


3. Epoll 的提升

把其他模型逐个批判了一下,再来看看 Epoll 的改进之处吧,其实把 select 的缺点反过来那就是 Epoll 的优点了。

3.1. Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 察看。

3.2. 效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll 的效率就会远远高于 select poll

3.3. 内存拷贝, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。

 

 

4. Epoll 为什么高效

Epoll 的高效和其数据结构的设计是密不可分的,这个下面就会提到。

首先回忆一下 select 模型,当有 I/O 事件到来时, select 通知应用程序有事件到了快去处理,而应用程序必须轮询所有的 FD 集合,测试每个 FD 是否有事件发生,并处理事件;代码像下面这样:

 

int res = select(maxfd+1, &readfds, NULL, NULL, 120);

if (res > 0)

{

    for (int i = 0; i < MAX_CONNECTION; i++)

    {

        if (FD_ISSET(allConnection[i], &readfds))

        {

            handleEvent(allConnection[i]);

        }

    }

}

// if(res == 0) handle timeout, res < 0 handle error 
 

Epoll 不仅会告诉应用程序有I/0 事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个FD 集合。

 

int res = epoll_wait(epfd, events, 20, 120);

for (int i = 0; i < res;i++)

{

    handleEvent(events[n]);

} 
 

5. Epoll 关键数据结构

前面提到 Epoll 速度快和其数据结构密不可分,其关键数据结构就是:

 

struct epoll_event {

    __uint32_t events;      // Epoll events

    epoll_data_t data;      // User data variable

};

typedef union epoll_data {

    void *ptr;

    int fd;

    __uint32_t u32;

    __uint64_t u64;

} epoll_data_t; 

 

可见 epoll_data 是一个 union 结构体 , 借助于它应用程序可以保存很多类型的信息 :fd 、指针等等。有了它,应用程序就可以直接定位目标了。

6. 使用 Epoll

既然 Epoll 相比 select 这么好,那么用起来如何呢?会不会很繁琐啊 先看看下面的三个函数吧,就知道 Epoll 的易用了。

 

int epoll_create(int size);

 

生成一个 Epoll 专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的 socket fd 上是否发生以及发生了什么事件。 size 就是你在这个 Epoll fd 上能关注的最大 socket fd 数,大小自定,只要内存足够。

 

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

 

控制某个 Epoll 文件描述符上的事件:注册、修改、删除。其中参数 epfd epoll_create() 创建 Epoll 专用的文件描述符。相对于 select 模型中的 FD_SET FD_CLR 宏。

 

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

 

控制某个 Epoll 文件描述符上的事件:注册、修改、删除。其中参数 epfd epoll_create() 创建 Epoll 专用的文件描述符。相对于 select 模型中的 FD_SET FD_CLR 宏。

 

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

 

等待 I/O 事件的发生;参数说明:

epfd: epoll_create() 生成的 Epoll 专用的文件描述符;

epoll_event: 用于回传代处理事件的数组;

maxevents: 每次能处理的事件数;

timeout: 等待 I/O 事件发生的超时值;

返回发生事件数。

相对于 select 模型中的 select 函数。

 

7. 例子程序

下面是一个简单 Echo Server 的例子程序,麻雀虽小,五脏俱全,还包含了一个简单的超时检查机制,简洁起见没有做错误处理。

 

// 
// a simple echo server using epoll in linux
// 
// 2009-11-05
// by sparkling
// 
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <iostream>
using namespace std;
#define MAX_EVENTS 500
struct myevent_s
{
    int fd;
    void (*call_back)(int fd, int events, void *arg);
    int events;
    void *arg;
    int status; // 1: in epoll wait list, 0 not in
    char buff[128]; // recv data buffer
    int len;
    long last_active; // last active time
};
// set event
void EventSet(myevent_s *ev, int fd, void (*call_back)(int, int, void*), void *arg)
{
    ev->fd = fd;
    ev->call_back = call_back;
    ev->events = 0;
    ev->arg = arg;
    ev->status = 0;
    ev->last_active = time(NULL);
}
// add/mod an event to epoll
void EventAdd(int epollFd, int events, myevent_s *ev)
{
    struct epoll_event epv = {0, {0}};
    int op;
    epv.data.ptr = ev;
    epv.events = ev->events = events;
    if(ev->status == 1){
        op = EPOLL_CTL_MOD;
    }
    else{
        op = EPOLL_CTL_ADD;
        ev->status = 1;
    }
    if(epoll_ctl(epollFd, op, ev->fd, &epv) < 0)
        printf("Event Add failed[fd=%d]/n", ev->fd);
    else
        printf("Event Add OK[fd=%d]/n", ev->fd);
}
// delete an event from epoll
void EventDel(int epollFd, myevent_s *ev)
{
    struct epoll_event epv = {0, {0}};
    if(ev->status != 1) return;
    epv.data.ptr = ev;
    ev->status = 0;
    epoll_ctl(epollFd, EPOLL_CTL_DEL, ev->fd, &epv);
}
int g_epollFd;
myevent_s g_Events[MAX_EVENTS+1]; // g_Events[MAX_EVENTS] is used by listen fd
void RecvData(int fd, int events, void *arg);
void SendData(int fd, int events, void *arg);
// accept new connections from clients
void AcceptConn(int fd, int events, void *arg)
{
    struct sockaddr_in sin;
    socklen_t len = sizeof(struct sockaddr_in);
    int nfd, i;
    // accept
    if((nfd = accept(fd, (struct sockaddr*)&sin, &len)) == -1)
    {
        if(errno != EAGAIN && errno != EINTR)
        {
            printf("%s: bad accept", __func__);
        }
        return;
    }
    do
    {
        for(i = 0; i < MAX_EVENTS; i++)
        {
            if(g_Events[i].status == 0)
            {
                break;
            }
        }
        if(i == MAX_EVENTS)
        {
            printf("%s:max connection limit[%d].", __func__, MAX_EVENTS);
            break;
        }
        // set nonblocking
        if(fcntl(nfd, F_SETFL, O_NONBLOCK) < 0) break;
        // add a read event for receive data
        EventSet(&g_Events[i], nfd, RecvData, &g_Events[i]);
        EventAdd(g_epollFd, EPOLLIN|EPOLLET, &g_Events[i]);
        printf("new conn[%s:%d][time:%d]/n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), g_Events[i].last_active);
    }while(0);
}
// receive data
void RecvData(int fd, int events, void *arg)
{
    struct myevent_s *ev = (struct myevent_s*)arg;
    int len;
    // receive data
    len = recv(fd, ev->buff, sizeof(ev->buff)-1, 0); 	
    EventDel(g_epollFd, ev);
    if(len > 0)
    {
        ev->len = len;
        ev->buff[len] = '/0';
        printf("C[%d]:%s/n", fd, ev->buff);
        // change to send event
        EventSet(ev, fd, SendData, ev);
        EventAdd(g_epollFd, EPOLLOUT|EPOLLET, ev);
    }
    else if(len == 0)
    {
        close(ev->fd);
        printf("[fd=%d] closed gracefully./n", fd);
    }
    else
    {
        close(ev->fd);
        printf("recv[fd=%d] error[%d]:%s/n", fd, errno, strerror(errno));
    }
}
// send data
void SendData(int fd, int events, void *arg)
{
    struct myevent_s *ev = (struct myevent_s*)arg;
    int len;
    // send data
    len = send(fd, ev->buff, ev->len, 0);
    ev->len = 0;
    EventDel(g_epollFd, ev);
    if(len > 0)
    {
        // change to receive event
        EventSet(ev, fd, RecvData, ev);
        EventAdd(g_epollFd, EPOLLIN|EPOLLET, ev);
    }
    else
    {
        close(ev->fd);
        printf("recv[fd=%d] error[%d]/n", fd, errno);
    }
}
void InitListenSocket(int epollFd, short port)
{
    int listenFd = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(listenFd, F_SETFL, O_NONBLOCK); // set non-blocking
    printf("server listen fd=%d/n", listenFd);
    EventSet(&g_Events[MAX_EVENTS], listenFd, AcceptConn, &g_Events[MAX_EVENTS]);
    // add listen socket
    EventAdd(epollFd, EPOLLIN|EPOLLET, &g_Events[MAX_EVENTS]);
    // bind & listen
    sockaddr_in sin;
    bzero(&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;
    sin.sin_port = htons(port);
    bind(listenFd, (const sockaddr*)&sin, sizeof(sin));
    listen(listenFd, 5);
}
int main(int argc, char **argv)
{
    short port = 12345; // default port
    if(argc == 2){
        port = atoi(argv[1]);
    }
    // create epoll
    g_epollFd = epoll_create(MAX_EVENTS);
    if(g_epollFd <= 0) printf("create epoll failed.%d/n", g_epollFd);
    // create & bind listen socket, and add to epoll, set non-blocking
    InitListenSocket(g_epollFd, port);
    // event loop
    struct epoll_event events[MAX_EVENTS];
    printf("server running:port[%d]/n", port);
    int checkPos = 0;
    while(1){
        // a simple timeout check here, every time 100, better to use a mini-heap, and add timer event
        long now = time(NULL);
        for(int i = 0; i < 100; i++, checkPos++) // doesn't check listen fd
        {
            if(checkPos == MAX_EVENTS) checkPos = 0; // recycle
            if(g_Events[checkPos].status != 1) continue;
            long duration = now - g_Events[checkPos].last_active;
            if(duration >= 60) // 60s timeout
            {
                close(g_Events[checkPos].fd);
                printf("[fd=%d] timeout[%d--%d]./n", g_Events[checkPos].fd, g_Events[checkPos].last_active, now);
                EventDel(g_epollFd, &g_Events[checkPos]);
            }
        }
        // wait for events to happen
        int fds = epoll_wait(g_epollFd, events, MAX_EVENTS, 1000);
        if(fds < 0){
            printf("epoll_wait error, exit/n");
            break;
        }
        for(int i = 0; i < fds; i++){
            myevent_s *ev = (struct myevent_s*)events[i].data.ptr;
            if((events[i].events&EPOLLIN)&&(ev->events&EPOLLIN)) // read event
            {
                ev->call_back(ev->fd, events[i].events, ev->arg);
            }
            if((events[i].events&EPOLLOUT)&&(ev->events&EPOLLOUT)) // write event
            {
                ev->call_back(ev->fd, events[i].events, ev->arg);
            }
        }
    }
    // free resource
    return 0;
}&nbsp;
 

 

 

 

分享到:
评论

相关推荐

    Linux_Epoll介绍和程序实例

    ### Linux Epoll 介绍与程序实例详解 #### 一、Epoll 的背景及引入原因 在探讨 Epoll 之前,我们需要了解 Linux 并发网络编程的一些常见模型及其不足之处,以此来理解 Epoll 引入的原因。 ##### 1.1 PPC 和 TPC ...

    linux下的epoll服务程序实例

    在Linux操作系统中,`epoll`是用于...通过理解和实践这个实例,你可以更好地掌握`epoll`的用法,提升在Linux环境下编写高并发服务程序的能力。记得检查`EPollCodeSource`中的代码,这将加深你对`epoll`实际应用的理解。

    linux下Epoll模型实例代码

    这个"linux下Epoll模型实例代码"是一个展示如何在Linux环境下使用Epoll进行I/O事件监控的程序示例。 Epoll的核心概念包括以下几个方面: 1. **Epoll创建**:首先,通过调用`epoll_create()`函数创建一个Epoll实例...

    linux epoll多线程编程 例子

    《MyLinuxThread.txt》文件可能包含了一个关于如何在Linux上使用epoll和线程池进行编程的实例,这将有助于加深对这一主题的理解。 总结来说,Linux的epoll多线程编程能够有效地提升高并发场景下的系统性能,通过...

    linux下epoll示例程序

    本示例程序旨在演示如何在Linux环境下使用`epoll`来构建一个支持多人聊天的服务器和客户端应用。下面将详细解释`epoll`的工作原理及其在`EpollServer.cpp`和`EpollClient.cpp`中的实现。 **epoll** 是 Linux 提供的...

    linux epoll 例子程序

    Linux的epoll是一种I/O多路...总之,`linux epoll 例子程序`提供了学习和理解epoll机制的实践平台,通过对这些代码的学习,我们可以深入理解epoll如何提升服务器处理并发能力,并掌握在实际项目中如何运用这一技术。

    linux epoll socket UDP通信的实现! 看清楚不是tcp哟.zip

    3. **设置epoll**:调用`epoll_create()`创建一个`epoll`实例,然后使用`epoll_ctl()`将刚才创建的UDP套接字添加到`epoll`实例中,设置为监听读事件。 4. **接收数据**:在主循环中,调用`epoll_wait()`阻塞等待事件...

    Linux Epoll 编程实例

    ### Linux Epoll编程实例解析 #### 一、Epoll简介 `epoll` 是 Linux 内核中的一个 I/O 复用技术,用于高效地管理大量并发连接。它通过使用事件驱动模型,允许应用程序监听多个文件描述符上的事件,如读写事件。与...

    linux epoll 的实现

    Linux下的epoll是一种高效、可扩展的I/O多路...通过`epoll_create()`, `epoll_ctl()`, 和 `epoll_wait()`的组合使用,可以构建出高性能的服务器程序。在`epoll.cpp`代码中,我们可以深入学习如何在实践中应用这些概念。

    linux epoll服务器

    本文将详细解析如何利用epoll在Linux系统下构建TCP服务器,以及涉及的相关类和文件。 首先,`epoll`是Linux内核提供的一种I/O事件通知机制,相较于传统的`poll`和`select`,它具有更高的性能和更低的延迟,尤其适用...

    linux epoll代码

    2. `epoll_ctl`函数:用于管理`epoll`实例中的文件描述符,包括`EPOLL_CTL_ADD`(添加)、`EPOLL_CTL_MOD`(修改)和`EPOLL_CTL_DEL`(删除)操作。 3. `epoll_wait`函数:阻塞等待,直到有文件描述符准备就绪。...

    linux epoll 代码例子

    在Linux系统中,epoll是一种I/O事件通知机制,它被设计用来替代传统的select和poll函数,以提高在高并发环境下的性能。epoll的主要优点在于它的效率和可扩展性,使得它成为处理大量并发连接的理想选择,尤其适用于...

    Windows完成端口与Linux epoll技术简介.doc

    下面我们将详细介绍 Windows 完成端口和 Linux epoll 技术的基本概念、特点、工作原理和实例代码。 Windows 完成端口 Windows 完成端口是一种高性能的 I/O 模型,允许开发者在 Windows 平台上开发出高性能的网络...

    基于Linux Epoll机制用电信息采集系统的设计.pdf

    总结来说,基于Linux Epoll机制的用电信息采集系统利用了现代信息技术,实现了用电信息的实时采集和高效处理,对于优化电力资源配置、提高用电效率和保障电网安全具有重要意义。此外,该系统也为未来的智能电网建设...

    linux下的高并发处理select 和epoll

    在Linux系统中,处理高并发I/O事件时,select和epoll是两种常见的技术。本文将详细介绍这两种技术,以及它们在处理大量并发连接时的特点和优势。 首先,我们来看看`select`函数。`select`是一种古老的I/O多路复用...

    linux epoll

    在Linux系统中,`epoll`是用于I/O多路复用的一种高效机制,它极大地改进了旧有的`poll`和`select`方法。`epoll`的主要优势在于其基于事件驱动的回调机制,能够更好地处理大量并发连接,并且具有低延迟、高效率的特性...

    Linux C++ epoll使用范例

    本资源提供的"Linux C++ epoll使用范例"包含了客户端、服务端以及一个测试程序,旨在帮助开发者更好地理解和运用`epoll`。 一、epoll介绍 `epoll`是Linux内核为解决旧有的`select`和`poll`方法在处理大量文件描述符...

    linux 下 通过epoll实现tcp服务器

    本文将详细介绍如何利用`epoll`来实现一个TCP服务器,并结合提供的文件`tcp_epoll_server.c`和`tcp_epoll_server.h`来探讨相关知识点。 首先,`epoll`的工作原理是基于事件驱动的,它提供了一个接口,可以监控多个...

    Linux操作系统下epoll反应堆代码

    通过这个压缩包中的代码,你可以深入理解epoll如何工作,如何在实际应用中设置和管理epoll实例,以及如何在服务器和客户端之间进行通信。这对你学习和掌握Linux下的网络编程和高性能服务器设计非常有帮助。

Global site tag (gtag.js) - Google Analytics