muduo中TcpServer、TcpConnection执行过程

muduo中的Acceptor类的主要功能是socket、bind、listen,Acceptor用于accept接受TCP连接

一般来说,在上层应用程序中,我们不直接使用Acceptor,而是把它作为TcpServer的成员
 

TcpServer还包含了一个TcpConnection列表这是一个已连接列表。TcpConnection与Acceptor类似,有两个重要的数据成员,Socket与Channel。

所以说,Acceptor用于accept接受TCP连接。Acceptor的数据成员包括Socket、Channel,Acceptor的socket是listening socket(即server socket)。Channel用于观察此socket的readable事件,并回调Acceptor::handleRead(),后者调用acceptor来接受新连接,并回调用户callback。

 

下面分析一个程序的执行步骤:

#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/InetAddress.h>

#include <stdio.h>

using namespace muduo;
using namespace muduo::net;

void onConnection(const TcpConnectionPtr& conn)
{
  if (conn->connected())
  {
    printf("onConnection(): new connection [%s] from %s\n",
           conn->name().c_str(),
           conn->peerAddress().toIpPort().c_str());
  }
  else
  {
    printf("onConnection(): connection [%s] is down\n",
           conn->name().c_str());
  }
}

void onMessage(const TcpConnectionPtr& conn,
               const char* data,
               ssize_t len)
{
  printf("onMessage(): received %zd bytes from connection [%s]\n",
         len, conn->name().c_str());
}

int main()
{
  printf("main(): pid = %d\n", getpid());

  //构造一个地址对象
  InetAddress listenAddr(8888);
  EventLoop loop;

  TcpServer server(&loop, listenAddr, "TestServer");
  //连接到来的回调函数
  server.setConnectionCallback(onConnection);
  //消息到来的回调函数
  server.setMessageCallback(onMessage);
  //启动
  server.start();

  loop.loop();
}

分析:

程序后面的acceptor_->setNewConnectionCallback(boost::bind(&TcpServer::newConnection, this, _1, _2));这个回调函数的注册对应的是acceptor_,也就是说Acceptor的handleRead()执行后会调用上层的(也就是TcpServer)newConnection函数。

TcpServer server(&loop, listenAddr, "TestServer");

TcpServer::TcpServer(EventLoop* loop,
                     const InetAddress& listenAddr,
                     const string& nameArg,
                     Option option)
  : loop_(CHECK_NOTNULL(loop)),
    ipPort_(listenAddr.toIpPort()),
    name_(nameArg),
    acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)),
    threadPool_(new EventLoopThreadPool(loop, name_)),
    connectionCallback_(defaultConnectionCallback),
    messageCallback_(defaultMessageCallback),
    nextConnId_(1)
{
  //对accptor_设置一个回调函数
  //Acceptor::handleRead函数中会回调用TcpServer::newConnection
  //_1对应的是socket文件描述符,_2对应的是对等方的地址
  acceptor_->setNewConnectionCallback(
      boost::bind(&TcpServer::newConnection, this, _1, _2));
}

 启动,该函数可以跨线程调用,主要就是要执行Acceptor中的listen,也就是监听

server.start();

//该函数可以跨线程调用
void TcpServer::start()
{
  if (started_.getAndSet(1) == 0)
  {
    threadPool_->start(threadInitCallback_);

    //判断是否处于监听状态
    assert(!acceptor_->listenning());
    loop_->runInLoop(
		//现在服务器处于监听的状态了
        boost::bind(&Acceptor::listen, get_pointer(acceptor_)));
  }
}

关注监听套接字对应通道的可读事件 

void Acceptor::listen()
{
  loop_->assertInLoopThread();
  listenning_ = true;
  //监听
  acceptSocket_.listen();
  //关注可读事件,TcpConnection所对应的通道加入到Poller关注
  acceptChannel_.enableReading();
}

事件循环

loop.loop();

void EventLoop::loop()
{
    .....
    //遍历活动通道进行处理
    for (ChannelList::iterator it = activeChannels_.begin();
        it != activeChannels_.end(); ++it)
    {
      //当前的处理通道
      currentActiveChannel_ = *it;
	  //调用handleEvent处理通道
      currentActiveChannel_->handleEvent(pollReturnTime_);
    }
    ..... 
  }

当监听到一个连接到来的时候,在TcpServer.cc的构造函数中的setNewConnctionCallback,对Acceptor的acceptor_设置一个回调函数,当一个连接事件到来的时候,Poller的poll函数返回通道,调用了通道的handleEvent(),又调用了Acceptor的handleRead(),在TcpServer中注册的回调函数是TcpServer::newConnection 

所以先执行的Acceptor中的handleRead(),然后再执行TcpServer中的newConnection()

在handleRead()中得到已连接套接字,并传递给TcpServer的函数newConnection()

void Acceptor::handleRead()
{
  loop_->assertInLoopThread();
  //准备一个地址,接受对等方地址
  InetAddress peerAddr;
  //FIXME loop until no more
  //得到了一个连接
  int connfd = acceptSocket_.accept(&peerAddr);
  if (connfd >= 0)
  {
    // string hostport = peerAddr.toIpPort();
    // LOG_TRACE << "Accepts of " << hostport;
    //如果上层定义了回调函数,则执行
    if (newConnectionCallback_)
    {
      //传递套接字以及对等方的地址
      newConnectionCallback_(connfd, peerAddr);
    }
    else
    {
      sockets::close(connfd);
    }
  }
  //失败
  else
  {
    LOG_SYSERR << "in Acceptor::handleRead";
    // Read the section named "The special problem of
    // accept()ing when you can't" in libev's doc.
    // By Marc Lehmann, author of libev.
    if (errno == EMFILE)
    {
      //把这个空闲的文件描述符关闭掉,腾出一个位置
      ::close(idleFd_);
      idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
	  //接受完以后关闭掉
      ::close(idleFd_);
      idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
    }
  }

 在函数newConnection()中创建连接对象并放入map容器进行管理,然后调用TcpConnection中的connectEstablished,进行连接的管理

//一个新的连接之后
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
  loop_->assertInLoopThread();
  //按照轮叫的方式选择一个EventLoop	
  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));
  //将连接对象放到一个map容器中
  connections_[connName] = conn;
  对这个连接对象设置回调函数
  conn->setConnectionCallback(connectionCallback_);
  conn->setMessageCallback(messageCallback_);
  conn->setWriteCompleteCallback(writeCompleteCallback_);
  conn->setCloseCallback(
      boost::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
  //不能够在当前线程中调用,应该让ioLoop所属的IO线程调用这个连接
  ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn));
}

在这个函数中关注已连接通道的可读事件,此时是已连接状态,回调用户注册的连接建立函数。这个是在TcpServer的newConnection函数中注册的。 

//当连接到来的时候
void TcpConnection::connectEstablished()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnecting);
  setState(kConnected);
  //tie的参数是shared_ptr,shared_from_this()获得一个自身的share_ptr对象
  
  channel_->tie(shared_from_this());
  //TcpConnection所对应的通道加入到Poller关注
  channel_->enableReading();

  //这是用户的回调函数
  connectionCallback_(shared_from_this());
}

上面这个程序在连接到来的时候,将TcpConnection所对应的通道加入到Poller关注,此时,如果有可读事件发生,就会由handleEvent()->handleRead(),就可以处理连接通道中的可读事件了

读走数据并且执行回调函数messageCallback_(),这个函数是由用户注册到TcpServer,然后由TcpServer.cc中的newConnection()函数又注册到TcpConnection中。

//当一个消息到来的时候,调用handleRead
void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_->assertInLoopThread();
  int savedErrno = 0;
  ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
  if (n > 0)
  {
    //shared_from_this()的作用是转为shared_ptr
    messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
  }
  //处理连接断开
  else if (n == 0)
  {
    handleClose();
  }
  else
  {
    errno = savedErrno;
    LOG_SYSERR << "TcpConnection::handleRead";
    handleError();
  }
}

一个服务器维护一个已连接列表,当一个连接断开的时候,TcpConnection中的通道处于活跃状态,EventLopp的事件循环返回这个活跃的通道,并且调用handleEvent函数来处理,回调TcpConnection的handleRead函数,在这个函数中,又调用了read,这个时候read返回为0,又调用了handleClose函数,这个函数中又回调了TcpConnection的removeConnection,erase将这个连接对象从列表中移除。按照正常的思路,我们还应该将这个对象销毁掉,但是在这里我们不能立即销毁这个连接对象,如果销毁了这个对象,TcpConnection所包含的Channel对象也就跟着销毁了。而当前正在调用这个Channel对象的handleEvent函数,而这个Channel对象又销毁了,就会出现coredump。因而这个不能销毁TcpConnection对象,也就是说TcpConnection对象的生存期应该长于HandleEvent函数,如果做到这一点,可以利用shared_ptr来管理TcpConnection对象。

当连接到来,创建一个TcpConnection对象,立刻用shared_ptr来管理,这时候引用计数为1。

在Channel中维护一个weak_ptr(tie_),将这个shared_ptr对象赋值给tie_,因为是弱引用,所以引用计数不会加1。

当连接关闭,调用了Channel的handleEvent函数,在这个函数中,将tie_提升,得到一个shared_ptr对象,此时引用计数为2。

erase从列表中移除,引用计数减1,还剩1,因而TcpConnection对象不会销毁。

此时,会调用queueInLoop将connectDestroyed放到EventLoop的functors中,这个时候引用计数加1,变为2。handleEvent函数返回之后,它所提升的那个shared_ptr对象销毁了,引用计数减1,变为1。boost::function调用用户的函数connCb,引用计数减1变为0。TcpConnection对象就销毁了。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值