目录
Poller、EpollPoller - Demultiplex多路复用事件分发器
Thread、EventLoopThread和EventLoopThreadPool
4.有了一个新的连接之后怎么做,TcpServer::newConnection后续(连接的建立)
5. 连接成功,有数据通信怎么办?TcpServer::newConnection后续(数据的收发)
Channel
fd、events、revents、callback、
两种channel、listenfd-acceptorChannel 、connfd-connectionChannel
Channel主要做的事情就是封装了fd_, events_跟revents_,还有一组回调函数。
- fd_:表示要往poller上注册的文件描述符
- events_:事先设置的fd所感兴趣的事件(读事件或者写事件)
- revents_: poller最终给我们channel通知的这个fd上发生的事件,Channel根据相应的发生的事件来执行相应的回调。
Poller、EpollPoller - Demultiplex多路复用事件分发器
std::unordered_map<int, Channel*>
Channel和Poller之间的关系:
对于上层来说,如果有一个fd发生,它就会把这个fd打包成Channel通道,然后下发到poller上去,poller中有一个channels_容器,key是这个sockfd,值就是这个打包的channel:
也就是说如果poller检测到有哪个fd有事件发生了,它就可以通过发生事件的fd,再通过channels_这个map容器找到对应的channel,这个channel里面就记录了详细的事件回调:
总共有两种Channel,因为有两种fd:
一种是listenfd(依赖于acceptor),封装成了acceptorChannel;
一种是connfd,建立连接时用的,封装成connectionChannel。
listenfd发生的事件,其所调用的回调函数就是由acceptor设置的;已建立连接的客户端专门通信用的connfd,回调函数由TcpConnection设置。
①:Acceptor设置的回调函数:
②:TcpConnection设置的回调函数:
EventLoop - Reactor
①:using ChannelList = std::vector<Channel*>
ChannelList activeChannels_ //eventloop管理的所有channel
②:unique_ptr<Poller> poller_ //eventloop所管理的poller
③:int wakeupFd_ //loop通过该eventfd进行从mainloop到subloop的通知
主要作用:一个wakeupfd_隶属于一个loop,每一个loop都有一个wakeupfd_,因为loop最后执行的时候驱动底层的事件分发器,时epoll_wait,只要没有事件发生,相应的loop线程阻塞在epoll_wait上,如果我们想唤醒loop所在线程的阻塞状态,我们通过loop对象获取其wakeupfd_,直接在wakeupfd_上写入东西,相应的loop就会被唤醒。因为每一个wakeupfd_也被封装成wakeupChannel_,是loop的第一个成员变量,同时注册在自己loop底层的epoll事件分发器上。
④:unique_ptr<Channel> wakeupChannel_ //每一个线程都有一个wakeupFd_封装成channel
⑤:EventLoop上有一个vector (pendingFunctors),存了一堆的回调函数。因为这些回调,每一个回调在执行的时候都应该是在loop自己所在的线程执行,如果说当前线程调用了loop,让这个loop执行回调,在程序逻辑上会进行一个判断,如果当前线程就是这个loop那么直接执行回调,否则的话,就把它存到pendingFunctors这个vector里面,然后唤醒相应的loop,然后在vector里拿相应的回调执行。
Channel与Poller之间的通信:
但是Channel和Poller是相互独立的,它们不能直接相互通信,依赖EventLoop进行通信,所以Channel和Poller都有一个成员变量记录当前所在的EventLoop事件循环。
Channe、Poller、EventLoop逻辑:
Poller检测socket有相应事件发生,然后通过EventLoop调用Channel相应的,fd发生事件的回调函数。
Thread、EventLoopThread和EventLoopThreadPool
EventLoop* getNextLoop() : 通过轮询算法获取下一个subLoop,默认指向baseLoop
- Thread类是底层的线程
- EventloopThread类是事件的线程;
- EventLoopThreadPool是事件循环线程池:EventloopThreadPool里面有一个方法: getNextloop方法,默认是通过轮询算法获取下一个subloop; 如果客户没有提供setthreadnumber设置线程,那么就是没有创建subloopgetNextloop永远返回的是baseloop,当我们通过setthreadnumber设置底层的线程数量EventloopThreadPool就会驱动底层开始创建线程,一个线程一个oop,就是one loop perthread!
EventLoopThreadPool
包括基本的mainLoop、线程的数量、存放所有EventLoop线程、以及所有EventLoop指针。
EventLoopThread
底层有当前loop和底层线程;
有一个EventLoopThread::startLoop()函数:
在EventLoopThread的构造函数中我们对创建的线程进行绑定了一个线程执行函数:
EventLoopThread::startLoop()和EventLoopThread::threadFunc()之间存在一个条件变量通信的问题:startLoop中创建完线程后阻塞,要等待threadFunc中创建好线程对应的Eventloop之后通知再继续运行,one loop per thread
loop相应初始化完成后,loop.loop()开启当前loop的事件循环-epoll_wait()。
Acceptor:
Acceptor封装了listenfd相关的操作,比如说:创建listenfd,绑定bind,listen,listen成功后,把listenfd打包,成acceptorChannel扔给baseloop来监听它的事件。
Buffer:
Buffer是缓冲区,对于nonblocking(非阻塞)的I/0,我们都需要设置缓冲区,涉及到应用写数据写到缓冲区中,再写到TCP的发送缓冲区,然后send数据。
因为TCP的发送缓冲区,写满了就要发送,这是同步的过程,效率比较慢。
TcpConnection:
一个连接成功的客户端对应一个TcpConnection
封装了loop,socket,channel,和各种回调函数((对于connfd在Channel上执行的回调,都是由TcpConnection来设置的),高水位线的控制(不要发送过快),发送和接收缓冲区.
TcpServer:
总领所有,
①:首先有一个Acceptor
②:EventLoopThreadPool线程池
通过Acceptor获得新用户,然后把新用户封装成Tcpconnection,设置各种回调函数,然后才可从EventLoopThreadPool中getnextLoop,选择一个subloop,把TcpConnection丢给相应subLoop
③:一堆callback回调函数;
④:ConnectionMap:保存所有连接
流程总结:
1.首先看看我们如何使用muduo:
①:首先定义一个EventLoop对象,这个是mainloop
②:创建一个地址,ip:127.0.0.1 、 port:8000
③:调用EchoServer构造函数,
1. 创建TcpServer; 2.保留loop; 3.设置两个回调函数;4.设置线程数(这个数量不包括baseloop)。很好的把网络代码和业务代码分离,我们只需要关注两个回调函数做什内容就好。
④:调用start();
⑤:启动主线程loop。
2.一步步来看:
1. TcpServer server_:
①:首先是Acceptor的创建:
- 设置非阻塞Sockt,listenfd;
- 打包成Channel;准备向mainloop的poller上传;
- 设置一系列的tcp选项,地址、端口重用,绑定地址;
- 给listenfd打包的acceptchannel设置回调函数,accept channel只关心什么?只关心读事件,也就是说呢,我们在这个网络库上只关心了accept channel有新用户连接的时候。
②:EventLoopThreadPool的创建 :
baseloop传进去,创建一个线程池,但是此时还有没有开启loop线程。
③: 有新用户的连接实际上是执行TcpServer::newConnection
2. TcpServer server_.start()
①:启动线程池
内部调用EventLoopThreadPool::start:
EventLoopThread::threadFunc函数创建loop并启动loop,epoll_wait开始阻塞监听。每一个loop中都有一个weakupfd_,保证唤醒对应睡眠中的subloop。
②:启用Acceptor的listen方法
相当于把acceptorchannel注册在mainloop的poller上。(把acceptorChannel注册在baseLoop上)
3. loop.loop()
开启baseloop的loop().
总结一下调用的三个步骤:
1、构建TcpServer对象,同时包含了注册回调,设置底层线程的个数,和扩展。
2、start开启loop子线程,注册wakeupfd,能够让主线程mainloop来唤醒子线程loop。然后acceptorlisten,把listenfd打包成一个acceptorChannel注册到baseloop上。
3、最后启动loop();
4.有了一个新的连接之后怎么做,TcpServer::newConnection后续(连接的建立)
主要就是:
- 从EventLoopThreadPool轮询拿到一个subloop;
- 然后构建一个TcpConnection对象,并设置回调函数到底层的Channel中去;
- 重要的是设置了一个removeCallback关闭连接的回调函数。
- 最后执行了一个ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
- 进入 connectEstablished:
①:
一个connection肯定是包含有一个Channel,因为一个Connection表示一个connectfd,就会打包成1个Channel。
用Channel绑定了当前TcpConnection(tie方法):
因为只有Channel才能收到Poller给它通知的事件回调,它执行的事件回调都是TcpConnection设置给它的,如果TcpConnection由于一些原因这个对象TcpConnection没有了,那Channel到时候就不会执行回调了。
所以,Channel在这里使用了weakptr弱智能指针来记录了这个TcpConnection对象,到时候通过弱智能指针的提升来监测TcpConnection是否存活,存在就执行相应的回调,不存在就不执行了。
②:
然后Channel调用enableReading函数,向相应的Poller注册Channel,唤醒一下subloop,把当前TcpConnectionchannel注册在它选择的某个subloop上;
③:
执行connectionCallback_,由Tcpsever构造时传入。
5. 连接成功,有数据通信怎么办?TcpServer::newConnection后续(数据的收发)
对于新连接来说,都是enableReading,注册了socketfd的epollin事件,如果有相应的可读事件到来,那么相应的loop线程的poller就会返回,返回以后就执行相应的Channel的readcallback_回调函数!
Channel中的readcallback_回调函数,由TcpServer给TcpConnection设置!
去到 TcpConnection::handleRead中:
我们在测试代码中将messageCallback设置为
6. 连接的关闭
如果有异常,对端关闭了或者是当前服务端业务执行完之后在这里主动调用shutdown,底层的Channel都会响应closecallback回调函数!
TcpConnection给Channel传入回调函数,实际上是TcpConnection从TcpServer处拿到的回调函数,再封装成handleClose,传给Channel!