muduo网络库脉络分析(2)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Shreck66/article/details/50948878

因为此篇博文是上篇博文的延续,所以读者在阅读此时最好能先去看一下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采取的多线程负载均衡的策略

猜你喜欢

转载自blog.csdn.net/Shreck66/article/details/50948878