muduo中TcpConnection里IO事件的处理

本文详细介绍了muduo库中TcpConnection类如何处理TCP连接上的IO事件,特别是读写操作。TcpConnection维护了两个Buffer对象,分别用于数据的输入和输出。读写事件处理函数handleread和handlewrite将数据存入Buffer,然后进行后续操作。用户通过操作Buffer,而不是直接与socket交互,例如通过messageCallback_函数处理输入数据,send函数用于发送数据,最终调用线程安全的sendInLoop函数。该函数尝试立即发送数据,若未能完全发送则将剩余部分存入outputbuffer,待下次写事件触发继续发送。整个过程,muduo对原生socket的读写进行了全面封装。

muduo里面对IO事件的处理核心在TcpConnection里面。因为TcpConnection主要是负责TCP连接,因此有关TCP连接上所有的socket读写都发生在TcpConnection中。根据前一节对Buffer的介绍,在这里我们将会看到muduo是怎么使用Buffer的。

TcpConnection中有两个Buffer对象:

  Buffer inputBuffer_; 
  Buffer outputBuffer_; 

分别负责处理对数据的输入输出。从TcpConnection的构造函数中,我们可以看到TcpConnection的读写事件处理函数为:

void TcpConnection::handleRead(Timestamp receiveTime);
void TcpConnection::handleWrite();

两个函数的实现如下:

void TcpConnection::handleWrite()
{
  loop_->assertInLoopThread();
  if (channel_->isWriting())
  {
    ssize_t n = sockets::write(channel_->fd(),
                               outputBuffer_.peek(),
                               outputBuffer_.readableBytes()); //向chennel_->fd中写数据
    if (n > 0)
    {
      outputBuffer_.retrieve(n); //将已经写入channel_->fd的数据从outbuffer中除去
      if (outputBuffer_.readableBytes() == 0)  //如果outbuffer中已经没有数据了,则将该channel_的写事件从事件循环中去掉
      {
        channel_->disableWriting();
        if (writeCompleteCallback_)
        {
          loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this()));
        }
        if (state_ == kDisconnecting)
        {
          shutdownInLoop();
        }
      }  // end of if (说明还有数据需要些写,不关闭写事件)
    }
    else
    {
      LOG_SYSERR << "TcpConnection::handleWrite";
      // if (state_ == kDisconnecting)
      // {
      //   shutdownInLoop();
      // }
    }
  }
  else
  {
    LOG_TRACE << "Connection fd = " << channel_->fd()
              << " is down, no more writing";
  }
}

void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_->assertInLoopThread();
  int savedErrno = 0;
  ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);  //将fd中的数据读入到inputbuffer中
  //如果从fd中读到了数据,则调用messageCallback_,这个函数是可以通过用户来设置的
  if (n > 0)
  {
    messageCallback_(shared_from_this(), &inputBuffer_, receiveTime); 
  }
  //表示对端关闭了连接,调用handleclose
  else if (n == 0) // 
  {
    handleClose();
  }
  else
  {
    errno = savedErrno;
    LOG_SYSERR << "TcpConnection::handleRead";
    handleError();
  }
}

显然,不管是handleread还是handlewrite,他们都是先将数据放入到Buffer中,然后再通过Buffer进行进一步的操作的。因此,用户设置的函数都是直接操作Buffer的,用户程序不会直接接触到对socket的读写。比如messageCallback_函数,它就是基于inputbuffer的。至于发送数据的用户函数,一般是通过调用TcpConnection的send函数它们都间接调用sendInLoop,这个函数是线程安全的。下面看一下sendInLoop:


void TcpConnection::sendInLoop(const void* data, size_t len)
{
  loop_->assertInLoopThread();
  ssize_t nwrote = 0;
  size_t remaining = len;
  bool faultError = false;
  if (state_ == kDisconnected)
  {
    LOG_WARN << "disconnected, give up writing";
    return;
  }
  // if no thing in output queue, try writing directly
  if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
  {
    nwrote = sockets::write(channel_->fd(), data, len); //向fd中写数据
    if (nwrote >= 0) //写成功入了nwrote数据
    {
      remaining = len - nwrote; //记录还剩多少数据没有发送
      if (remaining == 0 && writeCompleteCallback_) //如果已经发送完毕
      {
        loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this()));
      }
    }
    else // nwrote < 0 //说明写socket出错
    {
      nwrote = 0;
      if (errno != EWOULDBLOCK)
      {
        LOG_SYSERR << "TcpConnection::sendInLoop";
        if (errno == EPIPE || errno == ECONNRESET) // FIXME: any others?
        {
          faultError = true;
        }
      }
    }
  }

  assert(remaining <= len);
  //如果写socket操作正确返回,并且没有把数据全部发送出去
  if (!faultError && remaining > 0)
  {
    size_t oldLen = outputBuffer_.readableBytes(); 
    if (oldLen + remaining >= highWaterMark_
        && oldLen < highWaterMark_
        && highWaterMarkCallback_) //如果outputbuffer中国数据超过了警戒线,则调用highWaterMarkCallback_函数
    {
      loop_->queueInLoop(boost::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining));
    }
    outputBuffer_.append(static_cast<const char*>(data)+nwrote, remaining); //将剩余的数据放入到outputbuffer中
    if (!channel_->isWriting()) //如果channel_没有在监听写事件,则注册监听事件,因为现在有数据等代写。
    {
      channel_->enableWriting(); // 缓冲区中还有数据,继续为当前的channel设置可写事件
    }
  }
}

根据源码注释,发送数据主要由两步完成,首先是尝试直接将数据发送出去,此时如果数据确实全部发送出去了,则正确返回,如果数据没有完全发送出去,则将剩余的数据写入outputbuffer中,等待下次写事件发生时在发送。用户调用这个函数时也是看不到原生socket的。对原生socket的读写全部由muduo封装起来了。

### TcpServer 在 Muduo 网络库中的用法与实现 Muduo 是一个高性能的 C++ 着重于网络编程的异步事件驱动框架,广泛应用于构建高并发服务器应用。TcpServer 类作为该库的核心组件之一,在处理 TCP 连接和服务端逻辑方面扮演着重要角色。 #### 1. **TcpServer 的基本结构** TcpServer 主要负责管理监听套接字以及客户端连接的状态维护工作。其核心设计围绕 Reactor 模型展开,通过 IO 多路复用来高效处理大量并发请求。具体来说: - `newConnection` 函数用于创建新的连接实例并将其加入到内部的数据结构中[^1]。 - 新建的 `TcpConnection` 对象会被存储在一个名为 `connectionMap_` 的映射表以便后续访问和操作。 - 同时,新建立的连接还会被注册至所属子循环(SubLoop),从而确保能够及时响应各种 I/O 事件。 #### 2. **TcpServer 的初始化过程** 在实际部署过程中,开发者通常需要完成以下几个方面的配置才能启动服务端运行环境: - 设置 IP 地址与端口号参数以绑定本地地址空间; - 定义最大允许待接受队列长度等相关选项; - 配置线程池大小及其分配策略来优化多核 CPU 利用效率。 以下是简单的代码示例展示如何设置并启动一个基于 Muduo 库的服务端程序: ```cpp #include "muduo/net/TcpServer.h" #include "muduo/base/Logging.h" using namespace muduo; using namespace muduo::net; void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time){ LOG_INFO << conn->name() << " receives " << buf->retrieveAllAsString(); } int main(){ EventLoop loop; InetAddress listenAddr(9981); TcpServer server(&loop, listenAddr, "TestServer"); server.setThreadNum(3); // 使用三个IO线程 server.setMessageCallback(onMessage); server.start(); loop.loop(); return 0; } ``` 上述片段展示了怎样快速搭建起具备基础功能的小型 HTTP 或其他协议类型的服务器雏形。 #### 3. **深入理解 Sockets API 及其实现细节的重要性** 即使像 Muduo 这样的高级抽象层已经屏蔽了许多底层复杂度,但对于从事生产级开发工作的工程师而言,掌握原始 Socket 编程技巧仍然至关重要[^2]。因为只有深入了解这些基础知识,才能够更有效地定位并修复潜在性能瓶颈或者异常状况下的行为表现问题。 此外,对于某些特定场景需求——比如自定义传输控制机制或是针对特殊硬件平台做适配调整等情况,则更加依赖程序员对整个通信栈有全面的认识水平[^3]。 #### 结论 综上所述,通过对 MuduoTcpServer 组件的学习可以让我们更好地理解和实践现代C++环境下高效的网络应用程序的设计理念和技术手段。与此同时,持续积累关于低层次网络原理的知识也将极大地促进个人技术能力的成长与发展。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值