wiki:

Epoll优点;

Epoll工作流程;

Epoll实现机制:

  epollevent;

Epoll源码分析;

Epoll接口:

  epoll_create;

  epoll_ctl;

  epoll_close;

Epoll工作方式:

  LT(level-triggered);

  ET(edge-triggered);

Epoll应用模式;

Epoll优点:

<1>支持一个进程打开大数目的socket描述符(FD)

select一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

<2>IO效率不随FD数目增加而线性下降

epoll只会对"活跃"的socket进行操 作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。同时对于监听的fd很多,但是活跃的fd很少的情况下epoll相比select也有很高的效率。

<3>使用mmap加速内核与用户空间的消息传递。

无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。

<4>内核微调

这一点其实不算 epoll 的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协 议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手 的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网 卡驱动架构。

<5>与select相比,不复用监听的文件描述集合来传递结果

这样不需要每次等待前对文件描述符集合重新赋值。

Epoll工作流程:

Epoll实现机制:

epoll fd有一个私有的struct eventpoll,它记录哪一个fd注册到了epfd上。eventpoll 同样有一个等待队列,记录所有等待的线程。还有一个预备好的fd列表,这些fd可以进行读或写。相关内核实现代码fs/eventpoll.c,判断是否tcp有激活事件吗:net/ipv4/tcp.c:tcp_poll函数;

struct eventpoll {

/* Protect the access to this structure */

spinlock_t lock;

/*

* This mutex is used to ensure that files are not removed

* while epoll is using them. This is held during the event

* collection loop, the file cleanup path, the epoll file exit

* code and the ctl operations.

*/

struct mutex mtx;

/* Wait queue used by sys_epoll_wait() */

wait_queue_head_t wq;

/* Wait queue used by file->poll() */

wait_queue_head_t poll_wait;

/* List of ready file descriptors */

struct list_head rdllist;//调用epoll_wait的时候,将readylist中的epitem出列,将触发的事件拷贝到用户空间.之后判断epitem是否需要重新添加回readylist.

/* RB tree root used to store monitored fd structs */

struct rb_root rbr;//红黑树的根,一个fd被添加到epoll中之后(EPOLL_ADD),内核会为它生成一个对应的epitem结构对象.epitem被添加到rbr中。该结构保存了epoll监视的文件描述符。

/*

* This is a single linked list that chains all the "struct epitem" that

* happened while transferring ready events to userspace w/out

* holding ->lock.

*/

struct epitem *ovflist;

/* The user that created the eventpoll descriptor */

struct user_struct *user;

};

epitem重新添加到readylist必须满足下列条件:

1) epitem上有用户关注的事件触发.

2) epitem被设置为水平触发模式(如果一个epitem被设置为边界触发则这个epitem不会被重新添加到readylist

注意,如果epitem被设置为EPOLLONESHOT模式,则当这个epitem上的事件拷贝到用户空间之后,会将

这个epitem上的关注事件清空(只是关注事件被清空,并没有从epoll中删除,要删除必须对那个描述符调用

EPOLL_DEL),也就是说即使这个epitem上有触发事件,但是因为没有用户关注的事件所以不会被重新添加到

readylist中.

epitem被添加到readylist中的各种情况(当一个epitem被添加到readylist如果有线程阻塞在epoll_wait中,那

个线程会被唤醒):

1)对一个fd调用EPOLL_ADD,如果这个fd上有用户关注的激活事件,则这个fd会被添加到readylist.

2)对一个fd调用EPOLL_MOD改变关注的事件,如果新增加了一个关注事件且对应的fd上有相应的事件激活,

则这个fd会被添加到readylist.

3)当一个fd上有事件触发时(例如一个socket上有外来的数据)会调用ep_poll_callback(见eventpoll::ep_ptable_queue_proc),

如果触发的事件是用户关注的事件,则这个fd会被添加到readylist中.

了解了epoll的执行过程之后,可以回答一个在使用边界触发时常见的疑问.在一个fd被设置为边界触发的情况下,

调用read/write,如何正确的判断那个fd已经没有数据可读/不再可写.epoll文档中的建议是直到触发EAGAIN

错误.而实际上只要你请求字节数小于read/write的返回值就可以确定那个fd上已经没有数据可读/不再可写.

最后用一个epollfd监听另一个epollfd也是合法的,epoll通过调用eventpoll::ep_eventpoll_poll来判断一个

epollfd上是否有触发的事件(只能是读事件).

Epoll源码分析:

涉及linux模块的编写;

<<Epoll源码分析.doc>>

Epoll module:

static int __init eventpoll_init(void){

//模块初始化函数

}

eventpoll_init函数源码

static int __init eventpoll_init(void)

{

int error;

init_MUTEX(&epsem);

/* Initialize the structure used to perform safe poll wait head wake ups */

ep_poll_safewake_init(&psw);

/* Allocates slab cache used to allocate "struct epitem" items */

epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),

0, SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC,

NULL, NULL);

/* Allocates slab cache used to allocate "struct eppoll_entry" */

pwq_cache = kmem_cache_create("eventpoll_pwq",

sizeof(struct eppoll_entry), 0,

EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL);

/*

* Register the virtual file system that will be the source of inodes

* for the eventpoll files

*/

/*注册了一个新的文件系统,叫"eventpollfs"(在eventpoll_fs_type结构里),然后挂载此文件系统*/

error = register_filesystem(&eventpoll_fs_type);

if (error)

goto epanic;

/* Mount the above commented virtual file system */

eventpoll_mnt = kern_mount(&eventpoll_fs_type);

error = PTR_ERR(eventpoll_mnt);

if (IS_ERR(eventpoll_mnt))

goto epanic;

DNPRINTK(3, (KERN_INFO "[%p] eventpoll: successfully initialized.\n",

current));

return 0;

epanic:

panic("eventpoll_init() failed\n");

}

epoll是个module,所以先看看module的入口eventpoll_init。这个module在初始化时注册了一个新的文件系统,叫"eventpollfs"(在eventpoll_fs_type结构里),然后挂载此文件系统。另外创建两个内核cache(在内核编程中,如果需要频繁分配小块内存,应该创建kmem_cahe来做“内存池”),分别用于存放struct epitem和eppoll_entry。

Epoll的接口:

epoll是Linux内核为处理大批句柄而作改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。因为它会复用文件描述符集合来传递结果而不是迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一个原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select\poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提供应用程序的效率。

1.工作函数

1>.int epoll_create(int size);

创建一个epoll的句柄,size用来告诉内核这个监听的数目fd+1,每个epoll都会占用一个fd值,可以在/proc/进程id/fd/查看。记得close()。

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

epoll的事件注册函数,epoll的控制函数;

这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fd到epfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:从epfd中删除一个fd;

第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

typedef union epoll_data {

void *ptr;//数据指针

int fd;/*descriptor*/

__uint32_t u32;

__uint64_t u64;

} epoll_data_t;

struct epoll_event {

__uint32_t events; /* Epoll events type */

epoll_data_t data; /* User data variable */

};

epoll_event->data涵盖了调用epoll_ctl增加或者修改某指定句柄时写入的信息,epoll_event->event,则包含了返回事件的位域。

events可以是以下几个宏的集合:

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

EPOLLOUT:表示对应的文件描述符可以写;

EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

EPOLLERR:表示对应的文件描述符发生错误;

EPOLLHUP:表示对应的文件描述符被挂断;

EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

enum EPOLL_EVENTS

{

EPOLLIN = 0x001,

#define EPOLLIN EPOLLIN

EPOLLPRI = 0x002,

#define EPOLLPRI EPOLLPRI

EPOLLOUT = 0x004,

#define EPOLLOUT EPOLLOUT

EPOLLRDNORM = 0x040,

#define EPOLLRDNORM EPOLLRDNORM

EPOLLRDBAND = 0x080,

#define EPOLLRDBAND EPOLLRDBAND

EPOLLWRNORM = 0x100,

#define EPOLLWRNORM EPOLLWRNORM

EPOLLWRBAND = 0x200,

#define EPOLLWRBAND EPOLLWRBAND

EPOLLMSG = 0x400,

#define EPOLLMSG EPOLLMSG

EPOLLERR = 0x008,

#define EPOLLERR EPOLLERR

EPOLLHUP = 0x010,

#define EPOLLHUP EPOLLHUP

EPOLLRDHUP = 0x2000,

#define EPOLLRDHUP EPOLLRDHUP

EPOLLWAKEUP = 1u << 29,

#define EPOLLWAKEUP EPOLLWAKEUP

EPOLLONESHOT = 1u << 30,

#define EPOLLONESHOT EPOLLONESHOT

EPOLLET = 1u << 31

#define EPOLLET EPOLLET

};

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

等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

工作方式:

LT/ET:

LT(level triggered):水平触发,缺省方式,同时支持block和no-block socket,在这种做法中,内核告诉我们一个文件描述符是否被就绪了,如果就绪了,你就可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性较小。传统的select\poll都是这种模型的代表。

ET(edge-triggered):边沿触发,高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪状态时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如:你在发送、接受或者接受请求,或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fs做IO操作(从而导致它再次变成未就绪状态),内核不会发送更多的通知。

应用模式:

那么究竟如何来使用epoll呢?其实非常简单。

通过在包含一个头文件#include <sys/epoll.h> 以及几个简单的API将可以大大的提高你的网络服务器的支持人数。

首先通过create_epoll(int maxfds)来创建一个epoll的句柄,其中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。

之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。基本的语法为:

nfds = epoll_wait(kdpfd, events, maxevents, -1);

其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件范围,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则范围。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。

epoll_wait范围之后应该是一个循环,遍利所有的事件。

几乎所有的epoll程序都使用下面的框架(尤其是socket):

for( ; ; )

{

nfds = epoll_wait(epfd,events,20,500);

for(i=0;i<nfds;++i)

{

if(events[i].data.fd==listenfd) //有新的连接;我们可以注册多个FD,如果内核发现事件,就会载入events,如果有我们要的描述符也就是listenfd,说明某某套接字监听描述符所对应的事件发生了变化。每次最多监测20个fd数。

{

connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接

ev.data.fd=connfd;

ev.events=EPOLLIN|EPOLLET;//LT

epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中

}

else if( events[i].events&EPOLLIN ) //接收到数据,读socket,数据可读标志EPOLLIN

{

n = read(sockfd, line, MAXLINE)) < 0    //读

ev.data.ptr = md;     //md为自定义类型,添加数据

ev.events=EPOLLOUT|EPOLLET;

epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓

}

else if(events[i].events&EPOLLOUT) //有数据待发送,写socket

{

struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取数据

sockfd = md->fd;

send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据

ev.data.fd=sockfd;

ev.events=EPOLLIN|EPOLLET;

epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据

}

else

{

//其他的处理

}

}

}

1.Linux下多线程epoll编程

来自 <http://blog.csdn.net/susubuhui/article/details/37906287>

2.epoll + 多线程实现并发网络连接处理

来自 <http://www.cnblogs.com/iTsihang/archive/2013/05/23/3095775.html>

3.高并发的epoll+线程池,业务在线程池内

来自 <http://blog.chinaunix.net/uid-311680-id-2439722.html>

转载于:https://www.cnblogs.com/ypwen/p/4725532.html

Linux epoll 笔记(高并发事件处理机制)相关推荐

  1. 9基于linux百万级高并发框架Skynet-王桂林-专题视频课程

    <9>基于linux百万级高并发框架Skynet-830人已学习 课程介绍         全面介绍一款专门为游戏服务所打造的后台框架skynet,Actor模型的剖析与搭建,通用服务模块 ...

  2. 实战解读丨Linux下实现高并发socket最大连接数的配置方法

    摘要:Linux操作系统,无论是编写客户端程序还是服务端程序,在高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制. [诉求场景] Linux操作系统,无论是编写 ...

  3. Linux下解决高并发socket最大连接数限制,tcp默认1024个连接

    linux作为服务器系统,当socket运行高并发TCP程序时,通常会出现连接建立到一定个数后不能再建立连接的情况 本人在工作时,测试高并发tcp程序(GPS服务器端程序),多次测试,发现每次连接建立 ...

  4. epoll实现高并发聊天室

    http://blog.csdn.net/qq_31564375/article/details/51581038 项目介绍 本项目是实现一个简单的聊天室,聊天室分为服务端和客户端.本项目将很多复杂的 ...

  5. epoll 版 高并发服务器

    #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h&g ...

  6. [Linux网络编程]高并发-Select模型

    概要介绍 一般情况下,处理socket通信一个线程或者进程在处理读写的时候要么阻塞在那一直等要么非阻塞然后过会查看是否可读可写,这样会浪费大量的资源,假如需要对多个套接字进行处理读写那么得开很多个线程 ...

  7. linux高并发开发视频教程,求视频教程- 基于linux百万级高并发框架Skynet-王桂林-专题视频课程...

    课程大纲 第1章:Day1 1.skynet模型介绍  31:30 2.skynet安装与配置文件介绍  59:38 3.服务的基础API  30:47 4.skynet的环境变量  38:57 5. ...

  8. Linux下高并发socket最大连接数所受的各种限制

    修改最大打开文件数 # ulimit -n 修改最大进程数 # ulimit -u ------------------------------------------------------ Lin ...

  9. 转:Linux下高并发socket最大连接数所受的各种限制

    1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每 ...

最新文章

  1. 【OpenCV 4开发详解】漫水填充法
  2. 转:java中数组与List相互转换的方法
  3. 估值再翻番的元气森林,该如何“长红”?
  4. 学习笔记:linuxsocket通信基础
  5. boost::contract模块实现sum的测试程序
  6. 如何只下载一个github项目的某一特定文件夹或文件
  7. 【机器学习】XGBoost学习笔记
  8. 【翻译】asp.net core中使用MediatR
  9. linux常用网络命令详解,linux网络命令详解(鸟哥)
  10. 前端学习(1401):多人管理21新增用户
  11. 3-5Tensor创建编程实例
  12. [转载]Oracle触发器用法实例详解
  13. 华硕b365安装服务器系统,华硕b365主板装win10系统及bios设置教程(uefi+gpt)
  14. Java截取视频第一帧
  15. 思维导图组件@hellowuxin/mindmap的基本使用
  16. 语音信号的短时平均过零率
  17. Android使用后端云Bmob实现登录、注册及失物招领
  18. 【微信辅助】疫情当前,python帮你找出朋友圈的武汉朋友给予关怀
  19. # Codeforces Round #548 (Div. 2)C Edgy Trees
  20. 成都拓嘉启远:拼多多开店有差评,原因是这样的

热门文章

  1. 对short类型,输出结果不一样?
  2. java 自定义xml_6.1 如何在spring中自定义xml标签
  3. mysql 磁盘组_有效管理 ASM 磁盘组空间
  4. eclipse wsdl2java_使用Eclipse的wsdl2java工具
  5. 新代数控系统参数说明书_台湾新代宏程序编程书
  6. Tomcat服务器性能优化
  7. linux配置定时删除日志文件,Linux使用shell脚本定时删除历史日志文件
  8. php 获取警告信息,获取PHP警告错误信息的解决方法_PHP教程
  9. C++ 继承 | 对象切割、菱形继承、虚继承、对象组合
  10. python学习实例(7)