学习muduo(框架的设计)

首先的话,以EventLoop为起点,它是一个事件循环,也是one loop per thread的核心,建议先了解一下reactor模型。我们需要设计一个loop函数,利用它 1.获得 活动的事件 2.执行对应事件的handle。

  1. 不可避免的我们需要操作系统select,poll,epoll的支持,所以我们把他们封装一个抽象类Poller,问题1就解决了。
  2. 怎么执行对应事件的handle?那么我们就可以把它包装为一个Channel类,设置read事件,write事件,error事件,close事件对应的handle。还有一个问题!不同的Channel类型他们的handle是不一样的,例如TcpConnection中的Channel和TcpServer中的channel,就拿读事件来说,前者应该是用户定义的,后者应该是创建一个TcpConnection。所以channel类的handle应该是上一层注册给它的,上一层就是Acceptor或者TcpConnection或者Connector。那么channel的设计也应该明了了,channel是一个注册和响应io的类,它的生存期决定与上一层。

现在我们引出了上一层的三个类,阐述一个它们和channel的相互作用。

  1. Acceptor类,它控制着 监听套接字 和 监听channel,创建新连接。一方面,它应该得到上一层的指示(即TcpServer)才开始监听。另一方面,刚才说到Channel类是用来注册和响应io的,显然Acceptor类对socket读事件感兴趣(即来及客户端的连接请求),那么我们应该设计一个handleRead函数注册给它的channel,它的作用是产生一个TcpConnection,上一条博客我们说了TcpConnection的产生需要做的三件事,简略的说就是,1.增加Tcpserver中指针,2.设置它的channel读事件感兴趣,3.增加poller中指针。
  2. TcpConnection类,负责接收发送数据,内有两块缓冲区。我们知道它应该有一套handle要注册给它的Channel,也即read,write,error,close。那么这些handle都应该是谁设计的呢?我们一个一个分析。对于handleRead,也即客户端发来了数据,那么我们该怎么办呢?显然这个是用户(根据muduo库编写服务端程序的人)定义的有关,比如说echo服务器,我的handleRead就是把数据发送回去(慢着,在此之前,我们得先读了数据再说,这点不是用户定义的),等等。handleWrite呢?想像一下这种情况,一次发出大量数据,我们先发出去了一部分,还剩下一部分放在缓冲区,这时我们就要设置channel对可写事件感兴趣,从而等待时机利用handleWrite继续发送剩余数据。关于销毁的问题,上一条博客提到。
  3. Connector类,负责连接服务器,超时重连(这个就牵扯到定时器,重试时间间隔加倍)等。由于可能连接失败,所以当连接上服务器,才把channel绑定到当前连接成功的socket.fd。同时注册给channel handleWrete函数,可写事件发生,创建一个TcpConnection。

终于说到了TcpServer和TcpClient。
这两个很类似,首先它们管理着各自TcpConnection,负责创建和删除TcpConnection(不一定,用户也可持有TcpConnectionPtr)。其次,它们各自拥有着Acceptor和Connector,负责监听和连接的任务。最后,他们承担着外部接口的作用,包括启动和设置直接注册到TcpConnection的回调函数。

关于服务端,Eventloop可以设置为多个,但是每个线程至多一个。就像主从reactor模式一样,主Eventloop用来处理连接请求,并把请求分发给从Eventloop处理(round_robin策略)。

还有一个问题,线程安全 以及线程间调配任务的问题。runInLoop,接受一个functor参数。只需要保证vector< functor >的线程安全就达到了线程间调配任务的安全性。假设现在有两个线程t1,t2,分别调用Tcpconnection::send函数,那么肯定不是同一个线程。加锁,两个fucntor传给queueInLoop。假设t1的send第一次没有发送完,那么把剩余的放入outputBuffer,并关注可写事件,待时而动。现在处理t2的functor,若此时outputBuffer空(即t1剩余的已发送出去),则一切正常,若不为空,则放入outputBuffer。可以发现,数据传输是有序的,不会t1发送了一半,再发送t2,再发送t1的后一半。

你可能感兴趣的:(muduo网络库)