1.TCP网络编程最本质是的处理三个半事件
-
连接建立:服务器accept(被动)接受连接,客户端connect(主动)发起连接
-
连接断开:主动断开(close、shutdown),被动断开(read返回0,指对等端主动断开连接)
-
消息到达:文件描述符可读
-
消息发送完毕:这算半个。对于低流量的服务,可不必关心这个事件;这里的发送完毕是指数据写入操作系统缓冲区,将由TCP协议栈负责数据的发送与重传,不代表对方已经接收到数据。
-
下面我们重点讨论一下消息到达和消息发送完毕事件
-
消息到达事件
(1)一个socket有2个缓冲区:接收缓冲区,发送缓冲区
(2)socket有数据到来时,数据先被内核接收,存放在内核接收缓冲区中,网络库事件循环中的可读事件被触发,将数据从内核缓冲区移动到应用缓冲区中,并且网络库会回调一个OnMessage函数表示有消息到达。
由于TCP接收到的可能是半包(即数据不完整),OnMessage要根据协议来判断这个数据包是否完整。若不是完整的数据包,OnMessage会立刻返回,直到内核又接收了一些数据,此时网络库中事件循环中的可读事件又触发了,又把数据从内核缓冲区移动到应用层缓冲区中,再次回调OnMessage来判断这个数据包是否完整;
而如果TCP接收到的数据包是完整的,就直接将这个数据包取出来进行处理(read->decode解包->compute->encode打包->write);
接着继续判断,后续的内容是否是一个完整的包,若数据包不完整,则OnMessage立即返回,若数据包完整,则还要继续处理;
-
消息发送完毕事件
(1)假设现在应用层要发送数据,如果内核发送缓冲区足够大,则将要发送的数据全部填入内核缓冲区中,并触发一个发送完成事件,并且网络库会回调一个OnWriteComplete函数表示消息发送完毕;而如果内核发送缓冲区不足以容纳这么多的数据,则只将其中一部分数据填入内核缓冲区,剩余部分追加到应用层的发送缓冲区中,等内核发送缓冲区将数据发送出去后,会触发一个可写事件,在这个事件中就会将应用层发送缓冲区中的数据填充到内核发送缓冲区中,这时如果内核发送缓冲区不足以容纳应用层发送缓冲区中的全部数据,则只将其中一部分数据填入内核缓冲区…就这样直到应用层发送缓冲区中的数据全部填充到内核缓冲区,网络库事件循环中的发送完成事件会被触发,并回调OnWriteComplete表示消息发送完毕。
(2)注意:应用层一定要在应用层发送缓冲区中的数据全部填入内核缓冲区并回调OnWriteComplete表示消息发送完毕后才能继续发送数据,这样做可以避免数据丢包。
2.EchoServer类图
- 类图:EchoServer类看看muduo库对这三个半事件的封装
(1)通过TcpServer提供的三个接口:setConnectionCallback等来注册三个回调函数,将上层的回调函数注册进来。这里三个半事件,用了三个回调函数,因为连接和断开通过一个标记用的都是setConnectionCallBack_
(2)EchoServer包含了一个TcpServer对象,(就是基于对象的编程思想!!)。在EchoServer的构造函数中会去调用TcpServer的三个函数,将回调函数注册进去:它要注册的回调函数是EchoServer的成员函数,连接建立与连接断开onConnection函数,将connectionCallback_注册进去;消息到达事件onMessage,将setMessageCallbak注册进去。到时候连接建立或者断开了就会调用onConnection,消息到达事件产生了就会调用onMessage函数
也就是编程的时候,只需要定义一个服务器类EchoServer,将TcpServer包含进来(对象),在EchoServer中提供感兴趣的几个回调函数,在构造函数中调用TcpServer的接口,将EchoServer的回调函数注册进去;
(3)若是面向对象编程思想,则setConnectionCallback等三个函数应该做成纯虚函数,EchoServer继承至TcpServer,通过虚函数来调用的时候会调用派生类所实现的三个虚函数。
- eg:examples\simple\echo\main.cc
examples\simple\echo\echo.cc
3.什么都不做的EventLoop
-
one loop per thread意思是说每个线程最多只能有一个EventLoop对象。
multiple reactors+threadpool(one loop per thread + threadpool);
每一个reactor都是一个线程; -
创建了EventLoop对象的线程称为IO线程,其功能是运行事件循环(EventLoop::loop)。
没有创建EventLoop对象的线程就不是IO线程。 -
EventLoop对象构造的时候,会检查当前线程是否已经创建了其他EventLoop对象,如果已创建,终止程序(LOG_FATAL)
-
EventLoop构造函数会记住本对象所属线程(threadId_)。
-
eg:25\jmuduo\muduo\net\EventLoop.h
25\jmuduo\muduo\net\EventLoop.cc
25\jmuduo\muduo\net\CMakeLists.txt -
eg测试:25\jmuduo\tests\Reactor_test01.cc
25\jmuduo\tests\Reactor_test02.cc
25\jmuduo\tests\CMakeLists.txt -
测试:多个线程可以创建EventLoop对象,这些线程都是I/O线程
错误的eg