muduo 网络库补充

muduo 网络库简要梳理

网络模型:  one loop per thread + thread pool 的 reactor模型。

每个线程最多可以有一个自己的EventLoop,多个线程间也可以共享一个EventLoop。 

使用主 EventLoop 连接线程 + 从 EventLoop工作线程 + 非阻塞IO模式。工作线程从线程池分配,如果线程池大小为0,则共用一个线程。

1 关键技术

1 每个线程最多可以有一个EventLoop,并启动一个loop事件循环。

2  EventLoop主线程职责:监听新连接、定时器、信号事件。

3  EventLoop工作线程职责:处理连接的读写操作。

4  EventLoop使用runInLoop(Func)函数,完成线程安全操作,即如果是当前 EventLoop线程Func则直接调用,如果是其他线程则加入 EventLoop 事件中。可以避免多线程操作频繁的加锁。

TcpServer处理流程

1 初始化 EventLoop ,并通过updateChannel、runAt等等增加socket、定时事件。
2 EventLoop启动loop循环, 监听事件(新的客户端连接、数据读写、定时器、信号事件等),并处理激活的事件以及悬挂其他线程的事件。

3 TcpServer 通过 accept 检测 socket新连接,并为每一个连接创建一个TcpConnectionPtr( 可能在主线程或工作线程),用来管理每个连接的读写。

如图,一个EventLoop添加两个TcpServer,也可以添加更多,Reactor模型为 单Reactor + 多工作线程。

简化类图

2 TCP新建连接

流程: 当有新连接到达,Channel::handleEvent处理请求,最终调用newConnection,TcpServer为该连接创建一个TcpConnection实例,调用connectEstablished将事件注册到Channel中。

TcpConnection是处理新连接的核心,也是muduo最核心最复杂的类,表示一次新的连接,管理该socket的所有读写消息,连接断开时该对象也随之销毁。TcpConnection生命周期比较模糊,因为用户可以持有,TcpConnection所在线程可能是TcpServer的loop,也有可能是线程池分配的loop

3 TCP断开连接

服务端半关闭机制
1  shutdown仅仅shutdownWrite,state为kConnected->kDisconnecting.
2  此时客户端会收到字节0,按正常逻辑客户端会关闭连接(潜在风险,如果客户端未关闭,则服务端一直处于半关闭状态,消耗系统资源)
3  服务端会收到字节为0的消息,此时调用handleClose完全关闭,handleClose通知TcpServer移除所持有的TcpConnectionPtr。
处理流程如下,调用removeConnectionInLoop移除TcpConnectionPtr的一个引用计数,在调用connectDestroyed将state调整kDisconnecting->kConnected,并在loop中移除该channel,handleClose()走完所有流程TcpConnection将进入析构销毁。

4 收发数据 

引入二级缓冲区

对于每一个客户端连接都会分配两个Buffer

接收缓冲区(inputBuffer):读取数据只需要关注数据是否完整,通过引入接收缓冲区解决掉粘包/拆包问题。
发送缓冲区(outputBuffer):发送数据要比读取数据困难,1 关注什么时候writable事件。 2 考虑发送速度数据高于接收数据速度,造成本地内存堆积。
muduo引入低水位WriteCompleteCallback、高水位HighWaterMarkCallback回调。发送缓冲区清空调用低水位回调,发送缓冲区超出上限调用高水位回调。


发送消息

1 判断发送缓冲区有无数据,如果没有数据且当前无消息发送,则直接调用sockets::write发送消息,否则等待。
2 发送消息后判断发送的消息是否有剩余,如果没有剩余且发送缓冲区已清空,则调用低水位回调。
3 如果有剩余,将剩余数据追加到发送缓冲区outputBuffer,使能此IOsocket可写。并判断outputBuffer总大小是否超过高水位门限,
如果超过调用高水位回调。

接收消息

1 Channel::handleEvent判断为接收消息后,TcpConnection::handleRead处理消息。
2 先将消息读入到inputBuffer,在回调给用户,这里的数据对于用户来说可能存在粘包/拆包现。
如果让用户只关心消息到达而不是数据到达,则需要引入一个中间件解决,
由于buffer设计存在预留区,可以在预留区存入消息长度通过buf->peek()获取到,从而方便的处理粘包/拆包。

5 接口

公开接口

Buffer:数据读写,
InetAddress:封装IPv4
EventLoop:事件循环(Reactor),每个线程只能有一个EventLoop实体,负责IO和定时器时间分配。
EventLoopThread:启动一个线程,在其中运行EventLoop::loop()
TcpConnection:网络库最核心最复杂的类,封装一次TCP连接,
TcpClient:网络客户端,发起连接重试功能
TcpServer:网络服务端,接收客户端连接
上述类中,TcpConnection生命周期由shared_ptr管理(即用户和库共同控制),Buffer生命周期由TcpConnection控制
其余生命周期由用户控制。

内部接口

Channel:selectable IO channel,负责注册与响应IO事件,处理IO事件。每个Channel对象只负责一个文件描述符(fd)的IO事件分发,但他不拥有这个fd。
是Acceptor、Connector、EventLoop、TimerQueue、TcpConnection的成员,生命周期由后者控制。
Socket:RAII,封装了一个file descriptor,并在析构时关闭fd,是Acceptor、TcpConnection的成员,生命期由后者控制。
SocketsOps:封装各类Sockets系统调用。
Pooler:PollPooler/EPollPooler的基类,是 EventLoop 的成员,只供EventLoop在IO线程调用,因此无需加锁,生命期由后者控制。
PollPooler/EPollPooler--只负责IO多路分发,不负责事件的分发(channel)
Connector:发起TCP连接,是TcpClient的成员,生命期由后者控制。
Acceptor:接受TCP连接,是TcpServer的成员,生命期由后者控制。
TimerQueue:用Timefd实现定时,是是EventLoop的成员,生命期由后者控制。
EventLoopThreadPool:用于创建IO线程池,用于把TcpConnection分配到某个EventLoop线程上,是TcpServer的成员,生命期由后者控制。

Buffer设计


 

结构:内部使用  std::vector<char> buffer_ ,易于使用,方便管理,对外表现为动态增长的连续空间,初始默认1024B。
通过readIndex,writeIndex元素,分别指向读位置与写位置(读位置结尾),将内存分为三部分(prependable,readable,writable)
数据操作 :每写一次数据,writeIndex向后滑动相应位置,每读一次数据,readIndex向后滑动响应位置。
挪移与扩容:Buffer自适应大小,当写入时writable数据不够用,先检测writable+prependable是否可用,如果可用,则先向前挪移在写入,否则扩大vector容量再写入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值