UNIX网络编程——epoll 的accept , read, write

2024-08-28 04:18

本文主要是介绍UNIX网络编程——epoll 的accept , read, write,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在一个非阻塞的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。如下两个示意图:

      从socket读数据:

                                                             

       往socket写数据:

                                                             

       所以在epoll的ET模式下,正确的读写方式为:

  • 读: 只要可读, 就一直读,直到返回0,或者 errno = EAGAIN
  • 写:只要可写, 就一直写,直到数据发送完,或者 errno = EAGAIN

 

正确的读:

[cpp] view plain copy print ?
  1. n = 0;    
  2. while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) {    
  3.     n += nread;    
  4. }    
  5. if (nread == -1 && errno != EAGAIN) {    
  6.     perror("read error");    
  7. }    

 正确的写:
[cpp] view plain copy print ?
  1. int nwrite, data_size = strlen(buf);    
  2. n = data_size;    
  3. while (n > 0) {    
  4.     nwrite = write(fd, buf + data_size - n, n);    
  5.     if (nwrite < n) {    
  6.         if (nwrite == -1 && errno != EAGAIN) {    
  7.             perror("write error");    
  8.         }    
  9.         break;    
  10.     }    
  11.     n -= nwrite;    
  12. }    


       正确的accept,accept 要考虑 2 个问题:参考<<http://blog.csdn.net/u013074465/article/details/44993253>>讲解的更加详细

       (1) LT模式下或ET模式下,阻塞的监听socket, accept 存在的问题

       accept每次都是从已经完成三次握手的tcp队列中取出一个连接,考虑这种情况: 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 模式下 的正确使用方式为:

[cpp] view plain copy print ?
  1. while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote,     
  2.                 (size_t *)&addrlen)) > 0) {    
  3.     handle_client(conn_sock);    
  4. }    
  5. if (conn_sock == -1) {    
  6.     if (errno != EAGAIN && errno != ECONNABORTED     
  7.             && errno != EPROTO && errno != EINTR)     
  8.         perror("accept");    
  9. }    



       一道腾讯后台开发的面试题:

       使用Linux epoll模型,水平触发模式;当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服务器代码:

[cpp] view plain copy print ?
  1. #include <sys/socket.h>      
  2. #include <sys/wait.h>      
  3. #include <netinet/in.h>      
  4. #include <netinet/tcp.h>      
  5. #include <sys/epoll.h>      
  6. #include <sys/sendfile.h>      
  7. #include <sys/stat.h>      
  8. #include <unistd.h>      
  9. #include <stdio.h>      
  10. #include <stdlib.h>      
  11. #include <string.h>      
  12. #include <strings.h>      
  13. #include <fcntl.h>      
  14. #include <errno.h>       
  15. #define MAX_EVENTS 10      
  16. #define PORT 8080      
  17. //设置socket连接为非阻塞模式      
  18. void setnonblocking(int sockfd) {      
  19.     int opts;      
  20.      
  21.    opts = fcntl(sockfd, F_GETFL);      
  22.     if(opts < 0) {      
  23.         perror("fcntl(F_GETFL)\n");      
  24.         exit(1);      
  25.     }      
  26.     opts = (opts | O_NONBLOCK);      
  27.     if(fcntl(sockfd, F_SETFL, opts) < 0) {      
  28.         perror("fcntl(F_SETFL)\n");      
  29.         exit(1);      
  30.     }      
  31. }      
  32.       
  33. int main(){      
  34.     struct epoll_event ev, events[MAX_EVENTS];      
  35.     int addrlen, listenfd, conn_sock, nfds, epfd, fd, i, nread, n;      
  36.     struct sockaddr_in local, remote;      
  37.     char buf[BUFSIZ];      
  38.       
  39.     //创建listen socket      
  40.     if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {      
  41.         perror("sockfd\n");      
  42.         exit(1);      
  43.     }      
  44.     setnonblocking(listenfd);      
  45.     bzero(&local, sizeof(local));      
  46.     local.sin_family = AF_INET;      
  47.     local.sin_addr.s_addr = htonl(INADDR_ANY);;      
  48.     local.sin_port = htons(PORT);      
  49.     if( bind(listenfd, (struct sockaddr *) &local, sizeof(local)) < 0) {      
  50.         perror("bind\n");      
  51.         exit(1);      
  52.     }      
  53.     listen(listenfd, 20);      
  54.       
  55.     epfd = epoll_create(MAX_EVENTS);      
  56.     if (epfd == -1) {      
  57.         perror("epoll_create");      
  58.         exit(EXIT_FAILURE);      
  59.     }        
  60.     ev.events = EPOLLIN;      
  61.     ev.data.fd = listenfd;      
  62.     if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {      
  63.         perror("epoll_ctl: listen_sock");      
  64.         exit(EXIT_FAILURE);      
  65.     }      
  66.       
  67.     for (;;) {      
  68.         nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);      
  69.        if (nfds == -1) {      
  70.             perror("epoll_pwait");      
  71.             exit(EXIT_FAILURE);      
  72.         }      
  73.       
  74.         for (i = 0; i < nfds; ++i) {      
  75.             fd = events[i].data.fd;      
  76.             if (fd == listenfd) {      
  77.                 while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote,(size_t *)&addrlen)) > 0) {      
  78.                     setnonblocking(conn_sock); //设置连接socket为非阻塞     
  79.                     ev.events = EPOLLIN | EPOLLET; //边沿触发要求套接字为非阻塞模式;水平触发可以是阻塞或非阻塞模式     
  80.                     ev.data.fd = conn_sock;      
  81.                     if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock,&ev) == -1) {      
  82.                         perror("epoll_ctl: add");      
  83.                         exit(EXIT_FAILURE);      
  84.                     }      
  85.                 }      
  86.                 if (conn_sock == -1) {      
  87.                     if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR)       
  88.                         perror("accept");      
  89.                 }      
  90.                 continue;      
  91.             }        
  92.             if (events[i].events & EPOLLIN) {      
  93.                 n = 0;      
  94.                 while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) {      
  95.                     n += nread;      
  96.                 }      
  97.                 if (nread == -1 && errno != EAGAIN) {      
  98.                     perror("read error");      
  99.                 }      
  100.                 ev.data.fd = fd;      
  101.                 ev.events = events[i].events | EPOLLOUT;      
  102.                 if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1) {      
  103.                     perror("epoll_ctl: mod");      
  104.                 }      
  105.             }      
  106.             if (events[i].events & EPOLLOUT) {      
  107.                 sprintf(buf, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\nHello World", 11);      
  108.                 int nwrite, data_size = strlen(buf);      
  109.                 n = data_size;      
  110.                 while (n > 0) {      
  111.                     nwrite = write(fd, buf + data_size - n, n);      
  112.                     if (nwrite < n) {      
  113.                         if (nwrite == -1 && errno != EAGAIN) {      
  114.                             perror("write error");      
  115.                         }      
  116.                         break;      
  117.                     }      
  118.                     n -= nwrite;      
  119.                 }      
  120.                 close(fd);      
  121.             }      
  122.         }      
  123.     }   
  124.     close(epfd);  
  125.     close(listenfd);      
  126.     return 0;      
  127. }  

这篇关于UNIX网络编程——epoll 的accept , read, write的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1113701

相关文章

Linux网络配置之网桥和虚拟网络的配置指南

《Linux网络配置之网桥和虚拟网络的配置指南》这篇文章主要为大家详细介绍了Linux中配置网桥和虚拟网络的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 一、网桥的配置在linux系统中配置一个新的网桥主要涉及以下几个步骤:1.为yum仓库做准备,安装组件epel-re

IDEA下"File is read-only"可能原因分析及"找不到或无法加载主类"的问题

《IDEA下Fileisread-only可能原因分析及找不到或无法加载主类的问题》:本文主要介绍IDEA下Fileisread-only可能原因分析及找不到或无法加载主类的问题,具有很好的参... 目录1.File is read-only”可能原因2.“找不到或无法加载主类”问题的解决总结1.File

python如何下载网络文件到本地指定文件夹

《python如何下载网络文件到本地指定文件夹》这篇文章主要为大家详细介绍了python如何实现下载网络文件到本地指定文件夹,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下...  在python中下载文件到本地指定文件夹可以通过以下步骤实现,使用requests库处理HTTP请求,并结合o

Linux高并发场景下的网络参数调优实战指南

《Linux高并发场景下的网络参数调优实战指南》在高并发网络服务场景中,Linux内核的默认网络参数往往无法满足需求,导致性能瓶颈、连接超时甚至服务崩溃,本文基于真实案例分析,从参数解读、问题诊断到优... 目录一、问题背景:当并发连接遇上性能瓶颈1.1 案例环境1.2 初始参数分析二、深度诊断:连接状态与

Python 异步编程 asyncio简介及基本用法

《Python异步编程asyncio简介及基本用法》asyncio是Python的一个库,用于编写并发代码,使用协程、任务和Futures来处理I/O密集型和高延迟操作,本文给大家介绍Python... 目录1、asyncio是什么IO密集型任务特征2、怎么用1、基本用法2、关键字 async1、async

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

关于pandas的read_csv方法使用解读

《关于pandas的read_csv方法使用解读》:本文主要介绍关于pandas的read_csv方法使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录pandas的read_csv方法解读read_csv中的参数基本参数通用解析参数空值处理相关参数时间处理相关

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子