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处于可读或者可写状态 ...
随机推荐
- eclipse快速查找一个变量、方法或者类被引用的地方
最近不停debug,拿到一个变量之后总是要先概览一下才好下手,之前一直用Ctrl+F来做,太麻烦.今天查了下eclipse使用,发现有快捷键,使用方法: 先双击要查看的变量.方法或者类,使之被选中,然 ...
- FAQs: 我们可以在那里来为我的没有提升管理权限的应用程序存储用户数据?
如果你正在写一个不需要管理员权限的应用程序,如写一个业务线应用(Line of Business,LOB),用户应用程序如游戏,你总是要把应用程序的数据写到一个标准用可以访问的目录.下面列出一些所推荐 ...
- SQL给字段加上统一的某个字符
表名:News 字段名:No_id Update News set No_id='字符'+No_id
- Java SE (1)之 JFrame 组件 FlowLayout 布局
package com.sunzhiyan; import java.awt.*; import java.awt.event.*; import javax.swing.*; public clas ...
- query 防止ajax重复提交
项目用到js了,首选jquery,能用库用库,原则. 碰到重复提交的问题,禁止住才行.百度google,还是Google给力. 知乎上有个高人,总结了四种,利用Jquery .post方法返回jqXH ...
- sql存储过程通过ID删除两表中的数据。
CREATE OR REPLACE PROCEDURE del_p --建立名为del_p 的过程 IS CURSOR get_abid --简历名为get_abid的cursor 用来存放a表的id ...
- 浅析Activity不可见与透明
http://blog.csdn.net/lincyang/article/details/6868582 看见标题也许你会有疑问,不可见和透明不是一个意思吗? 从字面上看,这还真是差不多.但在Act ...
- C#编程使用到的几种调试方式
一.前言: 使用C#语言从08年算起,到现在也有6个年头的时间了. 但 是会使用调试进行辅助编程的时间,却只有5个年头,其中第一年里面,只能傻傻地敲着老师给的案例,不会写就一遍一遍重复手写编码,上机练 ...
- c语言_链表实例讲解(两个经典例子)
建立一个学生成绩的线性链表,对其实现插入,删除,输出,最后销毁. demo1 // lianbiao.cpp : Defines the entry point for the console app ...
- 【转】ASP.NET MVC教程
转自:http://www.cnblogs.com/QLeelulu/category/123326.html ASP.NET MVC的最佳实践与性能优化的文章 摘要: 就一些文章链接,就不多废话了. ...