前面都在分析muduo/base中的源码,这些是辅助网络库的。在分析网络库前,先总结一下相关知识点。
TCP网络编程要关注哪些问题?muduo网络库总结为三个半事件。
1、建立连接:服务端的accept和客户端的connect。一旦建立了连接,收发双方都是平等的了。
2、断开连接:包括主动断开(close、shutdown)和被动断开(read返回0)。
3、消息到达:即文件描述符可读。这个是最终要的事件,对这个事件的处理方式决定了编程的风格。例如阻塞/非阻塞,如何分包,应用层缓冲如何设计等。
3.5、消息发送完毕,这算半个事件。“发送完毕”是指将数据写入到操作系统的系统缓冲区,之后由TCP协议栈负责数据的发送与重传,并不表示接收方已经收到数据。对于低流量的服务,可以不用关心这个事件。
假如应用程序要发送40kb数据,但是TCP的缓冲区只有25kb的剩余空间,那么剩下的15kb数据怎么办?如果等待OS的缓冲区可用,因为不知道什么时候缓冲区可用,会阻塞当前线程。这时应用层需要做的是把这15kb数据缓存起来,放到发送缓冲区,等待socket变为可写时立即发送数据,这样的“发送”操作才不会阻塞。如果应用程序又要发送50kb数据,这时如果应用层发送缓冲区还有数据,则应该把数据追加到发送缓冲区末尾;而不是调用write来写socket,这样会打乱发送数据的顺序。
对于应用层来说,它只需要关心生成数据,不需要关心数据是一次性发送还是分几次发送,这些应该由网络库来操心。应用层只需要调用TcpConnection::send()即可,网络库会将发送负责到底。网络库要做的是将15kb数据放到TcpConnection的output buffer里,然后注册POLLOUT事件,在事件的回调函数中发送剩余数据。如果剩余数据不能一次性发送,那么继续注册POLLOUT事件;如果发送完毕就停止关注POLLOUT事件避免造成busy loop。
假如一次读到的数据不够一个完整数据包,那么读到的数据应该先暂存到某个地方,等待剩余数据到达后在一并处理。加入数据是一个一个字节地间隔10ms到达,每个字节到达都会触发一次文件描述符可读事件,程序是否还能正常工作?
应用层缓冲区用来存放已经到达/即将发送的数据。一方面我们希望缓冲区尽量大,这样在发送和接收时就可以一次性处理更多数据,减少了系统调用。另一方面,我们又希望缓冲区尽量小,因为每个连接都会占用缓冲区,如果过大,将会使用很大的内存,且大多时候,缓冲区的使用效率很低。具体见muduo::buffer分析。
Reactor模式中文翻译过来叫做反应堆模式。这种模式是基于同步I/O的,我们把I/O事件注册到Reactor中,并设置好回调函数,当相应的事件到来时,会调用我们设置的毁掉函数。这一点区别于以往的让线程/进程等待某一事件。
上图为一个Reactor模式。在Reactor模式中注册了一些事件,当事件到达后会通过分发器(dispatch)调用对应回调函数来处理事件。
这只是一个基本的Reactor模式,回调函数的和Reactor在同一个线程中,除此之外还有几个变形。例如Reactor+ThreadPool、Multiple Reactors等。
翻译过来就是非阻塞IO加上IO复用。muduo网络库的实现还有一个限制:one loop per thread。即程序的每个线程有一个 event loop(即Reactor),用来处理读写事件和定时事件。
EventLoop代表了线程的主循环,想要那个线程干活,就把timer或IO Channel注册到对应线程的EventLoop里面即可。对实时性要求比较高的IO事件,可以单独用一个线程。
muduo推荐的模式为one (event)loop per thread+ thread pool。
event loop用作IO multiplexing,配合non-blocking IO和定时器。
thread pool用来处理计算任务,具体可以是生产者消费者队列。
线程池执行任务时,如果密集计算任务的比重为P(0< p <=1),系统有C个CPU,那么为了让C个CPU跑满,而又不过载,那么线程池大小的经验公式为T=C/P。
1、与传统的poll兼容。
2、level trigger编程更容易,不容易出现漏掉事件的bug。
3、读写的时候不用等到出现EAGAIN,减少了系统调用的次数。
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/kangroger/article/details/47170963