Table of Contents:

学习muduo库

TCP网络编程最本质的是处理三个半事件:
1.连接的建立,包括服务端接受(accept)新连接和客户端成功发起(connect)连接。TCP连接一旦建立,客户端和服务端是平等的,可以各自收发数据。
2.连接的断开,包括主动断开(close、shutdown)和被动断开(read(2)返回0)。
3.消息到达,文件描述符可读。这是最为重要的一个事件,对它的处理方式决定了网络编程的风格(阻塞还是非阻塞,如何处理分包,应用层的缓冲如何设计,等等)。
3.5 消息发送完毕,这算半个。对于低流量的服务,可以不必关心这个事件;另外,这里的“发送完毕”是指将数据写入操作系统的缓冲区,将由TCP协议栈负责数据的发送与重传,不代表对方已经收到数据。

常见问题

  1. 用户的回调是如何一步一步传到框架代码并被调用的
conn->setConnectionCallback(connectionCallback_);        //用户传
conn->setMessageCallback(messageCallback_);              //用户传
conn->setWriteCompleteCallback(writeCompleteCallback_);  //用户传
conn->setCloseCallback(std::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe

为什么需要buffer

int buffer
- TCP是一个无边界的字节流协议,接收方必须要处理“收到的数据尚不构成一条完整的消息"

output buffer
- 发送数据时,一次发很多,发不完,不能阻塞,得根据滑动TCP窗口得等对端收了后再发
- 如果buffer里还有待发送的20kB数据,程序又写入了50kB,那么网络库不应该直接调用write(),而应该把这50kB数据append在那20kB数据之后
- 如果output buffer里还有待发送的数据,而程序又想关闭连接,得等数据发送完毕再关

如何设计并使用缓冲区?
Buffer的设计要点:
·对外表现为一块连续的内存(char* p, int len),以方便客户代码的编写。
·其size()可以自动增长,以适应不同大小的消息。

Buffer大小的设计?
一方面我们希望减少系统调用,一次读的数据越多越划算。另一方面希望减少内存占用。如果有10000个并发连接,每个连接一建立就分配各50kB的读写缓冲区的话,将占用1GB内存。
在栈上准备一个65536字节的extrabuf,然后利用readv()来读取数据,iovec有两块,第一块指向muduo Buffer中的writable字节,另一块指向栈上的extrabuf。这样如果读入的数据不多,那么全部都读到Buffer中去了;如果长度超过Buffer的writable字节数,就会读到栈上的extrabuf里,然后程序再把extrabuf里的数据append()到Buffer中
这么做利用了临时栈上空间 14 ,避免每个连接的初始Buffer过大造成的内存浪费,也避免反复调用read()的系统开销

连接建立过程

一个连接的生命周期过程,如何被创建,如何被销毁?

连接断开过程

对象的生命周期

对于使用muduo库而言,只需要掌握5个关键类:EventLoop、TcpServer、TcpClient 、TcpConnection、Buffer。
TcpConnection 的生命期依靠shared_ptr管理(即用户和库共同控制)。
Buffer 的生命期由 TcpConnection 控制。
其余类的生命期由用户控制

Buffer和InetAddress具有值语义,可以拷贝;其他class都是对象语义,不可以拷贝

TCP分包,解码器

常见分包方法;
1.消息长度固定,比如muduo的roundtrip示例就采用了固定的16字节消息。
2.使用特殊的字符或字符串作为消息的边界,例如HTTP协议的headers以“\r\n”为字段的分隔符。
3.在每条消息的头部加一个长度字段。
4.利用消息本身的格式来分包,例如XML格式的消息中<root>...</root>的配对,或者JSON格式中的{ ... }的配对。解析这种消息格式通常会用到状态机(state machine)。

以第三种方式为例:
增加一个 LengthHeaderCodec中间层来进行编码解码,真正的逻辑通过设置回调函数设置到 LengthHeaderCodec 中。
- 当消息来时:
1. TcpServersetMessageCallback 设置成 LengthHeaderCodeconMessage, 业务逻辑的 onMessage回调设置到 LengthHeaderCodec
2. 先回调 LengthHeaderCodeconMessage, 再调用业务逻辑的 onMessage
- 当发消息时:先调用 LengthHeaderCodecsend , 封好包之后再调用网络的 send

image.png

消息分发器(dispatcher)

image.png
image.png

子线程启动eventLoop,主线程send

例子代码:example/asio/chat/client.cc
比如:客户端的例子。它要读取键盘输入,而EventLoop是独占线程的,所以需要两个线程:main()函数所在的线程负责读键盘,另外用一个EventLoopThread来处理网络IO。

  1. onConnection回调时,把 TcpConnectionPtr 保存起来
  2. 当有键盘输入时,在主线程调用 TcpConnectionPtr 进行消息的发送 conn->send(&buf);
  3. 调用 loop_->runInLoop( ), 把可调用对象插入到 eventLooppendingFunctors_
  4. 在loop循环的最后调用可调用对象进行真正的发送逻辑
image.png

base模块

通用

线程

事件循环

定时器模块

线程模块

网络模块

服务器端

客户端

protobuf

Http服务器