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 function
void 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服务器的流程。
版权声明:本文为博主原创文章,未经博主允许不得转载。