目录
1、流程叙述
2、我们看看TcpServer的构造都做了什么?
3、start()
3.1 开启loop
3.2 连接的建立
3.3 数据的收发
4、连接的关闭
muduo网络库各组件梳理见此博客
重写muduo库之组件梳理
1、流程叙述
首先,我们是怎么使用的?
1.定义一个loop,就是baseloop
2.InetAddress打包了IP地址和端口号
3.创建server对象,给到EchoServer的构造函数,通过其参数进行初始化底层的TCPserver对象(相当于创建了TcpServer对象),保留了loop
4.设置了2个回调setConnectionCallback、setMessageCallback
5.设置了底层的loop线程的数量(这个数量不包括baseloop)
很好的把网络代码和业务代码分离了,我们只需要做开发者需要关注的:
6.我们用户作为开发者只需要关注下面的2个方法:
响应连接的建立和断开、响应读写事件
设置完之后
7.server.start();启动loop loop.loop()
总结而来就是:首先构建tcpserver对象,设置回调方法,然后设置底层的loop线程的数量,然后调用start方法,最后开启主线程的loop
2、我们看看TcpServer的构造都做了什么?
创建了Acceptor,EventLoopThreadPool(还没有开启loop线程)
首先Acceptor创建了一个非阻塞的listenfd,然后把它打包成1个acceptchannel,准备往mainloop的poller上扔。
然后设置了一些tcp选项,创建socket,绑定bind,给channel设置回调,就是readcallback
也就是说:Acceptchannel只关心读事件。
只关心acceptchannel有新用户的连接的事件,底层的channel会去执行readcallback,对于Acceptor的readcallback执行的是handleRead
我们回过来继续看tcpserver的构造函数,接下来创建了EventLoopThreadPool(还没有开启loop线程),
然后执行了setNewConnectionCallback,我们刚才看到acceptor的构造函数
绑定了一个setReadCallback,也就是说,有新用户的连接,底层的channel就会去执行readcallback,因为有新用户的连接,响应了epollin事件,然后就执行readcallback
对于acceptor的setReadCallback执行的就算是Acceptor::handleRead。
我们进去看看handleread,会发现执行的是newConnectionCallback这个回调
这个里面的回调newConnectionCallback是谁设置的?通过setNewconnectionCallback设置的
这个setNewconnectionCallback是谁设置的?是最上层TcpServer设置的!
由上图,我们也就知道了,注册在mainloop上的acceptor,当有新用户连接,成功以后,经过上面一串,最终执行的是TcpServer的newConnection方法。
我们继续看TcpServer
3、start()
3.1 开启loop
通过atomic变量started控制一下,注意要将其初始化为0,只能取启动1次。
我们看第一句代码,threadpool启动底层线程池,创建loop子线程,并且开启loop的loop()
子线程创建执行的就是
我们设置的线程的回调方法就是
58行马上就启动loop()了
启动的时候,为了能把处于睡眠中的subloop唤醒,每个loop都有一个wakeupfd注册在相应loop的子线程的poller上。
接着我们看start的第二句
执行acceptor的listen方法,把acceptorchannel注册在baseloop上的poller上。
这一切就都准备好了
main函数最后一句开启baseloop的loop
我们在调用的三步骤:
1、构建TcpServer对象,同时包含了注册回调,设置底层线程的个数,和图中扩展的内容。
2、start开启loop子线程,注册wakeupfd,能够让主线程mainloop来唤醒子线程loop。然后acceptor listen,把listenfd打包成一个acceptorchannel注册到baseloop上。
3、最后启动loop();
我们看看acceptor的listen
muduo库:mainloop和subloop之间并没有同步队列,并没有使用生产者消费者模型:mainloop生成连接,放到同步队列里面,subloop从同步队列去自己取连接,做成异步的生产连接和消费连接。并没有。
而是用系统调用eventfd,创建wakeupfd,可以用它直接做线程间的notify,就是通知,唤醒,效率是非常非常高的。
在libevent上用的是socketpair这个系统调用,创建的是基于Unix本地通信的双向通信的套接字。
我们继续看,现在有一个新的连接了,mainloop就要返回了,执行acceptorchannel的readcallback,最终调用的是TCPserver的newconnection,就是有一条新的连接来了。
首先,通过轮询算法选择一个subloop,让这个ioloop指针指向它
接着创建1个tcpconnection对象
然后设置了一些回调,这些回调将来由TcpConnection设置到底层的channel里面。
主要的是设置了CloseCallback,当fd被关闭的时候,最终回调到TcpServer 的removeconnection,最终执行下面这个方法
这个ioLoop是肯定是在当前的主线程里去执行的,但是主线程访问ioLoop访问的肯定是一个子线程对应的loop,所以这里的ioLoop->runInLoop肯定不是在主线程里面执行的,除非是没有设置子线程(setthreadnum),ioLoop永远指向baseLoop。
我们看看runInLoop函数
如果执行的loop就在当前线程中(没有设置子线程数量),直接执行回调cb(),否则queueInloop。
queueInloop就是:每一个eventloop都有一个成员变量:wakeupfd,现在要在一个subloop里面执行下面这个方法:
通过这个指针找到subloop的wakeupfd,往wakeupfd上写数据,把相应的subloop唤醒,唤醒以后就去执行这个connectionEstablished,
3.2 连接的建立
我们进入connectEstablished
设置了状态,一个connection肯定是包含有一个channel,因为一个connection表示一个connectfd,只要有socketfd,就会打包成1个channel。
这里用channel绑定了当前 的TcpConnection,因为只有channel才能收到poller给它通知的事件回调,它执行的事件回调都是TcpConnection设置给它的,如果TcpConnection由于一些原因这个对象TcpConnection没有了,那channel到时候还执行不执行回调?
所以,channel在这里使用了 weakptr弱智能指针来记录了这个TcpConnection对象,到时候通过弱智能指针的提升来监测TcpConnection是否存活,存在就执行相应的回调,不存在就不执行了。避免了错误发生。
然后channel调用enableReading函数,向相应的poller注册channel,唤醒后把当前TcpConnectionchannel注册在它选择的某个subloop上了,然后再执行connectionCallback,对于测试代码来说,我们看到的就是下图代码中,日志的打印显示建立的成功。
在主函数调用中,新连接来了,就是上述的处理过程。
3.3 数据的收发
连接成功,有数据通信怎么办?
对于新连接来说,都是enabler eading注册了socketfd的epollIn事件,如果有相应的可读事件到来,那么相应的loop线程的poller就会返回,返回以后就执行相应的channel的readcallback事件
通过的是
执行的就是Tcpconnection的handleread
handleread开始读数据,数据读上来
调用messagecallback,相应的响应就是给上层应用报上来,onMessage这个函数被调用了,回调给用户。
通过buf->retrieveALLAsString拿到原始字符串,反序列化得到json或者pbbuff,然后业务处理,然后响应send。
所以说,相应的channel有可读事件发生,subloop就会回调channel的readcallback,tcpconnection给相应的channel的readcallback绑定的就是tcpconnection的handleread,handleread通过inputbuffer输入缓冲区读取相应的socketfd上的数据,读取完,回调用户设置的messagecallback,
然后用户这里就响应了
4、连接的关闭
如果有异常,对端关闭了或者是当前服务端在这里主动调用shutdown,底层的channel都会响应closecallback
这个closecallback,TCPconnection设置的是哪个回调?handleClose
handleclose执行disableAll把对应的channel和感兴趣的事件从poller上删除。
然后执行用户的回调。
对于使用者来说,onconnection又被回调了,这次是断开连接的回调。
执行tcpconnection的
执行TcpServer::newConnection的
就调用到removeConnection
执行removeConnectionInLoop
在TCPserver里,有一个connectionMap,把这个connection从connectionsmap删除掉,然后getloop,获取这条连接所在的子loop,执行connectDestroyed,
因为已经在handleclose中设置成kDisconnected了,所以此处进不去if语句,直接执行channel_->remove(),删掉channel,如果从其他地方进来,状态是已连接的,则执行if语句,断开连接