muduo网络库脉络分析(2)

来源:互联网 发布:手机添加网络 编辑:程序博客网 时间:2024/05/18 03:07

转自:http://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_;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

一个具有事件分发功能的类,最起码得拥有的数据成员有,分发哪个文件描述符上的事件即上面数据成员中的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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

为了让读者更加清楚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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

(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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

TcpConnection对外提供的主要接口如上并不复杂,复杂的是其内部实现的各种事件处理函数,所以读者要想明白muduo库是如何处理可读,可写,关闭等事件,需自己去看一下下面几个TcpConnection的私有成员函数

//处理读事件void handleRead(Timestamp receiveTime);//处理写事件void handleWrite();//处理关闭事件void handleClose();//处理错误事件void handleError();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

需要强调的一点是这些处理函数都是要传给TcpConnection对应的Channel的

关于TcpConnection类还有值得强调的一点就是

enum StateE { kDisconnected, kConnecting, kConnected, kDisconnecting };
  • 1

上面枚举了每个TcpConnection对应的几种状态,可别小看这东西,他们可是muduo用于处理服务器中过期事件的常见问题的关键所在

(5)EventLoopThreadPool类

muduo的TcpServer类通过该类创建多个线程,且每个线程上都运行这一个loop循环 
EventLoopThreadPool主要数据成员

  int numThreads_;  int next_;  boost::ptr_vector<EventLoopThread> threads_;  std::vector<EventLoop*> loops_;
  • 1
  • 2
  • 3
  • 4

(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();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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));}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

我们可以看到每次获得新连接的套接字后,newConnection会先调用threadPool_->getNextLoop()来轮流获得各个线程中的EventLoop循环,然后在构造新的TcpConnection时会将此EventLoop的地址传给该新连接对象,以此种方式来平均的将各个连接分配给各个loop循环,这就是muduo采取的多线程负载均衡的策略


原创粉丝点击