当前位置: 代码迷 >> 综合 >> muduo net库学习笔记1——TCP网络编程的本质、 EchoServer类、EventLoop类的简化封装
  详细解决方案

muduo net库学习笔记1——TCP网络编程的本质、 EchoServer类、EventLoop类的简化封装

热度:25   发布时间:2024-01-29 16:40:39.0

TCP网络编程最本质是处理三个半事件
1,连接建立:服务器accept接收连接,客户端发起连接
2,连接断开:主动断开(close、shutdown),被动断开(read返回0)
3,消息到达:文件描述符可读
4,消息发送完毕:这算半个。对于低流量的服务,可不关心这个事件;这里的发送完毕是指数据写入操作系统缓冲区,将由TCP协议栈负责数据的发送与重传,不代表对方已经接收到数据

在接收缓冲区内:
当一个套接字有数据到来的时候,会先被内核接收,网络库的可读事件就被触发,可读事件触发将数据从内核缓冲区移动到应用层的缓冲区中,
并且网络库会回调一个函数OnMessage,但收到的数据包有可能不是一个完整的包,OnMessage中就需要根据协议判断这个数据包是否是一个完整的数据包,如果不是一个完整的数据包就OnMessage就立刻返回,知道内核中又接收了一些数据,网络库事件循环中的可读事件就又会被触发,数据就又会移动到应用层缓冲区尾部,OnMessage再判断是一个完整的数据包了,就取出数据包read、解包decode、计算处理compute、将处理结果打包encode、发送write

在发送缓冲区: write(buf,…)
将缓冲区buf的数据填充到内核缓冲区,如果内核缓冲区的数据足够的话就全部填充进去,网络库会触发调用一个OnWriteComplete发送完成事件;如果内核缓冲区不足以容纳数据,就要把数据追加到应用层的发送缓冲区中,当内核中的数据发送出去了,有足够的空间了就会触发套接口的可写事件,就会将应用层缓冲区的数据填充到内核发送缓冲区中,如果应用层的数据都发送完了就也触发OnWriteComplete这个事件

EchoServer类图

EchoServer类看看muduo库对这三个半事件的封装

类图:
在这里插入图片描述
通过TcpServer提供了三个接口来注册三个回调函数,连接和断开通过一个标记用的都是setConnectionCallBack_EchoServer包含了一个TcpServer对象,(就是基于对象的编程思想!!),在EchoServer的构造函数中会去调用TcpServer的三个函数,将回调函数注册进去:它要注册的回调函数是EchoServer的成员函数,连接建立与连接断开onConnection函数,将connectionCallback_注册进去;消息到达事件onMessage,将setMessageCallbak注册进去。到时候连接建立或者断开了就会调用onConnection,消息到达事件产生了就会调用onMessage函数
也就是编程的时候,只需要定义一个服务器类EchoServer,将TcpServer包含进来(对象),在EchoServer中提供感兴趣的几个回调函数,创建对象时会在TcpServer的构造函数中调用TcpServer的接口,将这些回调函数注册到EchoServer回调函数里去

在这里插入图片描述
在这里插入图片描述
它的main函数:
在这里插入图片描述首先定义了一个EventLoop的对象,这就是事件循环的对象
**?*每个线程都有一个EventLoop对象(最多只有一个,也可以没有,没有的话就不是IO线程)one loop per thread+threadpool
然后初始化一个地址对象listenAddr,
构造一个对象server,传入loop地址和地址对象,由类图可见TcpServer中由start(),最后的loop.loop()是在不断地捕捉事件,一旦事件到来就会回调EchoServer中相应的回调函数

EventLoop的封装

(点开就是吓一跳==)
先看看简化代码,了解这个类的实现过程后,再完善起来

.h文件?

namespace muduo
{
namespace net
{
///
/// Reactor, at most one per thread. EventLoop就是Recator模式的封装,每个线程最多一个
///
/// This is an interface class, so don't expose too much details.
class EventLoop : noncopyable
{public:typedef std::function<void()> Functor;EventLoop();~EventLoop();  // force out-line dtor, for std::unique_ptr members.////// Loops forever.////// Must be called in the same thread as creation of the object.///void loop();//事件循环函数,该函数不能跨线程调用void assertInLoopThread(){if (!isInLoopThread()){abortNotInLoopThread();}}bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); }
private:void abortNotInLoopThread();static EventLoop* getEventLoopOfCurrentThread();bool looping_; /* atomic */ //是否处于循环的状态const pid_t threadId_;//记录当前对象属于哪一个线程,当前对象所属线程id
};
}
}
#endif //MUDUO_NET_EVENTLOOP_H

.cc文件?
每一个线程最多有一个EventLoop对象,.cc文件第一行代码就定义一个线程局部存储的当前线程EventLoop对象指针__thread EventLoop* t_loopInThisThread = 0;,再提一下,因为有__thread这个关键字就表示每一个线程都有这样的一个变量,否则的话这个变量是共享的
创建对象成功的话,这个指针t_loopInThisThread就指向当前对象,当然这个对象得是第一次创建,因为对象最多只能有一个。
报错都用日志的记录方式

构造函数
初始化looping为false,表示当前还没有处于循环的状态,并把当前线程的真实id初始化给ThreadId_

EventLoop::EventLoop():looping(false),thraedId_(CurrentThread::tid())
{LOG_TRACE << "EventLoop create "<<this<<"in thread "<<threadId_;//日志的记录if (t_loopInThisThread)//检查当前线程是否已经创建了其他EventLoop对象,如果已经创建了则LOG_FATAL终止程序{LOG_FATAL << "Another EventLoop " << t_loopInThisThread<< " exists in this thread " << threadId_;}else{t_loopInThisThread = this;//既然创建了一个对象,那么当前的对象指针就指向this}
}

**析构函数:**指向对象的指针置空

EventLoop::~EventLoop()
{LOG_DEBUG << "EventLoop " << this << " of thread " << threadId_<< " destructs in thread " << CurrentThread::tid();t_loopInThisThread = NULL;
}

loop函数:事件循环
事件循环,该函数不能跨线程调用,只能在创建该对象的线程中调用

void EventLoop::loop()
{assert(!looping_);//断言还没有开始循环assertInLoopThread();//断言当前处于创建该对象的线程中,函数的实现?looping_ = true;//开始要事件循环了,先将looping_置为trueLOG_TRACE << "EventLoop " << this << " start looping";//打印事件::poll(NULL, 0, 5*1000);//NULL表示当前没有关注事件,关注事件个数为0,等待5秒,仅仅而已,走个场LOG_TRACE << "EventLoop " << this << " stop looping";//loop完也打印一下looping_ = false;//并置回false
}

assertInLoopThread()判断是都处于创建该对象的线程中

 void assertInLoopThread(){if (!isInLoopThread())//只需要将当前线程的id与threadId比较是否相等?{abortNotInLoopThread();//阻止程序?}}bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); }void EventLoop::abortNotInLoopThread()
{LOG_FATAL << "EventLoop::abortNotInLoopThread - EventLoop " << this<< " was created in threadId_ = " << threadId_<< ", current thread id = " <<  CurrentThread::tid();
}//这就是日志的用法啦,开心