muduo整体介绍及Echo服务器流程分析

来源:互联网 发布:手机文件编辑软件 编辑:程序博客网 时间:2024/05/16 06:41

muduo是Ractor模式,整个核心是Reactor;EventLoop就充当了Reactor。下面就是muduo的简化类图结构:
这里写图片描述

EventLoop是one thread per loop中的loop,每个线程只能有一个EventLoop的实体,它来负责IO和定时器事件的分派。它用eventfd来异步唤醒,不同与传统的用一对pipe。它用TimerQueue作为计时管理,Poller实现IO multiplexing。

Poller是个抽象基类,它实现了pool/epoll的封装。派生类PollPoller实现了poll的封装;派生类EPollPoller实现了epoll的封装。

Channel是selectable IO channel,负责注册与响应IO事件。但是它没有file descriptor,它是TcpConnection、Acceptor、TcpConnection、TimeQueue的成员,其生命周期由后者控制。

Socket是一个RAII handle,封装了一个file descriptor,且在析构时关闭fd。它是Acceptor、TcpConnection的成员,生命周期由后者控制。EventLoop、TimeQueue中拥有fd,但是没有封装为Scokets class。

Connector用来发起TCP连接,它是TcpClient的成员,生命周期由后者控制。

Acceptor用来接收TCP连接,它是TcpServer的成员,生命周期由后者控制。

muduo网络库头文件关系如下:
这里写图片描述

其中白底为用户可见,灰底为用户不可见。

Edian.h封装了计算机字节顺序/网络字节顺序之间的转换函数,放在命名空间sockets。

SocketsOps.{h, cc}封装了了套接字fd的创建、连接、绑定、监听、关闭函数;还封装了网络地址间的转换,例如“1.2.3.4”/sockaddr_in.sin_addr.s_addr,scokaddr/sockaddr_in等之间的转换。

InetAddress.{h, cc}封装了InetAddress class,类中只有一个私有变量struct sockaddr_in addr_,封装了网络地址的一些操作。resolve实现了域名/主机名解析IP。

Socket.{h, cc}封装了fd,实现了fd常见的一些操作。它使用RAII手法,在析构时close(fd)。

下面以一个Echo服务器为例,讲解一下muduo实现Reactor的大概流程:
Echo源码为muduo自带的,为了便于理解,把封装的EchoServer拆开了。

#include <muduo/base/Logging.h>#include <muduo/net/TcpServer.h>#include <muduo/net/EventLoop.h>#include <boost/bind.hpp>//define callback functionvoid onConnection(const muduo::net::TcpConnectionPtr& conn){  LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> "           << conn->localAddress().toIpPort() << " is "           << (conn->connected() ? "UP" : "DOWN");}void onMessage(const muduo::net::TcpConnectionPtr& conn,                           muduo::net::Buffer* buf,                           muduo::Timestamp time){  muduo::string msg(buf->retrieveAllAsString());  LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, "           << "data received at " << time.toString();  conn->send(msg);}int main(){    LOG_INFO << "pid = " << getpid();    muduo::net::EventLoop loop;    muduo::net::InetAddress listenAddr(2007);    muduo::net::TcpServer server(&loop, listenAddr, "EchoServer");    server.setConnectionCallback(            boost::bind(onConnection,  _1));    server.setMessageCallback(            boost::bind(onMessage,  _1, _2, _3));    server.start();    loop.loop();    return 0;}

首先定义了两个callBack函数,这两个函数要有一定格式(传入参数和返回值类型),具体格式定义了Callbacks.h中。onConnection在连接到来时调用,onMessage在消息到来时调用,(具体怎么调用后面解释)。

在main函数首先定了EventLoop对象和TcpServer对象。在定义TcpServer时不仅仅绑定了端口,还传入了EventLoop对象的指针,在TcpServer中保存着EventLoop对象的指针,EventLoop指针只能在TcpServer初始化时指定,后面不能更改。TcpServer在构造函数中还绑定了新连接到来时调用的回调函数,用来处理accept返回的fd(这个绑定其实是Acceptor绑定TcpServer的函数)。

之后,TcpServer对象server设置了回调函数,这两个函数分别在连接建立和消息到达时调用。

调用server.start()时,会初始化server对象中的threadPool_(用来给后面新建连接使用),随后把listen函数放入到loop函数对象容器中(如果在同一个线程则会立即执行,否则在下一次执行完IO事件后执行listen)。listen封装在Acceptor类中,Acceptor在TcpServer创建时创建,也包含了EventLoop对象指针,还包含了channel类。在Acceptor对象初始化时会创建channel类对象且将其加入到EventLoop的监听事件集合中,当有连接到来时,回调函数为void Acceptor::handleRead,在回调函数中,接受新连接,并调用newConnectionCallback_(即TcpServer::newConnection),接收新连接(保存为TcpConnection)并设置新连接回调函数,将新连接加入到另一个EventLoop中,在这个EventLoop中处理接收和发送。

以上大概就是Echo服务器的流程。

0 0
原创粉丝点击