muduo源码阅读(三):TcpServer

本文深入解析muduo库中的TcpServer,从测试代码开始,探讨设置端口号的过程,介绍Acceptor的功能及工作原理,并详细阐述TcpConnection构造时内部发生的事情,揭示muduo如何管理连接并实现事件驱动的网络编程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

TcpServer

功能 :这是一个接口类,拥有一个管理监听套接字的类accptor,拥有一张具有多个管理连接套接字的TcpConnection类的映射表。
它只是对这两个类进行管理,会设置它们的一些回调函数,监听端口,等,负责acceptor和TcpConnection两个类与用户交互的接口,而具体的调用实现还是由那两个具体的类去实现。比如连接到来时的调用函数,用户设置为onConnection,则接口TcpServer会将其设置为,TcpConnection类的connectioncallback函数,然后在建立函数的newconnection中调用。而将建立新的TcpConnection的TcpServer::NewConnection函数设置为acceptor的
类图

在这里插入图片描述

从测试代码读起

先上一段测试代码

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

  muduo::InetAddress listenAddr(9981);
  muduo::EventLoop loop;

  muduo::TcpServer server(&loop, listenAddr);
  server.setConnectionCallback(onConnection);
  server.setMessageCallback(onMessage);
  server.start();

  loop.loop();
}

这段测试代码中,我们构造了一个TcpServer对象,然后调用了两个设置回调函数的成员函数,然后调用一个start函数。也就是说,我们使用TcpServer的时候,只要提供监听的端口号,注册连接时的回调函数,注册消息到来时的回调函数,即可。

设置端口号背后的故事

我们为 InetAddress 提供了端口号,那么muduo为我们做了什么呢?

explicit InetAddress(uint16_t port = 0, bool loopbackOnly = false, bool ipv6 = false);

InetAddress::InetAddress(uint16_t port, bool loopbackOnly, bool ipv6)
{
    memZero(&addr_, sizeof addr_);
    addr_.sin_family = AF_INET;
    in_addr_t ip = loopbackOnly ? kInaddrLoopback : kInaddrAny;
    addr_.sin_addr.s_addr = sockets::hostToNetwork32(ip);
    addr_.sin_port = sockets::hostToNetwork16(port);

}

这里对构造函数做了些删减,只留下了比较主干的部分,
在 InetAddress 的构造函数中,会创建一个 sockaddr_in_ 结构体对象,
假如我们只提供端口号,那么muduo会默认为ipv4,然后默认监听本机所有ip,初始化addr_成员,在初始化的时候,还用了sockets的一些转换函数,这些工作由muduo为我们做,可以为我们节省大量的时间和重复的工作量。
这样我们就完成了要监听的ip地址和端口号。

接着关注,TcpServer对象,
在TcpServer的构造函数:

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)
{
  acceptor_->setNewConnectionCallback(
      std::bind(&TcpServer::newConnection, this, _1, _2));//把newconnection设为了acceptor的回调函数
}

可以看到,该TcpServer有着所属的eventloop,监听的端口号,自己的名字,这都是一对一的属性。
同时这里创建了一个accptor_对象,这个对象会为我们完成一部分重要而规范化的工作。

acceptor介绍

功能 :调用accept建立新的TCP连接。每个accptor负责一个监听套接字,一旦有连接请求到来,会调用accept函数建立一个TcpConnection类,管理连接套接字。
类图在这里插入图片描述
Acceptor的构造函数,调用socket完成了套接字的创建,调用bind绑定了套接字与服务器addr_结构体,也就是在TcpServer中创建的对象。

Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)
  : loop_(loop),
    acceptSocket_(sockets::createNonblockingOrDie(listenAddr.family())),
    acceptChannel_(loop, acceptSocket_.fd()),//初始化channel对应的文件描述符
    listenning_(false),
    idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC))
{
  assert(idleFd_ >= 0);
  acceptSocket_.setReuseAddr(true);
  acceptSocket_.setReusePort(reuseport);
  acceptSocket_.bindAddress(listenAddr);
  acceptChannel_.setReadCallback(
      std::bind(&Acceptor::handleRead, this));//设置channel的读事件回调函数
}

//创建socket的函数   SocketOps.cc
int sockets::createNonblockingOrDie(sa_family_t family)
{
  int sockfd = ::socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
  if (sockfd < 0)
  {
    LOG_SYSFATAL << "sockets::createNonblockingOrDie";
  }
  return sockfd;
}

// 绑定监听地址 在
acceptSocket_.bindAddress(listenAddr);

//调用listen,转为监听状态:在TcpServer::start()中调用
void Acceptor::listen()
{
  loop_->assertInLoopThread();
  listenning_ = true;
  acceptSocket_.listen();
  acceptChannel_.enableReading();
}

因为我们的eventloop机制是通过channel来管理事件的,一个channel对应一个fd,这里的监听socketfd会对应一个channel,这里把该channel的读处理事件回调函数绑定为Acceptor::handleRead。也就是监听套接字可读,那就是发生了TCP连接事件,因此,我们应该想到这个时候是该调用accept来处理连接事件了。
Acceptor::handleRead就是通过调用accept(2)来建立了新的TCP连接

//channel读事件发生时会调用这个函数
void Acceptor::handleRead()
{
  loop_->assertInLoopThread();
  InetAddress peerAddr;
  //FIXME loop until no more
  int connfd = acceptSocket_.accept(&peerAddr);//调用accept建立连接,得到客户端fd
  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);
    }
  }
}

建立连接以后,调用 newConnectionCallback_ 这个函数在TcpServer的构造函数中,被传入的是TcpServer::newConnection也就是后续创建Tcpconnection 的函数

  acceptor_->setNewConnectionCallback(
      std::bind(&TcpServer::newConnection, this, _1, _2));

现在用accept得到了连接套接字,连接套接字得到以后,我们就需要建立一个TcpConnection来维持两者的通信。


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
  //创建了一个Tcpconnection对象
  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));
}

调用Tcpconnection的构造函数的时候,背后发生了什么呢?

先看一下构造函数代码:


TcpConnection::TcpConnection(EventLoop* loop,
                             const string& nameArg,
                             int sockfd,
                             const InetAddress& localAddr,
                             const InetAddress& peerAddr)
  : loop_(CHECK_NOTNULL(loop)),
    name_(nameArg),
    state_(kConnecting),
    reading_(true),
    socket_(new Socket(sockfd)),
    channel_(new Channel(loop, sockfd)),
    localAddr_(localAddr),
    peerAddr_(peerAddr),
    highWaterMark_(64*1024*1024)
{
  channel_->setReadCallback(
      std::bind(&TcpConnection::handleRead, this, _1));
  channel_->setWriteCallback(
      std::bind(&TcpConnection::handleWrite, this));
  channel_->setCloseCallback(
      std::bind(&TcpConnection::handleClose, this));
  channel_->setErrorCallback(
      std::bind(&TcpConnection::handleError, this));
  LOG_DEBUG << "TcpConnection::ctor[" <<  name_ << "] at " << this
            << " fd=" << sockfd;
  socket_->setKeepAlive(true);
}

构造函数:
socket_(new Socket(sockfd)),
channel_(new Channel(loop, sockfd)),
创建了一个socket作为连接套接字,创建一个channel负责该连接套接字
然后设置一些channel对应的处理回调函数
先分析两个相对重要的回调函数
第一个是处理读取的函数

void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_->assertInLoopThread();
  int savedErrno = 0;
  ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);  //调用inputBuffer_的read函数
  if (n > 0)
  {
    messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);//调用我们注册的消息到来时的函数
  }
  else if (n == 0)
  {
    handleClose();
  }
  else
  {
    errno = savedErrno;
    LOG_SYSERR << "TcpConnection::handleRead";
    handleError();
  }
}

这个回调函数主要是两件事:调用InputBuffer的读函数,调用我们注册的消息到来时的函数onMessage
处理回调写函数
handlewrite

void TcpConnection::handleWrite()
{
  loop_->assertInLoopThread();
  if (channel_->isWriting())
  {
    ssize_t n = sockets::write(channel_->fd(),
                               outputBuffer_.peek(),
                               outputBuffer_.readableBytes());
    if (n > 0)
    {
      outputBuffer_.retrieve(n);
      if (outputBuffer_.readableBytes() == 0)
      {
        channel_->disableWriting();
        if (writeCompleteCallback_)
        {
          loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));
        }
        if (state_ == kDisconnecting)
        {
          shutdownInLoop();
        }
      }
    }
#include <muduo/net/TcpServer.h> #include <muduo/base/Logging.h> #include <boost/bind.hpp> #include <muduo/net/EventLoop.h> // 使用muduo开发回显服务器 class EchoServer { public: EchoServer(muduo::net::EventLoop* loop, const muduo::net::InetAddress&amp; listenAddr); void start(); private: void onConnection(const muduo::net::TcpConnectionPtr& conn); void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp time); muduo::net::TcpServer server_; }; EchoServer::EchoServer(muduo::net::EventLoop* loop, const muduo::net::InetAddress&amp; listenAddr) : server_(loop, listenAddr, "EchoServer") { server_.setConnectionCallback( boost::bind(&EchoServer::onConnection, this, _1)); server_.setMessageCallback( boost::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); } int main() { LOG_INFO << "pid = " << getpid(); muduo::net::EventLoop loop; muduo::net::InetAddress listenAddr(8888); EchoServer server(&loop, listenAddr); server.start(); loop.loop(); } 这个代码应该怎么改?
最新发布
07-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值