因为此篇博文是上篇博文的延续,所以读者在阅读此时最好能先去看一下muduo网络库脉络分析(1)
Channel类
其实在上篇博文讲完Acceptor类之后,我因该按照流程顺序接着讲TcpConnection类的,但是因为TcpConnection中包含一个很重要的类Channel类,之前讲时也都有设计Channel类,但是我只是笼统的描述那是对事件的一个抽象,所以这里将Channel类,既可以帮助大家理解前面的内容,同时也使我在将之后的TcpConnection类时能够得心应手
Channel类的定义
EventLoop* loop_;
const int fd_;
int events_;
int revents_;
ReadEventCallback readCallback_;
EventCallback writeCallback_;
EventCallback closeCallback_;
EventCallback errorCallback_;
一个具有事件分发功能的类,最起码得拥有的数据成员有,分发哪个文件描述符上的事件即上面数据成员中的fd_,其次得知道该poller监控该文件描述符上的哪些事件,对应events_,接着就是当该文件描述符就绪之后其上发生了哪些事件对应上面的revents。知道了发生了什么事件,要达到事件分发的功能你总得有各个事件的处理回调函数把,对应上面的各种callback。最后该Channel由哪个loop_监控并处理的loop_
Channel提供的主要接口
//该接口便是Channel最核心的接口真正实现事件分发的函数
void handleEvent(Timestamp receiveTime);
//以下接口便是设置各种事件对应的回调函数
void setReadCallback(const ReadEventCallback& cb);
void setWriteCallback(const EventCallback& cb);
void setCloseCallback(const EventCallback& cb);
void setErrorCallback(const EventCallback& cb);
//该接口用来设置Poller需要监听Channel的哪些事件
void set_revents(int revt);
为了让读者更加清楚Channel的核心接口做了什么,这里简单的贴以下其源码
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
eventHandling_ = true;
LOG_TRACE << reventsToString();
if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
{
if (logHup_)
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
}
if (closeCallback_) closeCallback_();
}
if (revents_ & POLLNVAL)
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLNVAL";
}
if (revents_ & (POLLERR | POLLNVAL))
{
if (errorCallback_) errorCallback_();
}
if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
{
if (readCallback_) readCallback_(receiveTime);
}
if (revents_ & POLLOUT)
{
if (writeCallback_) writeCallback_();
}
eventHandling_ = false;
}
Channel的核心事件分发接口为handleEvent其调用了上面的handleEventWithGuard接口,其内部我相信写过I/O复用的读者一看便会知道它都干了些什么
(4)TcpConnection类
中途小插曲Channel类叙述完了,那我们就接着上篇博文讲,muduo会将和新连接相关的所有内容统一封装到TcpConnection中
TcpConnection主要数据成员定义如下
EventLoop* loop_;
boost::scoped_ptr<Socket> socket_;
boost::scoped_ptr<Channel> channel_;
ConnectionCallback connectionCallback_;
MessageCallback messageCallback_;
WriteCompleteCallback writeCompleteCallback_;
HighWaterMarkCallback highWaterMarkCallback_;
CloseCallback closeCallback_;
Buffer inputBuffer_;
Buffer outputBuffer_;
(1)标示一个连接最重要的当然是对应的套接字,上述定义了socket_(对套接字的封装)成员变量
(2)套接字有了其上对应的事件以及处理都将由和套接字对应的Channel来处理,其对应的变量为channel_
(3)由用户传入的各种回调
(4)每一个连接多会对应一对读写buffer对应上面的inputBuffer_和outputBuffer_(关于应用层buffer在网络库中的重要性请翻阅我之前的文章)
(5)每个连接所属的loop循环对应loop_
TcpConnection中提供的主要接口
//给连接发送数据的接口
void send(const StringPiece& message);
//关闭该连接写端的接口
void shutdown();
//强制关闭该连接的接口
void forceClose();
//以下接口为设置连接对应的各种回调
void setConnectionCallback(const ConnectionCallback& cb);
void setMessageCallback(const MessageCallback& cb);
void setWriteCompleteCallback(const WriteCompleteCallback& cb);
void setHighWaterMarkCallback(const HighWaterMarkCallback& cb, size_t highWaterMark);
void setCloseCallback(const CloseCallback& cb);
TcpConnection对外提供的主要接口如上并不复杂,复杂的是其内部实现的各种事件处理函数,所以读者要想明白muduo库是如何处理可读,可写,关闭等事件,需自己去看一下下面几个TcpConnection的私有成员函数
//处理读事件
void handleRead(Timestamp receiveTime);
//处理写事件
void handleWrite();
//处理关闭事件
void handleClose();
//处理错误事件
void handleError();
需要强调的一点是这些处理函数都是要传给TcpConnection对应的Channel的
关于TcpConnection类还有值得强调的一点就是
enum StateE { kDisconnected, kConnecting, kConnected, kDisconnecting };
上面枚举了每个TcpConnection对应的几种状态,可别小看这东西,他们可是muduo用于处理服务器中过期事件的常见问题的关键所在
(5)EventLoopThreadPool类
muduo的TcpServer类通过该类创建多个线程,且每个线程上都运行这一个loop循环
EventLoopThreadPool主要数据成员
int numThreads_;
int next_;
boost::ptr_vector<EventLoopThread> threads_;
std::vector<EventLoop*> loops_;
(1)创建多少个loop线程 对应成员numThreads_
(2)loops_用来保存每个loop循环的EventLoop的指针
(3)next为保存当前loops_的下标
(5)threads_保存运行loop循环的线程
EventLoopThreadPool提供的接口
//设置开启loop循环的线程数量
void setThreadNum(int numThreads);
//启动各个loop线程
void start(const ThreadInitCallback& cb = ThreadInitCallback());
//获得loops_中的下一个EventLoop地址
EventLoop* getNextLoop();
3.总结
到此位置我已从外到内的把muduo库中最核心的几个类一一介绍完了,此时我们可以再次回到最外层的TcpServer类看一看。其中有一个newConnection的私有成员函数,该函数是传给其成员变量acceptor_的新连接回调,即当baseloop中监听到有新连接到来时,会通过此回调处理新连接,那么它是咋么处理新连接的呢?
newConnection源码
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
loop_->assertInLoopThread();
EventLoop* ioLoop = threadPool_->getNextLoop();
char buf[64];
snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
++nextConnId_;
string connName = name_ + buf;
LOG_INFO << "TcpServer::newConnection [" << name_
<< "] - new connection [" << connName
<< "] from " << peerAddr.toIpPort();
InetAddress localAddr(sockets::getLocalAddr(sockfd));
// FIXME poll with zero timeout to double confirm the new connection
// FIXME use make_shared if necessary
TcpConnectionPtr conn(new TcpConnection(ioLoop,
connName,
sockfd,
localAddr,
peerAddr));
connections_[connName] = conn;
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback(
boost::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn));
}
我们可以看到每次获得新连接的套接字后,newConnection会先调用threadPool_->getNextLoop()来轮流获得各个线程中的EventLoop循环,然后在构造新的TcpConnection时会将此EventLoop的地址传给该新连接对象,以此种方式来平均的将各个连接分配给各个loop循环,这就是muduo采取的多线程负载均衡的策略