文章目录
- 一、muduo的IO模型
- 二、为什么non-blocking网络编程中应用层buffer是必须的
- 1. output buffer的必要性
- 2. input buffer的必要性
- 三、muduo库如何处理粘包问题
一、muduo的IO模型
event loop 是 non-blocking 网络编程的核心,在现实生活中,non-blocking 几乎总是和 IO multiplexing 一起使用,原因有两点:
- 没有人真的会编写循环轮询 (busy-pooling) 来检查某个 non-blocking IO 操作是否完成,这样太浪费 CPU资源了
- IO-multiplex 一般不能和 blocking IO 用在一起,因为 blocking IO read()/write()/accept()/connect() 都有可能阻塞当前线程,这样线程就没办法处理其他 socket上的 IO 事件了
为什么在non-blocking网络编程中,应用层buffer是必须的?
二、为什么non-blocking网络编程中应用层buffer是必须的
non-blocking IO的核心思想是避免阻塞在read()或write()或者其他IO系统调用上,这样可以最大限度地复用thread-of-control,让一个线程能服务于多个socket连接,IO线程只能阻塞在IO multiplexing函数上,如select/poll/epoll_wait
这样一来,应用层的缓冲是必须的,每个TCP socket都要有stateful的input buffer和output buffer
1. output buffer的必要性
考虑一个场景:程序想通过TCP连接发送100KB的数据,但是在write()调用中,操作系统只接受了80KB(受TCP advertised window的控制),此时你肯定不想原地等待,因为不知道会等多久(取决于对方什么时候接收数据,然后滑动TCP窗口)。程序不应该阻塞,而应该返回event loop。在这种情况下,剩余的20KB数据怎么办?
对于应用程序而言,它只管生成数据,它不关心到底数据是一次性发送还是分成几次发送,这些应该由网络库来操心,程序只要调用TcpConnection::send()就行了,具体如何发送,由网络库负责。网络库应该接管这剩余的20kB数据,把它保存在该TcpConnection的output buffer里,然后注册POLLOUT事件,一旦socket变得可写就立刻调用write系统调用发送数据。当然,这第二次write()也不一定能完全写人20kB,如果还有剩余,网络库应该继续关注POLLOUT事件;如果写完了20kB,网络库应该停止关注POLLOUT,以免造成busy loop。
如果应用程序此时又写入了50kB数据,而这时候output buffer里还有待发送的20kB数据,那么网络库不应该直接调用write(),而应该把这50kB数据append在那20kB数据之后,等socket变得可写的时候再一并写人。
如果outputbuffer里还有待发送的数据,而程序又想关闭连接(对应用程序而言,调用TcpConnection::send()之后他就认为数据迟早会发出去),那么这时候网络库不能立刻关闭连接,而要等数据发送完毕。
2. input buffer的必要性
TCP是一个无边界的字节流协议,接收方必须要处理 “收到的数据尚不构成一条完整的消息” 和 “一次收到两条消息的数据” 等情况。一个常见的场景是,发送方send()了两条1kB的消息(共2kB),接收方收到数据的情况可能是:
- 一次性收到2kB数据;
- 分两次收到,第一次600B, 第二次1400B;
- 分两次收到,第一次1400B, 第二次600B;
- 分两次收到,第一次1kB,第二次1kB;
- 分三次收到,第一次600B, 第二次800B,第三次600B;
- 其他任何可能。一般而言,长度为n字节的消息分块到达的可能性有
种
网络库在处理 “socket 可读” 事件的时候,在LT模式下,必须一次性把socket对应的内核TCP读缓冲区里的数据读完(从操作系统buffer搬到应用层buffer),否则会反复触发POLLIN事件,造成busy-loop。那么网络库必然要应对“数据不完整”的情况,收到的数据先放到input buffer里,等构成一条完整的消息,再通知程序的业务逻辑
三、muduo库如何处理粘包问题
粘包问题的最本质原因在与接收对等方无法分辨消息与消息之间的边界在哪。我们通过使用某种方案给出边界,例如:
- 发送定长包。如果每个消息的大小都是一样的,那么在接收对等方只要累计接收数据,直到数据等于一个定长的数值就将它作为一个消息。
- 包尾加上\r\n标记。FTP协议正是这么做的。但问题在于如果数据正文中也含有\r\n,则会误判为消息的边界。
- 包头加上包体长度。包头是定长的4个字节,说明了包体的长度。接收对等方先接收包体长度,依据包体长度来接收包体。
- 使用更加复杂的应用层协议。
muduo库采用的是第三种方案,包头存放包体长度
















