muduo源码分析之TcpConnection发送数据


(一)writeable事件的busy loop

发送数据是要比接收数据更加麻烦的事情,因为它是一个主动发生的事情,考虑下面情况:

水平触发模式(Level-Triggered);当socket可写时,会不停的触发socket可写的事件,如何处理?

也就是说,如果发送缓冲不满,将不停触发socket可写事件,也就是说,poll/epoll调用不停返回,也就进入busy loop了。怎么解决这个问题,以前有一个回答:“写的时候不用poll/epoll管理,也就是说直接write,如果返回EAGAIN才将描述符添加到loop中”


(二)muduo解决busy loop

muduo的做法就验证了上述回答,将在接下来的文章中细表。

在之前的学习中,已经知道用Channel表示一个loop中被管理的事件,不过目前只是对描述符可读进行表示:

1.TimerQueue用它来读timerfd(2)—–>定时器事件
2.EventLoop用它来读eventfd(2)——>跨线程唤醒
3.TcpServer/Acceptor用它来读listening socket——>有新连接时监听套接字描述符可读事件
4.TcpConnection用它来读普通的TCP socket——>已连接套接字收到数据时的可读事件

而对于一个writable事件来说,正如上文所说,由于level trigger的特性,可能一直触发造成busy loop,因此我们只在特定情况下管理可读事件的描述符,并且写完数据后要将可读事件移出poll/epoll,所以在Channel中需要添加:

void enableWriting();//添加writeable事件
void disableWriting();//移除writeable事件
bool isWriting();

通过上述三个函数打开和关闭写通道,并且获取当前状态,可以很好的控制防止busy loop。另外需要注意的就是打开关闭通道的时机了。


(三)一个例子

从一个示例中来看使用TcpConnection发送数据的过程。

void onConnection(const muduo::TcpConnectionPtr& conn)
{
  if (conn->connected())
  {
    printf("onConnection(): new connection [%s] from %s\n",
           conn->name().c_str(),
           conn->peerAddress().toHostPort().c_str());
   //发送数据
    conn->send(message1);
    conn->send(message2);
    conn->shutdown();
  }
  else
  {
    printf("onConnection(): connection [%s] is down\n",
           conn->name().c_str());
  }
}

在onConnection回调中,服务器向socket写入两次数据,客户端将收到数据,下面是TcpConnection::send的实现:

void TcpConnection::send(const std::string& message)
{
  if (state_ == kConnected) {
    if (loop_->isInLoopThread()) {
      sendInLoop(message);//本线程直接调用
    } else {
    //用runInLoop进行跨线程调用
      loop_->runInLoop(
          boost::bind(&TcpConnection::sendInLoop, this, message));
    }   
  }
}

再来看TcpConnection::sendInLoop

void TcpConnection::sendInLoop(const std::string& message)
{
  loop_->assertInLoopThread();
  ssize_t nwrote = 0;
  // if no thing in output queue, try writing directly
  // 发送缓冲没有内容,直接写数据。否则就不发送了,先处理outbuffer不然会造成数据乱序
  // 先尝试直接写数据,如果数据没有发完再用poll去管理描述符
  if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0) {
    nwrote = ::write(channel_->fd(), message.data(), message.size());
    if (nwrote >= 0) {
      if (implicit_cast<size_t>(nwrote) < message.size()) {//数据没有写完
        LOG_TRACE << "I am going to write more data";
      }
    } else {//出错
      nwrote = 0;
      if (errno != EWOULDBLOCK) {
        LOG_SYSERR << "TcpConnection::sendInLoop";
      }
    }
  }
  assert(nwrote >= 0);
  if (implicit_cast<size_t>(nwrote) < message.size()) {//数据没写完发
    outputBuffer_.append(message.data()+nwrote, message.size()-nwrote);//往发送缓冲添加数据
    if (!channel_->isWriting()) {
        //使用epoll/poll管理,将outputbuffer_里面剩余的数据管理起来
      channel_->enableWriting();
    }
  }
}

正如本文最开始提到的解决办法,来看muduo的做法:
1.如果outputBuffer_发送缓冲没有数据时,我们直接调用wirte(2)
2.如果数据没有写完,将剩余的待写数据放到发送缓冲outputBuffer_中,并且打开写通道enableWriting()
3.这个时候writeable事件返回,将调用Channel::writeCallback()
4.在TcpConnection的构造函数中设置回调:

//该回调当将发送事件添加到loop中并返回时调用,处理outbuffer中的数据。
channel_->setWriteCallback(
        boost::bind(&TcpConnection::handleWrite, this));

再看TcpConnection::handleWrite

  //发送outputBuffer_中的数据
void TcpConnection::handleWrite()
  {
    loop_->assertInLoopThread();
    if (channel_->isWriting()) {
      ssize_t n = ::write(channel_->fd(),
                          outputBuffer_.peek(),
                          outputBuffer_.readableBytes());
      if (n > 0) {
        outputBuffer_.retrieve(n);
        if (outputBuffer_.readableBytes() == 0) {
            //数据发送完毕,关闭channel否则会busyloop
          channel_->disableWriting();
          if (state_ == kDisconnecting) {
            shutdownInLoop();
          }
        } else {
          LOG_TRACE << "I am going to write more data";
        }
      } else {
        LOG_SYSERR << "TcpConnection::handleWrite";
      }
    } else {
      LOG_TRACE << "Connection is down, no more writing";
    }
  }

(四)参考

1.Linux多线程服务端编程 使用muduo C++ 网络库
2.http://blog.csdn.net/jnu_simba/article/details/15027289
3.http://blog.csdn.net/zhangxiao93/article/details/52849349?locationNum=3&fps=1

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值