在前面的文章中曾经粗略讲过poll,那时是用阻塞IO实现,在发送和接收数据量都较小情况下和网络状况良好的情况下是基本没有问题的,read 不会只接收部分数据,write 也不会一直阻塞。但实际上poll IO复用经常是跟非阻塞IO一起使用的,想想如果现在内核接收缓冲区一点数据没有,read 阻塞了,或者内核发送缓冲区不够空间存放数据,write 阻塞了,那整个事件循环就会延迟响应,比如现在又有一个新连接connect上来了,也不能很快回到循环去accept 它。

在前面的文章中也曾粗略讲过epoll,使用的是ET 边沿触发模式,每次accept 返回需要将conn 设置为非阻塞,ET模式可能存在的问题是有可能只读取了部分数据,剩下的epoll_wait 就再也不会返回可读事件了。

这篇文章来谈谈如何正确使用non-blocking I/O Multiplexing + poll/epoll。

1、首先来回顾下poll / epoll 函数的原型

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
     int   fd;     /* file descriptor */
     short events;     /* requested events */
     short revents;     /* returned events */
};


#include <sys/epoll.h>


int epoll_create(int size);
 
//size
 并不代表能够容纳的事件个数


int epoll_create1(int flags); // EPOLL_CLOEXEC

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);


int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

typedef union epoll_data {


     void    *ptr;


     int      fd;


     uint32_t u32;


     uint64_t u64;


} epoll_data_t;

struct epoll_event {


     uint32_t     events;     /* Epoll events */


     epoll_data_t data;     /* User data variable */


};


具体的参数介绍参考以前的文章。


2、关于SIGPIPE 信号的产生和处理

如果客户端关闭套接字close,而服务器调用一次write, 服务器会接收一个RST segment(tcp传输层)
如果服务器端再次调用了write,这个时候就会产生SIGPIPE信号,默认终止进程。可以在程序中直接忽略掉,如 signal(SIGPIPE, SIG_IGN);

3、TIME_WAIT 状态对 服务器的影响

如果服务器端 主动断开连接(先于client 调用close),服务器端就会进入TIME_WAIT 状态。应尽可能在服务器端避免TIME_WAIT 状态,因为它会在一定时间内hold住一些内核资源。协议设计上,应该让客户端主动断开连接,这样就把TIME_WAIT状态分散到大量的客户端。如果客户端不活跃了,一些不客户端不断开连接,这样就会占用服务器端的连接资源。服务器端也要踢掉不活跃的连接close。


4、使用 C++ erase 的注意点

即erase 返回的是下一个元素的iterator

5、新的accept4 系统调用

accept - accept a connection on a socket

      #include <sys/types.h>          /* See NOTES */

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

#define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <sys/socket.h>

int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

可以使用accept4 这个新的系统调用,多了一个flags 参数,可以设置以下两个标志:

SOCK_NONBLOCK   Set the O_NONBLOCK file status flag on the new open file description.  Using this flag saves  extra  calls
 to fcntl(2) to achieve the same result.

 SOCK_CLOEXEC    Set  the close-on-exec (FD_CLOEXEC) flag on the new file descriptor.  See the description of the O_CLOEXEC  
flag in open(2) for reasons why this may be useful.

注意,这两个标志是设置accept 回来的conn 标志的,当然也可以使用fcntl (F_SETFL / F_SETFD) 设置,但少了两次系统调用,可以稍微提高点性能。

7、poll  的处理流程和存在的问题

存在的问题和解决办法:

(1)、read 可能一次并没有把connfd 所对应的接收缓冲区(内核)的数据都读完(粘包问题),那么connfd 下次仍然是活跃的
应该把读到的数据保存在connfd 的应用层接收缓冲区,每次都追加在末尾。需要处理协议以区分每条消息的边界

(2)、write 可能一次并不能把所有数据都写到发送缓冲区(内核),所以应该有一个应用层发送缓冲区,将未发送完的数据添加到应用层发送缓冲区,关注connfd 的POLLOUT 事件。POLLOUT事件到来,则取出应用层发送缓冲区数据发送write,如果应用层发送缓冲区数据发送完毕,则取消关注POLLOUT事件。
POLLOUT 事件触发条件:connfd的发送缓冲区(内核)不满(可以容纳数据)

注:connfd 的接收缓冲区(内核)数据被接收后会被清空,当发出数据后接收到对方的ACK段后,发送缓冲区(内核)数据会被清空。write只是将应用层发送缓冲区数据拷贝到connfd 对应的内核发送缓冲区就返回;read 只是从connfd对应的内核接收缓冲区数据拷贝到应用层接收缓冲区就返回。

9、epoll  的两种模式处理流程和存在的问题

Level-Triggered 
//跟poll
 基本类似


LT 电平触发(高电平触发):

EPOLLIN 事件
内核中的某个socket接收
缓冲区     为空          低电平
内核中的某个socket接收缓冲区     不为空       高电平

EPOLLOUT 事件
内核中的某个socket发送缓冲区     不满          高电平
内核中的某个socket发送缓冲区     满             低电平

如果采用Level-Triggered,那什么时候关注EPOLLOUT事件?会不会造成busy-loop(忙等待)?

Edge-Triggered:



ET 边沿触发:
低电平-》高电平      触发
高电平-》低电平      触发

推荐epoll使用LT模式的原因:

与poll兼容

LT模式不会发生漏掉事件的BUG,但POLLOUT事件不能一开始就关注,否则会出现busy loop,而应该在write无法完全写入内核缓冲区的时候才关注,将未写入内核缓冲区的数据添加到应用层output buffer,直到应用层output buffer写完,停止关注POLLOUT事件。

读写的时候不必等候EAGAIN,可以节省系统调用次数,降低延迟。(注:如果用ET模式,读的时候读到EAGAIN,写的时候直到output buffer写完或者写到EAGAIN)

10、accept(2)返回EMFILE的处理(文件描述符已经用完)


(1)、调高进程文件描述符数目

(2)、死等

(3)、退出程序

(4)、关闭监听套接字。那什么时候重新打开呢?

(5)、如果是epoll模型,可以改用edge trigger。问题是如果漏掉了一次accept(2),程序再也不会收到新连接(没有状态变化)


(6)、准备一个空闲的文件描述符。遇到这种情况,先关闭这个空闲文件,获得一个文件描述符名额;再accept(2)拿到socket连接的文件描述符;随后立刻close(2),这样就优雅地断开了与客户端的连接;最后重新打开空闲文件,把“坑”填上,以备再次出现这种情况时使用。


如下面的代码片段:
 C++ Code 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

 
int idlefd = open(
"/dev/null", O_RDONLY | O_CLOEXEC);

connfd = accept4(listenfd, (
struct sockaddr *)&peeraddr,

                 &peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC);

/*          if (connfd == -1)
                ERR_EXIT("accept4");
*/

if (connfd == -
)

{

    
if (errno == EMFILE)

    {

        close(idlefd);

        idlefd = accept(listenfd, 
NULL, 
NULL);

        close(idlefd);

        idlefd = open(
"/dev/null", O_RDONLY | O_CLOEXEC);

        
continue;

    }

    
else

        ERR_EXIT(
"accept4");

}



参考:
muduo manual.pdf
《linux 多线程服务器编程:使用muduo c++网络库》


浅谈 non-blocking I/O Multiplexing + poll/epoll 的正确使用的更多相关文章

  1. 浅谈 js 对象 toJSON 方法

    前些天在<浅谈 JSON.stringify 方法>说了他的正确使用姿势,今天来说下 toJSON 方法吧.其实我觉得这货跟 toString 一个道理,他是给 stringify 方法字 ...

  2. []转帖] 浅谈Linux下的五种I/O模型

    浅谈Linux下的五种I/O模型 https://www.cnblogs.com/chy2055/p/5220793.html  一.关于I/O模型的引出 我们都知道,为了OS的安全性等的考虑,进程是 ...

  3. 浅谈 Unix I/O 模型

    原文出处:http://miaoo.in/talk-about-unix-io-model.html 在实际应用中,数据操作通常分为输入和输出,那么以输入为例,在操作系统中,一个数据的输入通常分为以下 ...

  4. Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理

    Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...

  5. 浅谈Java线程安全

    浅谈Java线程安全 - - 2019-04-25    17:37:28 线程安全 Java中的线程安全 按照线程安全的安全程序由强至弱来排序,我们可以将Java语言中各种操作共享的数据分为以下五类 ...

  6. 浅谈Websocket、Ajax轮询和长轮询(long polling)

    浅谈Websocket.Ajax轮询和长轮询(long p0ll) 最近看到了一些介绍Websocket的文章,觉得挺有用,所以在这里将自己的对其三者的理解记录一下. 1.什么是Websocket W ...

  7. 浅谈 Fragment 生命周期

    版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...

  8. 浅谈 LayoutInflater

    浅谈 LayoutInflater 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/View 文中如有纰漏,欢迎大家留言指出. 在 Android 的 ...

  9. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...

随机推荐

  1. Mysql 计算时间间隔函数

    #计算两个时间的间隔 #计算间隔天数 select TIMESTAMPDIFF(day,'2014-06-01',date(now())) #计算间隔月数 select TIMESTAMPDIFF(m ...

  2. asp.net textbox控件基础

    asp.net有两种控件,一种是html控件,一种是asp控件,在说textbox控件之前,先看看按钮的两个命令oncommand和onclick.每次点击按钮后,都会提交命令,但是程序会首先执行Pa ...

  3. 引用jquery框架后出错

    问题描述:当引用了jquery框架后,页面的js不能正常工作. 后面我的解决办法:是因为在引用 jquery的框架时的代码为 <script type="text/javascript ...

  4. ARM和x86的区别

    CPU的指令集从主流的体系结构上分为精简指令集(RISC)和复杂指令集(CISC).嵌入式系统中的主流处理器——ARM处理器,所使用的就是精简指 令集.而桌面领域的处理器大部分使用的是复杂指令集,比如 ...

  5. 浅谈C中的指针和数组(二)

    原文转载地址:http://see.xidian.edu.cn/cpp/html/475.html 在原文的基础上增加自己的想法作为修改 很多初学者弄不清指针和数组到底有什么样的关系.我现在就告诉你: ...

  6. [转载]给10万pv的WordPress选择最便宜高可用的硬件以及WordPress高并发支持

    这些命令很长,但是希望可以让你很容易按照步骤创建一个全新的Linux服务器,使用Varnish, Nginx, W3 Total Cache, and WordPress来搭建一个WordPress博 ...

  7. 浮动层固定兼容IE6 position:fixed的最佳解决方案

    第一种:css方法 有时候当我们需要把一个元素固定在页面的某个部位,一般都是用css中的“position:fixed;”方法来解决,但是IE6不支持fixed,所以今天分享一个兼容IE6的页面底部固 ...

  8. American tour(If you have a chance)

    去美国旅游,或出国旅游,英语不精没关系,词汇量不多也没关系,但有一些实用的口语一定要学会哟~ 酒店住宿常用英文 I would like to have a morning Call at 8:00 ...

  9. Linux文件系统与结构

    一.Linux文件系统结构 /bin 二进制的缩写,用来放置可执行的二进制程序,基本命令 /boot 用来存放启动文件,kernel 和boot配置文件 /dev 用来放置设备文件 /dev/cons ...

  10. Perfmon 介绍

    1.UI 1.笔图标,可以把视图里的细线加粗. 2.加号图标.可以为视图添加新的记数器. 3.操作-->属性.可以用于设置默认的采样时间. 2.增加计数据器页面 1.一个计数据器会包涵多个对象. ...