菜鸟笔记
提升您的技术认知

《unix网络编程》epoll 的accept , read, write-ag真人游戏

在一个非阻塞的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]

  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. }    

 正确的写:

  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 个问题:参考<讲解的更加详细

 (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 模式下 的正确使用方式为:

  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服务器代码:

  1. #include       
  2. #include       
  3. #include       
  4. #include       
  5. #include       
  6. #include       
  7. #include       
  8. #include       
  9. #include       
  10. #include       
  11. #include       
  12. #include       
  13. #include       
  14. #include        
  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. }  
网站地图