linux-epoll研究
linux-epoll研究 - Geek_Ma - 博客园
linux-epoll研究
做linux网络编程的同学都清楚,2.6版本以前的linux内核大多都是用select作为非阻塞的事件触发模型,但是效率低,使用受限已经很明显的暴露了select(包括poll)的缺陷了,为了解决这些缺陷,epoll作为linux新的事件触发模型被创造出来。
一、epoll相对于select的优点:
1.支持一个进程socket描述符(FD)的最大数目
select支持的单进程socket描述符最大数目只有几千,而epoll支持的数目很大,等于系统最大打开的文件描述符数,这个文件描述符数跟内存有一定关系
2.IO效率不随FD数目增加而线性下降
select对事件的扫描是针对于所有创建的socket描述符进行的,也就是说,有多少个socket描述符,就需要遍历多少个句柄,所以IO效率是随描述符增加线性下降的;而epoll只遍历活跃的socket描述符,这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会。比如一个高速LAN环境,epoll并不比select/poll有什么效率,相 反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
3.使用mmap加速内核与用户空间的消息传递
select事件触发后会将信息从内核拷贝到用户空间,这种拷贝就影响了效率。而mmap将内核与用户空间的内存映射到一块内存上,内核将消息捕获后放入该内存空间,用户无需拷贝直接可以访问,减少了拷贝次数,提高了效率。
二、epoll工作模型
epoll事件有两种模型:
Edge Triggered (ET),边缘触发是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,因此效率高。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。
Level Triggered (LT),水平触发是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。三、值得注意的情况:
1.当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取:
while(rs) {buflen = recv(events[i].data.fd, buf, sizeof(buf), 0);if(buflen < 0){// 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读// 在这里就当作是该次事件已处理处.if(errno == EAGAIN)break;elsereturn;}else if(buflen == 0){// 这里表示对端的socket已正常关闭. }if(buflen == sizeof(buf)rs = 1; // 需要再次读取elsers = 0; }2.如果发送端流量大于接收端的流量,也就是说,epoll所在的程序读比转发的socket要慢,由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考mansend),同时,不理会这次请求发送的数据。所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回-1表示出错。在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试。这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send()内部,但暂没有更好的办法。
ssize_t socket_send(int sockfd, const char* buffer, size_t buflen) {ssize_t tmp;size_t total = buflen;const char *p = buffer;while(1){tmp = send(sockfd, p, total, 0);if(tmp < 0){// 当send收到信号时,可以继续写,但这里返回-1.if(errno == EINTR)return -1;// 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满,// 在这里做延时后再重试.if(errno == EAGAIN){usleep(1000);continue;}return -1;}if((size_t)tmp == total)return buflen;total -= tmp;p += tmp;}return tmp; }四、实例
#include <iostream> #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>using namespace std;#define MAXLINE 5 #define OPEN_MAX 100 #define LISTENQ 20 #define SERV_PORT 5000 #define INFTIM 1000 //设置非阻塞 void setnonblocking(int sock) {int opts;opts = fcntl(sock, F_GETFL);if(opts<0){perror("fcntl(sock,GETFL)");exit(1);}opts = opts|O_NONBLOCK;if(fcntl(sock,F_SETFL,opts)<0){perror("fcntl(sock,SETFL,opts)");exit(1);} }int main() {int i, maxi, listenfd,connfd, sockfd,epfd,nfds;ssize_t n;char line[MAXLINE];socklen_t clilen;//声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件struct epoll_event ev, events[20];//生成用于处理accept的epoll专用的文件描述符epfd = epoll_create(256);struct sockaddr_in clientaddr;struct sockaddr_in serveraddr;listenfd = socket(AF_INET, SOCK_STREAM, 0);//把socket设置为非阻塞方式//setnonblocking(listenfd);//设置与要处理的事件相关的文件描述符ev.data.fd = listenfd;//设置要处理的事件类型ETev.events = EPOLLIN|EPOLLET;//ev.events=EPOLLIN;//注册epoll事件epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);bzero(&serveraddr, sizeof(serveraddr));serveraddr.sin_family = AF_INET;char *local_addr="127.0.0.1";inet_aton(local_addr,&(serveraddr.sin_addr));//htons(SERV_PORT);serveraddr.sin_port=htons(SERV_PORT);bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));listen(listenfd, LISTENQ);maxi = 0;for ( ; ; ) {//等待epoll事件的发生nfds = epoll_wait(epfd, events, 20, 500);//处理所发生的所有事件 for(i = 0; i < nfds;++i){if(events[i].data.fd == listenfd){connfd = accept(listenfd, (sockaddr *)&clientaddr, &clilen);if(connfd < 0){perror("connfd<0");exit(1);}//setnonblocking(connfd);char *str = inet_ntoa(clientaddr.sin_addr);cout << "accapt a connection from " << str << endl;//设置用于读操作的文件描述符ev.data.fd = connfd;//设置用于注测的读操作事件ev.events = EPOLLIN|EPOLLET;//ev.events=EPOLLIN;//注册evepoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);}else if(events[i].events&EPOLLIN){cout << "EPOLLIN" << endl;if ( (sockfd = events[i].data.fd) < 0)continue;if ( (n = read(sockfd, line, MAXLINE)) < 0){if (errno == ECONNRESET) {close(sockfd);events[i].data.fd = -1;} elsestd::cout<<"readline error"<<std::endl;} else if (n == 0) {close(sockfd);events[i].data.fd = -1;}line[n] = '\0';cout << "read " << line << endl;//设置用于写操作的文件描述符ev.data.fd = sockfd;//设置用于注册的写操作事件ev.events = EPOLLOUT|EPOLLET;//修改sockfd上要处理的事件为EPOLLOUT//epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev); }else if(events[i].events&EPOLLOUT){ sockfd = events[i].data.fd;write(sockfd, line, n);//设置用于读操作的文件描述符ev.data.fd = sockfd;//设置用于注测的读操作事件ev.events = EPOLLIN|EPOLLET;//修改sockfd上要处理的事件为EPOLINepoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);}}}return 0; }上面的代码是ET模式
测试脚本1:
#!/usr/bin/python import socket import timesock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('127.0.0.1', 5000))sock.send('1234567890') time.sleep(5)while(1):time.sleep(1)输出1:
accapt a connection from 0.0.0.0 EPOLLIN read 12345说明1:
运行server和client发现,server仅仅读取了5字节的数据,而client其实发送了10字节的数据,也就是说,server仅当第一次监听到了EPOLLIN事件,由于没有读取完数据,而且采用的是ET模式,状态在此之后不发生变化,因此server再也接收不到EPOLLIN事件了。当关闭客户端时,会另外触发一个事件,这个事件又触发了一次读操作,也就将后面的5个字节读取出来。
测试脚本2:
#!/usr/bin/python import socket import timesock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('127.0.0.1', 5000))sock.send('1234567890') time.sleep(5) sock.send('1234567890')while(1):time.sleep(1)输出2:
accapt a connection from 0.0.0.0 EPOLLIN read 12345 (5 sec...) EPOLLIN read 67890说明2:
可以发现,在server接收完5字节的数据之后一直监听不到client的事件,而当client休眠5秒之后重新发送数据,server再次监听到了变化,只不过因为只是读取了5个字节,仍然有10个字节的数据(client第二次发送的数据)没有接收完。
如果上面的实验中,对accept的socket都采用的是LT模式,那么只要还有数据留在buffer中,server就会继续得到通知,可以将上面标黄的选项去掉则变为LT模式。
五、总结
ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知了,大多是这个原因造成的;而LT模式是只要有数据没有处理就会一直通知下去的。
补充说明一下这里一直强调的"状态变化"是什么:
1)对于监听可读事件时,如果是socket是监听socket,那么当有新的主动连接到来为状态发生变化;对一般的socket而言,协议栈中相应的缓冲区有新的数据为状态发生变化。但是,如果在一个时间同时接收了N个连接(N>1),但是监听socket只accept了一个连接,那么其它未 accept的连接将不会在ET模式下给监听socket发出通知,此时状态不发生变化;对于一般的socket,就如例子中而言,如果对应的缓冲区本身已经有了N字节的数据,而只取出了小于N字节的数据,那么残存的数据不会造成状态发生变化。
2)对于监听可写事件时,同理可推,不再详述。
不论是监听可读还是可写,对方关闭socket连接都将造成状态发生变化,比如在例子中,如果强行中断client脚本,也就是主动中断了socket连接,那么都将造成server端发生状态的变化,从而server得到通知,将已经在本方缓冲区中的数据读出。
把前面的描述可以总结如下:仅当对方的动作(发出数据,关闭连接等)造成的事件才能导致状态发生变化,而本方协议栈中已经处理的事件(包括接收了对方的数据,接收了对方的主动连接请求)并不是造成状态发生变化的必要条件,状态变化一定是对方造成的。所以在ET模式下的,必须一直处理到出错或者完全处理完毕,才能进行下一个动作,否则可能会发生错误。
部分转自他处-没有找到最终来源
linux-epoll研究相关推荐
- Linux技术研究-基础篇(raid与LVM,配额)
Linux技术研究-基础篇(raid与LVM,配额) 创建RAID-5 若想建立新的md1设备 只在/dev下建立还不够 重启后会消失 固化的方法是 为了使udev自动产生/dev/md1, /dev ...
- linux epoll监听套接字实例
linux epoll机制用于IO多路复用,能够同时监听多个接字,使用起来比较简单. 相关接口: #include <sys/epoll.h>int epoll_create(int si ...
- 劫起|再谈Linux epoll惊群问题的原因和解决方案
原作者:dog250,授权发布 重新整理: 极客重生 文章有点长,可以三连收藏慢慢看 缘起 近期排查了一个问题,epoll惊群的问题,起初我并不认为这是惊群导致,因为从现象上看,只是体现了CPU不均衡 ...
- linux epoll 开发指南-【ffrpc源码解析】
linux epoll 开发指南-[ffrpc源码解析] 摘要 关于epoll的问题很早就像写文章讲讲自己的看法,但是由于ffrpc一直没有完工,所以也就拖下来了.Epoll主要在服务器编程中使用,本 ...
- Linux epoll的用法
Linux epoll的用法 epollfd_create函数 #include <sys/epoll.h>int epoll_create (int __size) 参数 含义 __si ...
- Linux技术研究-基础篇(启动和自动挂载)
Linux技术研究-基础篇(启动和自动挂载) 系统启动流程 如果有一天你的服务器启动不了,面对屏幕上的各种各样的提示素手无策. 你不知道服务器出了什么问题,无法判断启动到了哪个环节. 若想排查出问题原 ...
- python网络编程linux pdf_Python网络编程:Linux epoll
原文地址:http://scotdoyle.com/python-epoll-howto.html 介绍 Python已于2.6版本添加访问Linux epoll库的API.这篇教程使用Python ...
- [转] Windows完成端口与Linux epoll技术简介
Windows完成端口与Linux epoll技术简介 2008-01-03 16:18 WINDOWS完成端口编程1.基本概念 2.WINDOWS完成端口的特点 3.完成端口(Completion ...
- 再谈Linux epoll惊群问题的原因和解决方案
差别是什么?差别只是西装! 缘起 近期排查了一个问题,epoll惊群的问题,起初我并不认为这是惊群导致,因为从现象上看,只是体现了CPU不均衡.一共fork了20个Server进程,在请求负载中等的时 ...
- 基于linux epoll网络编程细节处理丨epoll原理剖析
epoll原理剖析以及三握四挥的处理 1. epoll原理详解 2. 连接的创建与断开 3. epoll如何连接细节问题 视频讲解如下,点击观看: 基于linux epoll网络编程细节处理丨epol ...
最新文章
- WF流程设计器升级说明
- Unit Testing for WinForm
- 5G NR 同步过程
- Windows之Wireshake之抓HTTP请求包(过滤目的IP)
- 【免费毕设】asp.netERP客户关系系统设计(源代码+lunwen)
- linux安装mysql5.6rpm_centos6.5 下安装mysql5.6,rpm方式
- JVM学习(1)——通过实例总结Java虚拟机的运行机制
- 优启通制作系统u盘_EasyU优启通U盘启动盘制作工具BIOS+UEFI双启无捆绑
- Rhino6.5软件安装教程|兼容WIN10
- [C语言] [游戏] 扫雷
- beyond compare 4 This license key has been revoked 解决办法
- 微信小程序开发学习5(自定义组件)
- 深入理解计算机大端与小端
- 繁荣国家数学教育,坚持“知识共享”许可原则
- html无法获取图片高宽,如何解决谷歌浏览器下jquery无法获取图片的尺寸
- 基于matlab的pcm设计实验报告,基于MATLAB的PCM系统仿真课程设计
- 写跨文化交际的论文,有哪些好的题目推荐?
- 高德地图API JS实现获取坐标和回显点标记
- 计算机中真值的概念,真值和机器数概念
- InstallShield 2008 And 脚本(十)
热门文章
- java 更新文件内容吗_java Io流更新文件内容
- 【测试面试题】偶数和奇数
- Python PIL.Image和OpenCV图像格式相互转换
- 树莓派cpu检测_【树莓派3B+测评】线程的挂起与恢复CPU温度检测
- c语言指数pow,C语言中的指数函数pow()问题
- 2015 计算机考研大纲,2015年考研计算机大纲详解:操作系统
- php如何使用代码清除bom,使用php清除bom示例
- E数据结构实验之查找五:平方之哈希表
- 数据结构实验之链表八:Farey序列
- 《STL源码剖析》学习-- 1.9-- 可能令你困惑的C++语法2