对于服务器的并发处理能力,我们需要的是:每一毫秒服务器都能及时处理这一毫秒内收到的数百个不同TCP连接上的报文,与此同时,可能服务器上还有数以十万计的最近几秒没有收发任何报文的相对不活跃连接。同时处理多个并行发生事件的连接,简称为并发;同时处理万计、十万计的连接,则是高并发。服务器的并发编程所追求的就是处理的并发连接数目无限大,同时维持着高效率使用CPU等资源,直至物理资源首先耗尽。

并发编程有很多种实现模型,最简单的就是与“线程”捆绑,1个线程处理1个连接的全部生命周期。优点:这个模型足够简单,它可以实现复杂的业务场景,同时,线程个数是可以远大于CPU个数的。然而,线程个数又不是可以无限增大的,为什么呢?因为线程什么时候执行是由操作系统内核调度算法决定的,调度算法并不会考虑某个线程可能只是为了一个连接服务的,它会做大一统的玩法:时间片到了就执行一下,哪怕这个线程一执行就会不得不继续睡眠。这样来回的唤醒、睡眠线程在次数不多的情况下,是廉价的,但如果操作系统的线程总数很多时,它就是昂贵的(被放大了),因为这种技术性的调度损耗会影响到线程上执行的业务代码的时间。

消息处理,可以分为两个阶段:等待消息准备好、消息处理。当使用默认的阻塞套接字时(例如上面提到的1个线程捆绑处理1个连接),往往是把这两个阶段合而为一,这样操作套接字的代码所在的线程就得睡眠来等待消息准备好,这导致了高并发下线程会频繁的睡眠、唤醒,从而影响了CPU的使用效率。

对高并发编程,目前只有一种模型,也是本质上唯一有效的玩法当然就是把两个阶段分开处理。即,等待消息准备好的代码段,与处理消息的代码段是分离的。当然,这也要求套接字必须是非阻塞的,否则,处理消息的代码段很容易导致条件不满足时,所在线程又进入了睡眠等待阶段。那么问题来了,等待消息准备好这个阶段怎么实现?它毕竟还是等待,这意味着线程还是要睡眠的!解决办法就是,线程主动查询,或者让1个线程为所有连接而等待!这就是IO多路复用了。多路复用就是处理等待消息准备好这件事的,但它可以同时处理多个连接!它也可能“等待”,所以它也会导致线程睡眠,然而这不要紧,因为它一对多、它可以监控所有连接。这样,当我们的线程被唤醒执行时,就一定是有一些连接准备好被我们的代码执行了,这是有效率的!没有那么多个线程都在争抢处理“等待消息准备好”阶段,整个世界终于清净了!

多路复用有很多种实现,在linux上,2.4内核前主要是select和poll,现在主流是epoll(Mac OS等的kevent),它们的使用方法似乎很不同,但本质是一样的。对于select和epoll的性能比较,可以参考这篇论文:https://www.kernel.org/doc/ols/2004/ols2004v1-pages-215-226.pdf

高并发的核心解决方案是1个线程处理所有连接的“等待消息准备好”,这一点上epoll和select是无争议的。但select预估错误了一件事,就像我们开篇所说,当数十万并发连接存在时,可能每一毫秒只有数百个活跃的连接,同时其余数十万连接在这一毫秒是非活跃的。select的使用方法是这样的:
返回的活跃连接 ==select(全部待监控的连接)
什么时候会调用select方法呢?在你认为需要找出有报文到达的活跃连接时,就应该调用。所以,调用select在高并发时是会被频繁调用的。这样,这个频繁调用的方法就很有必要看看它是否有效率,因为,它的轻微效率损失都会被“频繁”二字所放大。它有效率损失吗?显而易见,全部待监控连接是数以十万计的,返回的只是数百个活跃连接,这本身就是无效率的表现。被放大后就会发现,处理并发上万个连接时,select就完全力不从心了。

再来说说epoll是如何解决的。它很聪明的用了3个方法来实现select方法要做的事:
新建的epoll描述符==epoll_create()
epoll_ctrl(epoll描述符,添加或者删除所有待监控的连接)
返回的活跃连接 ==epoll_wait( epoll描述符 )
这么做的好处主要是:分清了频繁调用和不频繁调用的操作。例如,epoll_ctrl是不太频繁调用的,而epoll_wait是非常频繁调用的。这时,epoll_wait却几乎没有入参,这比select的效率高出一大截,而且,它也不会随着并发连接的增加使得入参越发多起来,导致内核执行效率下降。

epoll是怎么实现的呢?其实很简单,从这3个方法就可以看出,它比select聪明的避免了每次频繁调用“哪些连接已经处在消息准备好阶段”的 epoll_wait时,是不需要把所有待监控连接传入的。这意味着,它在内核态维护了一个数据结构保存着所有待监控的连接。这个数据结构就是一棵红黑树,它的结点的增加、减少是通过epoll_ctrl来完成的。

关于epoll的实现,可以参考内核代码:fs/eventpoll.c

最后,再看看epoll提供的2种玩法ETLT,即翻译过来的边缘触发水平触发(结合电子脉冲理解)。其实这两个中文名字倒也有些贴切。这2种使用方式针对的仍然是效率问题,只不过变成了epoll_wait返回的连接如何能够更准确些。例如,我们需要监控一个连接的写缓冲区是否空闲,满足“可写”时我们就可以从用户态将响应调用write发送给客户端 。但是,或者连接可写时,我们的“响应”内容还在磁盘上呢,此时若是磁盘读取还未完成呢?肯定不能使线程阻塞的,那么就不发送响应了。但是,下一次epoll_wait时可能又把这个连接返回给你了,你还得检查下是否要处理。可能,我们的程序有另一个模块专门处理磁盘IO,它会在磁盘IO完成时再发送响应。那么,每次epoll_wait都返回这个“可写”的、却无法立刻处理的连接,是否符合用户预期呢?

于是,ET和LT模式就应运而生了。LT是每次满足期待状态的连接,都得在epoll_wait中返回,所以它一视同仁,都在一条水平线上。ET则不然,它倾向更精确的返回连接。在上面的例子中,连接第一次变为可写后,若是程序未向连接上写入任何数据,那么下一次epoll_wait是不会返回这个连接的。ET叫做 边缘触发,就是指,只有连接从一个状态转到另一个状态时,才会触发epoll_wait返回它。可见,ET的编程要复杂不少,至少应用程序要小心的防止epoll_wait的返回的连接出现:可写时未写数据后却期待下一次“可写”、可读时未读尽数据却期待下一次“可读”。

当然,从一般应用场景上它们性能是不会有什么大的差距的,ET可能的优点是,epoll_wait的调用次数会减少一些,某些场景下连接在不必要唤醒时不会被唤醒(此唤醒指epoll_wait返回)。但如果像我上面举例所说的,有时它不单纯是一个网络问题,跟应用场景相关。当然,大部分开源框架都是基于ET写的,框架嘛,它追求的是纯技术问题,当然力求尽善尽美。

参考文章:http://blog.csdn.net/russell_tao

网络编程之IO复用:select or epoll的更多相关文章

  1. 服务器端网络编程之 IO 模型

    引言  从 T 跳槽到 A 之后,我的编程语言也从 C++ 转为 了 Java.在 T 做的偏服务器端开发,而在 A 更偏向于业务开发.上周在 A 公司组内做了一个<服务器端高性能网络编程> ...

  2. 网络编程之IO模型

    IO模型的分类 blocking IO:阻塞IO nonblocking IO:非阻塞IO IO multiplexing:IO多路复用 signal driven IO:异步IO 通常情况下IO默认 ...

  3. linux网络编程之IO模型

    本文转自作者:huangguisu 1. 概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式:同步:      所谓 ...

  4. linux网络编程之IO函数

    Linux操作系统中的IO函数主要有read(),write(),recv(),send(),recvmsg(),sendmsg(),readv(),writev(). 接收数据的recv()函数 # ...

  5. unix下网络编程之I/O复用(三)

    poll函数 在上文unix下网络编程之I/O复用(二)中已经介绍了select函数的相关使用,本文将介绍另一个常用的I/O复用函数poll.poll提供的功能与select类似,不过在处理流设备时, ...

  6. 高并发网络编程之epoll详解(转载)

    高并发网络编程之epoll详解(转载) 转载自:https://blog.csdn.net/shenya1314/article/details/73691088 在linux 没有实现epoll事件 ...

  7. 网络编程之C10K

    网络编程之C10K 虽然在过去的十几年里C10K问题已经可以很好的解决,但学习网络编程时研究C10K问题仍然价值巨大,因为技术的发展都是有规律和线索可循的,了解C10K问题及其解决思路,通过举一反三, ...

  8. python3网络编程之socketserver

    本节主要是讲解python3网络编程之socketserver,在上一节中我们讲到了socket.由于socket无法支持多用户和多并发,于是就有了socket server. socket serv ...

  9. 网络编程之UDP编程

    网络编程之UDP编程 UDP协议是一种不可靠的网络协议,它在通信的2端各建立一个Socket,但是这个Socket之间并没有虚拟链路,这2个Socket只是发送和接受数据的对象,Java提供了Data ...

随机推荐

  1. [uestc oj]H - 邱老师选妹子

    H - 邱老师选妹子 Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submi ...

  2. BCB:Windows消息处理

    Windows消息处理 BCB 本文研究了BCB中的消息处理机制,在此基础上提出了处理Windows消息和自定义消息响应的方法和建立动态和静态消息映射的技巧. C++ Builder作为一种RAD方式 ...

  3. Bootstrap历练实例:激活导航状态

    激活导航状态 您可以在激活状态的胶囊式导航和列表导航中放置徽章.通过使用 <span class="badge"> 来激活链接,如下面的实例所示: <!DOCTY ...

  4. 配置Xcode的Device Orientation、AppIcon、LaunchImage

    以下图片指出的 TARGETS→General 面板的信息. 下面我们讲讲根据 APP 需求配置我们的Xcode: 1.设置 Device Orientation,指定 APP 支持设备的方向 ,我们 ...

  5. C语言中声明和定义详解(待看。。

    变量声明和变量定义 变量定义:用于为变量分配存储空间,还可为变量指定初始值.程序中,变量有且仅有一个定义. 变量声明:用于向程序表明变量的类型和名字. 定义也是声明,extern声明不是定义 定义也是 ...

  6. 【模拟】HHHOJ#251. 「NOIP模拟赛 伍」高精度

    积累模拟经验 题目描述 维护一个二进制数,支持如下操作 "+" 该数加 11 "-" 该数减 11 "*" 该数乘 22 "\&q ...

  7. mysql中的的按小数位截取

    format()函数返回类型是字符串,满三位会加一个逗号. 针对数字类型转换建议使用 convert或者cast函数,用法如下: format(param, 2) (不建议) convert(para ...

  8. october安装过程

    下载代码 composer create-project october/october myoctober 准备好数据库, create database october; 配置环境于安装 php ...

  9. ccf 201803-4 棋局评估(Python实现)

    一.原题 问题描述 试题编号: 201803-4 试题名称: 棋局评估 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 Alice和Bob正在玩井字棋游戏. 井字棋游戏的规则很 ...

  10. jQuery将物体居中,并且转换显示和隐藏

    今天来给大家贴一段代码,代码的作用就是利用jQuery将物体居中,并且转换显示和隐藏: 首先建立一个div标签并且写好css样式,具体如下 然后我想要的效果是当我点击了button这个按钮,test可 ...