[Muduo Source Code Analysis] Interpretation of Muduo's Tcpserver and Acceptor

Keywords: socket network

This paper mainly analyses how the server establishes the connection in the Muduuo network library.

First, look at the constructor of Tcpserver (this series is based primarily on sample programs, which may differ from the final version of Muduo, but the overall framework is consistent)

TcpServer::TcpServer(EventLoop* loop, const InetAddress& listenAddr)
  : loop_(CHECK_NOTNULL(loop)),
    name_(listenAddr.toHostPort()),
    acceptor_(new Acceptor(loop, listenAddr)),
    threadPool_(new EventLoopThreadPool(loop)),
    started_(false),
    nextConnId_(1)
{
  acceptor_->setNewConnectionCallback(
      boost::bind(&TcpServer::newConnection, this, _1, _2));
}

Tcpsever is constructed by passing in an Eventloop pointer and an address structure (for listening). Then these two parameters are listed in the parameter list and used as parameters to construct an Acceptor object.

Acceptor, as its name implies, is responsible for receiving client connections. Now look at the construction of Acceptor

Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr)
  : loop_(loop),
    acceptSocket_(sockets::createNonblockingOrDie()),
    acceptChannel_(loop, acceptSocket_.fd()),
    listenning_(false)
{
  acceptSocket_.setReuseAddr(true);
  acceptSocket_.bindAddress(listenAddr);
  acceptChannel_.setReadCallback(
      boost::bind(&Acceptor::handleRead, this));
}

When an Acceptor object is created, a non-blocking socket is created, and then a Channel is created for the socket, which binds acceptSocket_to the channel.

In the function body of the constructor, the handleRead function of Acceptor is registered as a readable event callback function of acceptChannel_ (The registration of Channel callback function is explained by Channel, which has not been written yet, and will be added later.)

Next, look at the handleRead function

void Acceptor::handleRead()
{
  loop_->assertInLoopThread();
  InetAddress peerAddr(0);
  //FIXME loop until no more
  int connfd = acceptSocket_.accept(&peerAddr);
  if (connfd >= 0) {
    if (newConnectionCallback_) {
      newConnectionCallback_(connfd, peerAddr);
    } else {
      sockets::close(connfd);
    }
  }
}

When handleRead is readable, the server must have initiated a connection request. So when handleRead is called, the accept function is first called to establish the connection. Then call the new ConnectionCallback_callback function.

So when was the new ConnectionCallback_callback function registered? In the function body of the Tcpsever object constructor, see the code above.

Tcpserver sets its member function newConnection to the execution function of handleRead in Acceptor.

Take a look at the new Connection below.

void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
  loop_->assertInLoopThread();
  char buf[32];
  snprintf(buf, sizeof buf, "#%d", nextConnId_);
  ++nextConnId_;
  std::string connName = name_ + buf;

  LOG_INFO << "TcpServer::newConnection [" << name_
           << "] - new connection [" << connName
           << "] from " << peerAddr.toHostPort();
  InetAddress localAddr(sockets::getLocalAddr(sockfd));
  // FIXME poll with zero timeout to double confirm the new connection
  EventLoop* ioLoop = threadPool_->getNextLoop();
  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));
}

Because it is called by handleRead, the parameter is the connection descriptor passed in by handleRead and the address structure of the other party. EventLoopThreadPool is used here, from which an Eventloop is selected, and then a Tcopconnection object is created to execute the object in the selected loop. Then all kinds of callback functions set by the server are registered in Tcpconnection, and finally read and write events in the Tcpconnection object are executed in loop.

At this point, Tcpsever successfully established the connection and roughly sorted out the process.

Posted by j0hn_ on Tue, 13 Aug 2019 05:39:49 -0700