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处于可读或者可写状态 ...
随机推荐
- react native web
http://rawgit.com/taobaofed/react-web/master/pages/uiexplorer.html#/scene_1?_k=7vm99j
- android中的样式和主题
有的时候我们一个页面要用很多个textview,而且这些textview的样式非常相像,这种情况下我们可以把这些样式抽取出来,然后在每个textview中引用即可,这样修改起来也方便. 我们来看一个简 ...
- 字符串反转实现(C++)
字符串反转 C++实现,不使用系统函数: // ReverseString.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include ...
- JavaScript判断数据类型总结
最近做项目中遇到了一些关于javascript数据类型的判断处理,上网找了一下资料,并且亲自验证了各种数据类型的判断网页特效,在此做一个总结吧! 一.JS中的数据类型 1.数值型(Number):包 ...
- Spring Mvc和Mybatis的多数据库访问配置过程
Spring Mvc 加Mybatis的多数据库访问源配置访问过程如下: 在applicationContext.xml进行配置 <?xml version="1.0" en ...
- asp.net 使用IHttpModule 做权限检查 登录超时检查(转)
IHttpModule 权限 检查 登录超时检查 这样就不需要每个页面都做一次检查 也不需要继承任何父类. using System;using System.Collections.Generic; ...
- php并发处理
最近某个项目用php生成文件,但是由于文件量太大,单个进程生成需要很长的时间,所以想并发进行处理. 网上查找了下相关的资料,php本身是没有多线程的概念的,那就只能用多进程了,再找资料却是 ...
- CI框架篇之基础篇(2)
CodeIgniter 的基础了解了后,现在就来对这个框架进行预热 CodeIgniter 配置 理论是不用配置,直接拷贝到服务器目录下即可运行 CodeIgniter 安装分为四个步骤: 1. 解压 ...
- Android - This Handler class should be static or leaks might occur.
今天学习了使用 HTTP协议,从Android客户端往Tomcat服务器端以GET发送请求,途中无意中发现自己写的Handler类被Android提示:This Handler class shoul ...
- VS番茄助手安装(vs2015+vs2010):卸载之前的vs助手再安装新版本
1 卸载之前的vs助手 vs2010: vs2015: 2 安装新版本