muduo一个简单echo服务器的分析

muduo一个简单echo服务器的分析

前两篇写muduo网络框架线程处理,这两篇通过一个小的echo服务器来完整说明这个网络事件处理的流程。echo是muduo自带的例子,十分简单:

int main()
{
  LOG_INFO << "pid = " << getpid();
  muduo::net::EventLoop loop;
  muduo::net::InetAddress listenAddr(2007);
  EchoServer server(&loop, listenAddr);
  server.start();
  loop.loop();
}
EchoServer::EchoServer(muduo::net::EventLoop* loop,
                       const muduo::net::InetAddress& listenAddr)
  : server_(loop, listenAddr, "EchoServer")
{
  server_.setConnectionCallback(
      std::bind(&EchoServer::onConnection, this, _1));
  server_.setMessageCallback(
      std::bind(&EchoServer::onMessage, this, _1, _2, _3));
}

void EchoServer::start()
{
  server_.start();
}

void EchoServer::onConnection(const muduo::net::TcpConnectionPtr& conn)
{
  LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> "
           << conn->localAddress().toIpPort() << " is "
           << (conn->connected() ? "UP" : "DOWN");
}

void EchoServer::onMessage(const muduo::net::TcpConnectionPtr& conn,
                           muduo::net::Buffer* buf,
                           muduo::Timestamp time)
{
  muduo::string msg(buf->retrieveAllAsString());
  LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, "
           << "data received at " << time.toString();
  conn->send(msg); 
}

muduo的主线程有一个在栈上显示声明的EventLoop实例,我们看到EchoServer有个TcpServer类型的组合变量server_(注意muduo提倡组合方式,而非派生的方式),server_会引用到这个EventLoop 实例。进而是server_开始运行start。start中线程池也开始运行start,线程池的start分析,前面已经讲过,这里就不再赘述了。接着是监听socket对象Acceptor的监听工作,这个没什么好说的。为什么要用loop_->runInLoop的方式,这个下面会说到。

当有新的连接到来时,会触发事件,调用TcpServer::newConnection,这个是在TcpServer构造函数中就绑定好了的,函数如下:

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;

  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(
      std::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
  ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}

前面说过新的连接事件处理会在工作线程的EventLoop(如果有的话)中。如何获取新的新的线程,调用threadPool_->getNextLoop()即可,他采用简单的round-robin方式来获取。接着会创建一个TcpConnection实例,设置各种回调函数,例如连接成功之后的回调函数,收到消息之后的回调函数,他们已经在EchoServer构造函数中绑定。然后是运行ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn)):

void EventLoop::runInLoop(Functor cb)
{
  if (isInLoopThread())
  {
    cb();
  }
  else
  {
    queueInLoop(std::move(cb));
  }
}

void EventLoop::queueInLoop(Functor cb)
{
  {
  MutexLockGuard lock(mutex_);
  pendingFunctors_.push_back(std::move(cb));
  }

  if (!isInLoopThread() || callingPendingFunctors_)
  {
    wakeup();
  }
}

runInLoop的目的就是,如果新连接获得EventLoop在主线程中,那么直接执行该绑定的函数,否则EventLoop先暂存其回调函数,叫作未决的函数PendingFunctors。什么时候调用呢?前面说到新线程的可能处于epoll的阻塞中。所以要唤醒新的线程,或者已经是在处理之前的回调函数状态,为了快速处理新的回调函数,也再次发出唤醒的事件。

唤醒后EventLoop继续循环下去,处理未决函数doPendingFunctors。TcpConnection::connectEstablished()函数就被调用了:

void TcpConnection::connectEstablished()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnecting);
  setState(kConnected);
  channel_->tie(shared_from_this());
  channel_->enableReading();

  connectionCallback_(shared_from_this());
}

他要做的是添加到epoll的监管之中。然后回调函数触发,前面说过在EchoServer中绑定过了。当有数据来到时,会触发其读回调函数(这个是在通道Channel类中绑定的,会另写一篇文章来说明)。

void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_->assertInLoopThread();
  int savedErrno = 0;
  ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
  if (n > 0)
  {
    messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
  }
  else if (n == 0)
  {
    handleClose();
  }
  else
  {
    errno = savedErrno;
    LOG_SYSERR << "TcpConnection::handleRead";
    handleError();
  }
}

messageCallback_在EchoServer中绑定了,他会给客户端原封不动的发送收到的消息。其实接收与发送消息时涉及到的缓存处理还是有点复杂的,这个有机会再剖析。

至此整个流程就走完了。

猜你喜欢

转载自blog.csdn.net/zxm342698145/article/details/81392116