Epoll在LT和ET模式下的读写方式
在一个非阻塞的socket上调用read/write函数, 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK)
从字面上看, 意思是:EAGAIN: 再试一次,EWOULDBLOCK: 如果这是一个阻塞socket, 操作将被block,perror输出: Resource temporarily unavailable
总结:
这个错误表示资源暂时不够,能read时,读缓冲区没有数据,或者write时,写缓冲区满了。遇到这种情况,如果是阻塞
socket,read/write就要阻塞掉。而如果是非阻塞socket,read/write立即返回-1, 同时errno设置为EAGAIN。
所以,对于阻塞socket,read/write返回-1代表网络出错了。但对于非阻塞socket,read/write返回-1不一定网络真的出错
了。可能是Resource temporarily unavailable。这时你应该再试,直到Resource available。
综上,对于non-blocking的socket,正确的读写操作为:
读:忽略掉errno = EAGAIN的错误,下次继续读
写:忽略掉errno = EAGAIN的错误,下次继续写
对于select和epoll的LT模式,这种读写方式是没有问题的。但对于epoll的ET模式,这种方式还有漏洞。
epoll的两种模式LT和ET
二者的差异在于level-trigger模式下只要某个socket处于readable/writable状态,无论什么时候进行
epoll_wait都会返回该socket;而edge-trigger模式下只有某个socket从unreadable变为readable或从
unwritable变为writable时,epoll_wait才会返回该socket。
所以,在epoll的ET模式下,正确的读写方式为:
读:只要可读,就一直读,直到返回0,或者 errno = EAGAIN
写:只要可写,就一直写,直到数据发送完,或者 errno = EAGAIN
正确的读
| n = 0; |
| while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) { |
| n += nread; |
| } |
| if (nread == -1 && errno != EAGAIN) { |
| perror("read error"); |
| } |
正确的写
| int nwrite, data_size = strlen(buf); |
| n = data_size; |
| while (n > 0) { |
| nwrite = write(fd, buf + data_size - n, n); |
| if (nwrite < n) { |
| if (nwrite == -1 && errno != EAGAIN) { |
| perror("write error"); |
| } |
| break; |
| } |
| n -= nwrite; |
| } |
正确的accept,accept 要考虑 2 个问题
(1) 阻塞模式 accept 存在的问题
考虑这种情况:TCP连接被客户端夭折,即在服务器调用accept之前,客户端主动发送RST终止连接,导致刚刚建立的连接从就绪队列中移出,如果套接口被设置成阻塞模式,服务器就会一直阻塞在accept调用上,直到其他某个客户建立一个新的连接为止。但是在此期间,服务器单纯地阻塞在accept调用上,就绪队列中的其他描述符都得不到处理。
解决办法是把监听套接口设置为非阻塞,当客户在服务器调用accept之前中止某个连接时,accept调用可以立即返回-1,这时源自Berkeley的实现会在内核中处理该事件,并不会将该事件通知给epool,而其他实现把errno设置为ECONNABORTED或者EPROTO错误,我们应该忽略这两个错误。
(2)ET模式下accept存在的问题
考虑这种情况:多个连接同时到达,服务器的TCP就绪队列瞬间积累多个就绪连接,由于是边缘触发模式,epoll只会通知一次,accept只处理一个连接,导致TCP就绪队列中剩下的连接都得不到处理。
解决办法是用while循环抱住accept调用,处理完TCP就绪队列中的所有连接后再退出循环。如何知道是否处理完就绪队列中的所有连接呢?accept返回-1并且errno设置为EAGAIN就表示所有连接都处理完。
综合以上两种情况,服务器应该使用非阻塞地accept,accept在ET模式下的正确使用方式为:
| while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote, (size_t *)&addrlen)) > 0) { |
| handle_client(conn_sock); |
| } |
| if (conn_sock == -1) { |
| if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR) |
| perror("accept"); |
| } |
一道腾讯后台开发的面试题
使用Linuxepoll模型,水平触发模式;当socket可写时,会不停的触发socket可写的事件,如何处理?
第一种最普遍的方式:
需要向socket写数据的时候才把socket加入epoll,等待可写事件。
接受到可写事件后,调用write或者send发送数据。
当所有数据都写完后,把socket移出epoll。
这种方式的缺点是,即使发送很少的数据,也要把socket加入epoll,写完后在移出epoll,有一定操作代价。
一种改进的方式:
开始不把socket加入epoll,需要向socket写数据的时候,直接调用write或者send发送数据。如果返回EAGAIN,把socket加入epoll,在epoll的驱动下写数据,全部数据发送完毕后,再移出epoll。
这种方式的优点是:数据不多的时候可以避免epoll的事件处理,提高效率。
最后贴一个使用epoll,ET模式的简单HTTP服务器代码:
| #include <sys/socket.h> |
| #include <sys/wait.h> |
| #include <netinet/in.h> |
| #include <netinet/tcp.h> |
| #include <sys/epoll.h> |
| #include <sys/sendfile.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #define MAX_EVENTS 10 |
| #define PORT 8080 |
| //设置socket连接为非阻塞模式 |
| void setnonblocking(int sockfd) { |
| int opts; |
| opts = fcntl(sockfd, F_GETFL); |
| if(opts < 0) { |
| perror("fcntl(F_GETFL)\n"); |
| exit(1); |
| } |
| opts = (opts | O_NONBLOCK); |
| if(fcntl(sockfd, F_SETFL, opts) < 0) { |
| perror("fcntl(F_SETFL)\n"); |
| exit(1); |
| } |
| } |
| int main(){ |
| struct epoll_event ev, events[MAX_EVENTS]; |
| int addrlen, listenfd, conn_sock, nfds, epfd, fd, i, nread, n; |
| struct sockaddr_in local, remote; |
| char buf[BUFSIZ]; |
| //创建listen socket |
| if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { |
| perror("sockfd\n"); |
| exit(1); |
| } |
| setnonblocking(listenfd); |
| bzero(&local, sizeof(local)); |
| local.sin_family = AF_INET; |
| local.sin_addr.s_addr = htonl(INADDR_ANY);; |
| local.sin_port = htons(PORT); |
| if( bind(listenfd, (struct sockaddr *) &local, sizeof(local)) < 0) { |
| perror("bind\n"); |
| exit(1); |
| } |
| listen(listenfd, 20); |
| epfd = epoll_create(MAX_EVENTS); |
| if (epfd == -1) { |
| perror("epoll_create"); |
| exit(EXIT_FAILURE); |
| } |
| ev.events = EPOLLIN; |
| ev.data.fd = listenfd; |
| if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) { |
| perror("epoll_ctl: listen_sock"); |
| exit(EXIT_FAILURE); |
| } |
| for (;;) { |
| nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); |
| if (nfds == -1) { |
| perror("epoll_pwait"); |
| exit(EXIT_FAILURE); |
| } |
| for (i = 0; i < nfds; ++i) { |
| fd = events[i].data.fd; |
| if (fd == listenfd) { |
| while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote, |
| (size_t *)&addrlen)) > 0) { |
| setnonblocking(conn_sock); |
| ev.events = EPOLLIN | EPOLLET; |
| ev.data.fd = conn_sock; |
| if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock, |
| &ev) == -1) { |
| perror("epoll_ctl: add"); |
| exit(EXIT_FAILURE); |
| } |
| } |
| if (conn_sock == -1) { |
| if (errno != EAGAIN && errno != ECONNABORTED |
| && errno != EPROTO && errno != EINTR) |
| perror("accept"); |
| } |
| continue; |
| } |
| if (events[i].events & EPOLLIN) { |
| n = 0; |
| while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) { |
| n += nread; |
| } |
| if (nread == -1 && errno != EAGAIN) { |
| perror("read error"); |
| } |
| ev.data.fd = fd; |
| ev.events = events[i].events | EPOLLOUT; |
| if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1) { |
| perror("epoll_ctl: mod"); |
| } |
| } |
| if (events[i].events & EPOLLOUT) { |
| sprintf(buf, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\nHello World", 11); |
| int nwrite, data_size = strlen(buf); |
| n = data_size; |
| while (n > 0) { |
| nwrite = write(fd, buf + data_size - n, n); |
| if (nwrite < n) { |
| if (nwrite == -1 && errno != EAGAIN) { |
| perror("write error"); |
| } |
| break; |
| } |
| n -= nwrite; |
| } |
| close(fd); |
| } |
| } |
| } |
| return 0; |
| } |
Epoll在LT和ET模式下的读写方式的更多相关文章
- MySQL-Front 出现“程序注册时间到期 程序将被限制模式下运行”解决方式
MySQL-Front 出现“程序注册时间到期 程序将被限制模式下运行”解决方式 在用mysql-front的时候遇到显示:程序注册时间到期程序将被限制模式下运行.可以在“帮助”菜单下的点“登记”-- ...
- 我理解的epoll(三)多线程模式下的ET
ET模式下,需要循环从缓存中读取,直到返回EAGAIN没有数据可读后,一个被通知的事件才算结束.如果还读取过程中,同一个连接又有新的事件到来,触发其他线程处理同一个socket,就乱了.EPOLL_O ...
- Mycat主从模式下的读写分离与自动切换
1. 机器环境 192.168.2.136 mycat1 192.168.2.134 mydb1 192.168.2.135 mydb2 2在mysql1.mysql2上安装mysql 更改root用 ...
- Redis集群模式下的redis-py-cluster方式读写测试
与MySQL主从复制,从节点可以分担部分读压力不一样,甚至可以增加slave或者slave的slave来分担读压力,Redis集群中的从节点,默认是不分担读请求的,从节点只作为主节点的备份,仅负责故障 ...
- 浅析SqlServer简单参数化模式下对sql语句自动参数化处理以及执行计划重用
我们知道,SqlServer执行sql语句的时候,有一步是对sql进行编译以生成执行计划, 在生成执行计划之前会去缓存中查找执行计划 如果执行计划缓存中有对应的执行计划缓存,那么SqlServer就会 ...
- epoll水平/边缘触发模式下阻塞/非阻塞EPOLLOUT事件触发条件及次数
在IO多路复用技术中,epoll默认的事件触发模式为Level_triggered(水平触发)模式,即当被监控的文件描述符上有可读/写事件发生时,epoll_wait()会通知处理程序去读写.如果这次 ...
- epoll的lt和et模式的实验
针对epoll api的两种触发模式,lt和et,仿照一些例子写了代码进行实验. #include <sys/types.h> #include <sys/socket.h> ...
- socket异步通信-如何设置成非阻塞模式、非阻塞模式下判断connect成功(失败)、判断recv/recvfrom成功(失败)、判断send/sendto
socket异步通信-如何设置成非阻塞模式.非阻塞模式下判断connect成功(失败).判断recv/recvfrom成功(失败).判断send/sendto 博客分类: Linux Socket s ...
- Epoll之ET、LT模式
Epoll之ET.LT模式 在使用epoll时,在函数 epoll_ctl中如果不设定,epoll_event 的event默认为LT(水平触发)模式. 使用LT模式意味着只要fd处于可读或者可写状态 ...
随机推荐
- XC软件管理器应用
这是一个基于android 4.4开发的android应用-XC软件管理器.包含应用的信息查看,打开应用以及应用的卸载等功能.非常实用的一个应用,欢迎大家下载使用. 下载地址:http://downl ...
- ASP.NET MVC 第七回 UrlHelper
这节讲 一下ASP.NET MVC中的Helper. 何谓Helper,其实就是在View中为了实现一些灵活功能而写的方法组. 其实ASP.NET MVC的View是Aspx的页面,本身可以声明定义方 ...
- 在解决方案中添加Layout,Image文件夹
在解决方案中添加文件夹如Layout,Image,可以使用映射的方式(右键该解决方案,添加对Layout的映射),这样IIS的地址和该文件夹就绑定了:拖该文件夹中的 文件时,显示的就是该文件在IIS中 ...
- android studio或者IntelliJ代码样式的设置
使用过程中发现了一些与Eclipse不同的地方,在这里提供一些设置方法 1. Backspace会将开头的空格和换行符一并删除,如果要禁用 Settings -> Editor -> ...
- MongoDB的索引
一.索引详讲 索引是什么,索引就好比一本书的目录,当我们想找某一章节的时候,通过书籍的目录可以很快的找到,所以适当的加入索引可以提高我们查询的数据的速度. 准备工作,向MongoDB中插入20000条 ...
- hibernate - Initial SessionFactory creation failed.org.hibernate.HibernateException
对于数据库字段映射, 被映射的字段除了提供 getter方法之外, 还需要提供setter方法. 这也是java bean的一些规范. 例如, 题目中的异常 Initial SessionFactor ...
- IOS-图片上传到服务器
//获取document 路径- (NSString *)getDocumentPath{ NSArray *paths = NSSearchPathForDirectoriesInDomain ...
- 菜鸟日记之JSP二 内置对象的理解
·最近学习JSP了,对编程和网络又有了一些理解.无论是现实中人与人的交流,还是网络世界的接触,都是在相互表达自己的意思让别人知道,并理解对方的信息.然后我们知道的事情不断的变多,会又交杂出新的内容,不 ...
- log4j文件
log4j文件是一种开源日志记录工具,其作用是记录程序运异常行过程中的重要的操作信息和记录可能出现的异常情况便于调试. 根据日志记录的信息内容可分为3类: Sql日志:记录系统执行的SQL语句 异常日 ...
- [转]显示文件命令:cat、more、less、tail、touch详解
cat命令cat命令连接文件并打印到标准输出设备上.cat经常用来显示文件的内容,类似于下的type命令. 一般格式:cat [选项] 文件说明:该命令有两项功能,其一是用来显示文件的内容,它依次读取 ...