首页 滚动 > > 正文

详解Linux处理高并发的利器epoll

来源:面包芯语 发布日期:2023-04-05 09:14:54 分享到:

设想一个场景:有100万用户同时与一个进程保持着TCP连接,而每一时刻只有几十个或几百个TCP连接是活跃的(接收TCP包),也就是说在每一时刻进程只需要处理这100万连接中的一小部分连接。


(相关资料图)

那么,如何才能高效的处理这种场景呢?进程是否在每次询问操作系统收集有事件发生的TCP连接时,把这100万个连接告诉操作系统,然后由操作系统找出其中有事件发生的几百个连接呢?实际上,在 Linux2.4 版本以前,那时的select 或者 poll 事件驱动方式是这样做的。

这里有个非常明显的问题,即在某一时刻,进程收集有事件的连接时,其实这100万连接中的大部分都是没有事件发生的。

因此如果每次收集事件时,都把100万连接的套接字传给操作系统(这首先是用户态内存到内核态内存的大量复制),而由操作系统内核寻找这些连接上有没有未处理的事件,将会是巨大的资源浪费,然后select和poll就是这样做的,因此它们最多只能处理几千个并发连接。

而epoll不这样做,它在Linux内核中申请了一个简易的文件系统,把原先的一个select或poll调用分成了3部分:

intepoll_create(intsize);intepoll_ctl(intepfd,intop,intfd,structepoll_event*event);intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout);

这样只需要在进程启动时建立 1 个 epoll 对象,并在需要的时候向它添加或删除连接就可以了,因此,在实际收集事件时,epoll_wait 的效率就会非常高,因为调用 epoll_wait 时并没有向它传递这100万个连接,内核也不需要去遍历全部的连接。

一、epoll原理详解

当某一进程调用 epoll_create 方法时,Linux 内核会创建一个 eventpoll 结构体,这个结构体中有两个成员与epoll的使用方式密切相关,如下所示:

structeventpoll{.../*红黑树的根节点,这棵树中存储着所有添加到epoll中的事件,也就是这个epoll监控的事件*/structrb_rootrbr;/*双向链表rdllist保存着将要通过epoll_wait返回给用户的、满足条件的事件*/structlist_headrdllist;...};

我们在调用 epoll_create 时,内核除了帮我们在 epoll 文件系统里建了个 file 结点,在内核 cache 里建了个红黑树用于存储以后 epoll_ctl 传来的 socket 外,还会再建立一个 rdllist 双向链表,用于存储准备就绪的事件,当 epoll_wait 调用时,仅仅观察这个 rdllist 双向链表里有没有数据即可。

有数据就返回,没有数据就 sleep,等到 timeout 时间到后即使链表没数据也返回。所以,epoll_wait 非常高效。

所有添加到epoll中的事件都会与设备(如网卡)驱动程序建立回调关系,也就是说相应事件的发生时会调用这里的回调方法。这个回调方法在内核中叫做ep_poll_callback,它会把这样的事件放到上面的rdllist双向链表中。

在epoll中对于每一个事件都会建立一个epitem结构体,如下所示:

structepitem{...//红黑树节点structrb_noderbn;//双向链表节点structlist_headrdllink;//事件句柄等信息structepoll_filefdffd;//指向其所属的eventepoll对象structeventpoll*ep;//期待的事件类型structepoll_eventevent;...};//这里包含每一个事件对应着的信息。

当调用 epoll_wait 检查是否有发生事件的连接时,只是检查eventpoll对象中的rdllist双向链表是否有epitem元素而已,如果rdllist链表不为空,则这里的事件复制到用户态内存(使用共享内存提高效率)中,同时将事件数量返回给用户。

因此epoll_waitx效率非常高。epoll_ctl在向epoll对象中添加、修改、删除事件时,从rbr红黑树中查找事件也非常快,也就是说epoll是非常高效的,它可以轻易地处理百万级别的并发连接。

【总结】:

一颗红黑树,一张准备就绪句柄链表,少量的内核cache,就帮我们解决了大并发下的socket处理问题。

二、epoll 的两种触发模式

epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。

还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。

【epoll为什么要有EPOLLET触发模式?】:

如果采用 EPOLLLT 模式的话,系统中一旦有大量你不需要读写的就绪文件描述符,它们每次调用epoll_wait都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率。

而采用EPOLLET这种边缘触发模式的话,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。

如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。

【总结】:

三、epoll反应堆模型

【epoll模型原来的流程】:

epoll_create();//创建监听红黑树epoll_ctl();//向书上添加监听fdepoll_wait();//监听有监听fd事件发送--->返回监听满足数组--->判断返回数组元素--->lfd满足accept--->返回cfd---->read()读数据--->write()给客户端回应。

【epoll反应堆模型的流程】:

epoll_create();//创建监听红黑树epoll_ctl();//向书上添加监听fdepoll_wait();//监听有客户端连接上来--->lfd调用acceptconn()--->将cfd挂载到红黑树上监听其读事件--->epoll_wait()返回cfd--->cfd回调recvdata()--->将cfd摘下来监听写事件--->epoll_wait()返回cfd--->cfd回调senddata()--->将cfd摘下来监听读事件--->...--->

【Demo】:

#include#include#include#include#include#include#include#include#include#include#defineMAX_EVENTS1024/*监听上限*/#defineBUFLEN4096/*缓存区大小*/#defineSERV_PORT6666/*端口号*/voidrecvdata(intfd,intevents,void*arg);voidsenddata(intfd,intevents,void*arg);/*描述就绪文件描述符的相关信息*/structmyevent_s{intfd;//要监听的文件描述符intevents;//对应的监听事件,EPOLLIN和EPLLOUTvoid*arg;//指向自己结构体指针void(*call_back)(intfd,intevents,void*arg);//回调函数intstatus;//是否在监听:1->在红黑树上(监听),0->不在(不监听)charbuf[BUFLEN];intlen;longlast_active;//记录每次加入红黑树g_efd的时间值};intg_efd;//全局变量,作为红黑树根structmyevent_sg_events[MAX_EVENTS+1];//自定义结构体类型数组.+1-->listenfd/**封装一个自定义事件,包括fd,这个fd的回调函数,还有一个额外的参数项*注意:在封装这个事件的时候,为这个事件指明了回调函数,一般来说,一个fd只对一个特定的事件*感兴趣,当这个事件发生的时候,就调用这个回调函数*/voideventset(structmyevent_s*ev,intfd,void(*call_back)(intfd,intevents,void*arg),void*arg){ev->fd=fd;ev->call_back=call_back;ev->events=0;ev->arg=arg;ev->status=0;if(ev->len<=0){memset(ev->buf,0,sizeof(ev->buf));ev->len=0;}ev->last_active=time(NULL);//调用eventset函数的时间return;}/*向epoll监听的红黑树添加一个文件描述符*/voideventadd(intefd,intevents,structmyevent_s*ev){structepoll_eventepv={0,{0}};intop=0;epv.data.ptr=ev;//ptr指向一个结构体(之前的epoll模型红黑树上挂载的是文件描述符cfd和lfd,现在是ptr指针)epv.events=ev->events=events;//EPOLLIN或EPOLLOUTif(ev->status==0)//status说明文件描述符是否在红黑树上0不在,1在{op=EPOLL_CTL_ADD;//将其加入红黑树g_efd,并将status置1ev->status=1;}if(epoll_ctl(efd,op,ev->fd,&epv)<0)//添加一个节点printf("eventaddfailed[fd=%d],events[%d]\n",ev->fd,events);elseprintf("eventaddOK[fd=%d],events[%0X]\n",ev->fd,events);return;}/*从epoll监听的红黑树中删除一个文件描述符*/voideventdel(intefd,structmyevent_s*ev){structepoll_eventepv={0,{0}};if(ev->status!=1)//如果fd没有添加到监听树上,就不用删除,直接返回return;epv.data.ptr=NULL;ev->status=0;epoll_ctl(efd,EPOLL_CTL_DEL,ev->fd,&epv);return;}/*当有文件描述符就绪,epoll返回,调用该函数与客户端建立链接*/voidacceptconn(intlfd,intevents,void*arg){structsockaddr_incin;socklen_tlen=sizeof(cin);intcfd,i;if((cfd=accept(lfd,(structsockaddr*)&cin,&len))==-1){if(errno!=EAGAIN&&errno!=EINTR){sleep(1);}printf("%s:accept,%s\n",__func__,strerror(errno));return;}do{for(i=0;ibuf,sizeof(ev->buf),0);//读取客户端发过来的数据eventdel(g_efd,ev);//将该节点从红黑树上摘除if(len>0){ev->len=len;ev->buf[len]="\0";//手动添加字符串结束标记printf("C[%d]:%s\n",fd,ev->buf);eventset(ev,fd,senddata,ev);//设置该fd对应的回调函数为senddataeventadd(g_efd,EPOLLOUT,ev);//将fd加入红黑树g_efd中,监听其写事件}elseif(len==0){close(ev->fd);/*ev-g_events地址相减得到偏移元素位置*/printf("[fd=%d]pos[%ld],closed\n",fd,ev-g_events);}else{close(ev->fd);printf("recv[fd=%d]error[%d]:%s\n",fd,errno,strerror(errno));}return;}/*发送给客户端数据*/voidsenddata(intfd,intevents,void*arg){structmyevent_s*ev=(structmyevent_s*)arg;intlen;len=send(fd,ev->buf,ev->len,0);//直接将数据回射给客户端eventdel(g_efd,ev);//从红黑树g_efd中移除if(len>0){printf("send[fd=%d],[%d]%s\n",fd,len,ev->buf);eventset(ev,fd,recvdata,ev);//将该fd的回调函数改为recvdataeventadd(g_efd,EPOLLIN,ev);//重新添加到红黑树上,设为监听读事件}else{close(ev->fd);//关闭链接printf("send[fd=%d]error%s\n",fd,strerror(errno));}return;}/*创建socket,初始化lfd*/voidinitlistensocket(intefd,shortport){structsockaddr_insin;intlfd=socket(AF_INET,SOCK_STREAM,0);fcntl(lfd,F_SETFL,O_NONBLOCK);//将socket设为非阻塞memset(&sin,0,sizeof(sin));//bzero(&sin,sizeof(sin))sin.sin_family=AF_INET;sin.sin_addr.s_addr=INADDR_ANY;sin.sin_port=htons(port);bind(lfd,(structsockaddr*)&sin,sizeof(sin));listen(lfd,20);/*voideventset(structmyevent_s*ev,intfd,void(*call_back)(int,int,void*),void*arg);*/eventset(&g_events[MAX_EVENTS],lfd,acceptconn,&g_events[MAX_EVENTS]);/*voideventadd(intefd,intevents,structmyevent_s*ev)*/eventadd(efd,EPOLLIN,&g_events[MAX_EVENTS]);//将lfd添加到监听树上,监听读事件return;}intmain(){intport=SERV_PORT;g_efd=epoll_create(MAX_EVENTS+1);//创建红黑树,返回给全局g_efdif(g_efd<=0)printf("createefdin%serr%s\n",__func__,strerror(errno));initlistensocket(g_efd,port);//初始化监听socketstructepoll_eventevents[MAX_EVENTS+1];//定义这个结构体数组,用来接收epoll_wait传出的满足监听事件的fd结构体printf("serverrunning:port[%d]\n",port);intcheckpos=0;inti;while(1){/*longnow=time(NULL);for(i=0;i<100;i++,checkpos++){if(checkpos==MAX_EVENTS);checkpos=0;if(g_events[checkpos].status!=1)continue;longduration=now-g_events[checkpos].last_active;if(duration>=60){close(g_events[checkpos].fd);printf("[fd=%d]timeout\n",g_events[checkpos].fd);eventdel(g_efd,&g_events[checkpos]);}}*///调用eppoll_wait等待接入的客户端事件,epoll_wait传出的是满足监听条件的那些fd的structepoll_event类型intnfd=epoll_wait(g_efd,events,MAX_EVENTS+1,1000);if(nfd<0){printf("epoll_waiterror,exit\n");exit(-1);}for(i=0;ievents&EPOLLIN)){ev->call_back(ev->fd,events[i].events,ev->arg);}//如果监听的是写事件,并返回的是写事件if((events[i].events&EPOLLOUT)&&(ev->events&EPOLLOUT)){ev->call_back(ev->fd,events[i].events,ev->arg);}}}return0;}

原文:https://blog.csdn.net/daaikuaichuan/article/details/83862311

我的私人微信

点击阅读原文查看你错过的宝藏

关键词:

x 广告

河北印发出台通用机场布局规划(2021-2030年)

到2030年,全省形成以A类通用机场为主体、B类通用机场为补充,功能完善、覆盖广泛的通用机场体系,全省通用机场达到23个。其中,到2025年全

复原民国旧菜单 一批“消失的名菜”重现羊城

  中新网广州12月5日电 (记者 程景伟)“粤宴中国·消失的名菜”活动4日晚在广州博物馆镇海楼广场举行,一批业已失传或十分罕见的传统粤

青海再度“双清零”:战“疫”催生定点救治医院反思与成长

  中新网西宁12月5日电 题:青海再度“双清零”:战“疫”催生定点救治医院反思与成长  作者 潘雨洁  全面停诊、四下无人;火线冲

世界海拔最高高铁客运站山丹马场站运营

  中新网兰州12月5日电 (记者 杨艳敏)记者从中国铁路兰州局集团有限公司获悉,12月5日10时29分随着嘉峪关南至西安北D2696次动车组列车

千年古都洛阳为何要建青年友好型城市?

  中新网洛阳12月5日电 题:千年古都洛阳为何要建青年友好型城市?  记者 肖开霖 李贵刚  千年古都洛阳日前公布《洛阳市建设青年

甘肃万余河长公示牌拥有“电子身份证” 局地启“千里眼”治水

  中新网兰州12月5日电 (记者 冯志军)记者5日从甘肃省水利厅获悉,今年以来,甘肃全面推动河长公示牌信息化建设,为全省河流换发“电子

满洲里市向呼伦贝尔市“手递手”异地转运3批次隔离人员

  (抗击新冠肺炎)满洲里市向呼伦贝尔市“手递手”异地转运3批次隔离人员  中新网呼伦贝尔12月5日电 (记者 张玮)5日,内蒙古自治区呼

2021年度法治人物沈云如:让群众过上“有身份的生活”

  中新网杭州12月5日电 题:2021年度法治人物沈云如:让群众过上“有身份的生活”  作者 郭其钰 张先登  行程10余万公里,为辖区3

x 广告

Copyright   2015-2022 北方海洋网版权所有  备案号:京ICP备2021034106号-50   联系邮箱: 55 16 53 8@qq.com