muduo库TcpConnection对send、shutdown、SIGPIPE的处理

SIGPIPE 原因和解决办法和信号处理 SIGPIPE 阅读详情


muduo库对send的处理:

muduo库的send()函数重载了三个:

 void send(const void* message, int len);
 void send(const StringPiece& message);   //短字符优化子string类
 void send(Buffer* message);  // this one will swap data

分别发送不同的类型,实际上它们有调用关系的,我们挑一种分析即可,发送字符数组:

//调用发送字符串的send,发送字符缓冲区
void TcpConnection::send(const void* data, int len)
{
  send(StringPiece(static_cast<const char*>(data), len));
}
转化为发送StringPiece类型:

void TcpConnection::send(const StringPiece& message)
{
  if (state_ == kConnected)
  {
    //如果在I/O线程,直接调用
    if (loop_->isInLoopThread())
    {
      sendInLoop(message);
    }
    else
    {
      //如果不在,跨线程调用,需要传递缓冲区message过去,有一定的开销
      loop_->runInLoop(
          boost::bind(&TcpConnection::sendInLoop,
                      this,     // FIXME
                      message.as_string()));
                    //std::forward<string>(message)));
    }
  }
}

可以看出如果处于已连接状态,且当前线程是I/O线程,有EventLoop,那么直接调用sendInLoop()发送,如果不是I/O线程,需要跨线程传入I/O线程,不过还是执行sendInLoop()函数,有一定的开销。

这个函数是这样实现的:

void TcpConnection::sendInLoop(const StringPiece& message)
{
  sendInLoop(message.data(), message.size());
}

又调用下一层:

void TcpConnection::sendInLoop(const StringPiece& message)
{
  sendInLoop(message.data(), message.size());
}


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;
  }
  //没有关注可写事件,并且缓冲区数目为0,直接发送
  // if no thing in output queue, try writing directly
  if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
  {
    nwrote = sockets::write(channel_->fd(), data, len);
    if (nwrote >= 0)
    {
      remaining = len - nwrote;
      if (remaining == 0 && writeCompleteCallback_)
      {
        loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this()));
      }
    }
    else // nwrote < 0
    {
      nwrote = 0;
      if (errno != EWOULDBLOCK)
      {
        LOG_SYSERR << "TcpConnection::sendInLoop";
        if (errno == EPIPE || errno == ECONNRESET) // FIXME: any others?
        {
          faultError = true;
        }
      }
    }
  }


  //没有错误,并且还有未写完的数据(说明内核缓冲区满,要将未写完的数据添加到output buffer中
  assert(remaining <= len);
  if (!faultError && remaining > 0)
  {
    //如果超过高水位标志,逻辑可能有问题,回调highWaterMarkCallback,不过还是要写
    size_t oldLen = outputBuffer_.readableBytes();
    if (oldLen + remaining >= highWaterMark_
        && oldLen < highWaterMark_
        && highWaterMarkCallback_)
    {
      loop_->queueInLoop(boost::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining));
    }


    //把剩余数据追加到outputbuffer,并注册POLLOUT事件
    outputBuffer_.append(static_cast<const char*>(data)+nwrote, remaining);
    if (!channel_->isWriting())
    {
      channel_->enableWriting();
    }
  }
}

函数有两个大情况:

    情况1:当我们调用send()时,如果没有关注可写事件并且缓冲区无缓存,那我们直接立即写入。这是为什么呢?因为muduo这就是一般异步网络库的通用方法,缓存应用层传过来的数据,在I/O设备可写的情况下尽量写入数据。如果关注了POLLOUT事件,意味着我们要等待POLLOUT事件发生才能写入。如果缓冲区有数据,意味者我们必须先写完缓冲区数据,所以都不能立即写。写入之后,可能没有写完需要注册writeCompleteCallback事件(这个函数是用户注册的写完数据的回调函数)。如果写出错,且errno!=EWOULDBLOCK,也就是EAGAIN,那就表示出错了。

    writeComplateCallback()函数表示数据发送完毕回调函数,即所有的用户数据都已拷贝到内核缓冲区时调用该回调函数,Buffer被清空也要调用该回调函数,可以理解为lowWaterMarkCallback()函数。上层用户只管调用conn->send()函数发送数据,网络库负责将用户数据拷贝到内核缓冲区。通常情况下编写大流量的应用程序,才需要关注writeCompleteCallback()。大流量的应用程序不断生成数据,然后send(),如果对等方接收不及时,收到通告窗口的影响,发送缓冲区会不足这个时候,就会将用户数据添加到应用层的发送缓冲区,可能会撑爆outputBuffer。解决方法就是调整发送频率,只需要关注writeCompleteCallback,当所有用户数据都拷贝到内核缓冲区后,上层用户会得到通知,就可以继续发送数据。这需要上层用户的配合。对于低流量的应用程序,通常不需要关注writeCompleteCallback()。


    情况2:情况2与情况1并不能完全分割,如果我们没能够立即写入,即没有执行情况1的if语句,或者执行了但是没有写完,我们需要把剩余数据追加到outputBuffer中。如果我们追加数据,但是readable长度加上我们要追加的超度超过了高水位线,意味着我们逻辑可能有问题。不能总是一加数据就超标,高水位线或许不够高。所以需要让I/O线程执行highWaterMarkCallback()函数,做相应处理(这个函数是用户定义,由用户决定超高水位标是否进行修改)。然后把数据追加到outputBuffer,或许空间不够,但append()函数内部可以处理这个问题,不论是内部调整,还是再申请空间都可以。最后,如果没有注册POLLOUT事件,我们需要注册POLLOUT事件,但它发生时,我们的缓冲区数据就可以安心的发送了。

    highWaterMarkCallback()是高水位标回调函数,outputBuffer撑到一定程度时最好回调该函数。这意味着对等方接收不及时,导致outputBuffer不断增大,很可能因为我们没有关注completeWaterMarkCallback()。在这个回调函数中用户就可以断开这个连接,避免内存不足。


    讲到这里,不得不再提一下muduo库所谓的“三个半事件”:

1.连接建立:服务器accept(被动)接收连接,客户端connect(主动)发起连接。
2.连接断开:主动断开(close、shutdown),被动断开(read返回0)
3.消息到达:文件描述符可读
4.消息发送完毕:这算半个事件。对于低流量的服务,可以不关心这个事件。这里的发送完毕是数据写入操作系统缓冲区,将由TCP协议栈法则数据的发送与重传,不代表对方已经接数据。高流量服务可能导致内核缓冲区满了,数据还会追加到应用层缓冲区。

    上面我们进行实际上就是最后的这“消息发送完毕”的半个事件直接写入套接字实际上就是直接写入操作系统缓冲区,然后写入数据有剩余的情况下,我们把数据追加到Buffer中。至于数据到底有没有发送到对端,这让TCP去负责,所以就是半个事件。


此外,还有一个函数,我们上面那个函数末尾进行了enableWriting,注册了关注POLLOUT事件,事件发生后会调用TcpConnection的handleWrite()函数:

//内核缓冲区有空间了,回调该函数
void TcpConnection::handleWrite()
{
  loop_->assertInLoopThread();
  if (channel_->isWriting())  //关注了可写事件
  {
    //把outputBuffer缓冲区内容写入fd
    ssize_t n = sockets::write(channel_->fd(),
                               outputBuffer_.peek(),  //返回可读下标,
                               outputBuffer_.readableBytes());
    if (n > 0)
    {
      outputBuffer_.retrieve(n);  //写了n个字节,移动下标n个
      if (outputBuffer_.readableBytes() == 0)  //如果全部写完
      {
        channel_->disableWriting();   //数据发送完毕,停止关闭POLLOUT事件,以免出现busy loop
        if (writeCompleteCallback_) //回调writeCompleteCallback
        {
          //彻底写完调用writeCompleteCallback
          loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this()));
        }
        if (state_ == kDisconnecting)  
        {
          shutdownInLoop();   //发送完毕关闭连接
        }
      }
    }
    else
    {
      LOG_SYSERR << "TcpConnection::handleWrite";
      // if (state_ == kDisconnecting)
      // {
      //   shutdownInLoop();
      // }
    }
  }
  else
  {
    LOG_TRACE << "Connection fd = " << channel_->fd()
              << " is down, no more writing";
  }
}

缓冲区有空间回调handleWrite()函数,发送数据完了会关闭连接,在这里由于数据发送完毕,调用了channel_->disableWriting()函数通知管制POLLOUT,会关闭连接。


muduo库对shutdown的处理:

一般情况下shutdown()函数也不是直接关闭的:

void TcpConnection::shutdownInLoop()
{
  loop_->assertInLoopThread();
  if (!channel_->isWriting())  //如果不再关注POLLOUT事件,否则不能关闭,那就仅仅是将状态更改为kDisconnecting,不能关闭连接
  {
    // we are not writing
    socket_->shutdownWrite();
  }
}
如果不再关注POLLOUT事件,才立即关闭连接,调用socket的shutdown()函数。否则不会关闭。


muduo库对SIGPIPE的处理:

    对一个对端已经关闭的socket调用两次write, 第二次将会生成SIGPIPE信号, 该信号默认结束进程.  
    具体的分析可以结合TCP的”四次握手”关闭. TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条. 当对端调用close时, 虽然本意是关闭整个两条信道, 但本端只是收到FIN包. 按照TCP协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为TCP协议的限制, 一个端点无法获知对端已经完全关闭.
    对一个已经收到FIN包的socket调用read方法, 如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出.

    服务器可是不能退出的,所以要忽略该信号。那么muduo库是怎么处理的呢?

    为了避免客户端每次都需要写一个signal()函数去捕获SIGPIPE信号,muduo库利用了RAII机制,在EventLoop.cc中有这样的定义:

class IgnoreSigPipe
{
 public:
  IgnoreSigPipe()
  {
    ::signal(SIGPIPE, SIG_IGN);
    // LOG_TRACE << "Ignore SIGPIPE";
  }
};

IgnoreSigPipe initObj;

可见,每当客户使用EventLoop时,由于该全局对象的初始化,SIGPIPE信号被自动设置为忽略了,好省事。


python设计俄罗斯方块(附源码) 【代码】python设计俄罗斯方块(附源码) 阅读详情

相关推荐

esp32-C3固件烧录用户手册

温馨提示:1.esp32-C3 BOOT脚为GPIO9,需要从芯片小板中焊接引出2.芯片小板中已经预留了所有烧录相关的引脚,大家可以按需焊接引线3.程序下载完,使用串口查看log时,需要使用220v供电使用(请务必准守用电安全)。原因:由于插座中包含继电器,计量计等耗电设备,所以usb转串口提供的3.3V电压和电流可能会出现供电不足,表现为usb转串口与电脑连接断断续续,导致无法查看log。

hbwang的博客 1万+

send或者write socket遭遇SIGPIPE信号

当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。 又或者当一个进程向某个已经收到RST的socket执行写操作是,内核向该进程发送一个SIGPIPE信号。该信号的缺省学位是终止进程,因此进程必须捕获它以免不情愿的被终止。

herostarone的专栏 1492

家长必看!用RouterOS轻松屏蔽不良网站,保护孩子上网安全

本文为家长提供了一份详细的RouterOS路由器配置指南,旨在通过技术手段保护儿童上网安全。文章核心介绍了如何利用RouterOS的DNS静态条目功能,轻松实现禁止访问特定不良网站,并结合防火墙时间规则,为孩子构建一个纯净、可控的家庭网络环境,操作简单,无需专业背景。

weixin_28363123的博客 1266

TCP socket SIGPIPE捕获

在socket通信中,偶发的PEER关闭链接(断网、异常FIN等)系统会抛出SIGPIPE信号,默认处理方式为进程退出,需要做屏蔽处理,否则系统会自动退出。解决SIGPIPE有好几种方法:1、设置socket属性:setsocketopt(sock_fd, SOL_SOCKET, SO_NOSIGPIPE, &amp;(int){1}, sizeof(int));2、丢弃SIGPIPE信号:sig...

junlon2006的博客 1462

解决mamba_ssm库安装难题:两种高效方法详解

本文详细解析了mamba_ssm库安装失败的常见原因,并提供了两种高效的解决方案。针对其依赖C++/CUDA扩展编译的特性,文章手把手指导用户通过GitHub克隆源码编译安装,或寻找匹配的预编译Wheel文件直接安装,帮助开发者彻底解决环境配置难题,顺利部署Mamba模型。

weixin_29056865的博客 554

使用plot函数可视化F分布累积分布函数数据(R语言)

在上面的代码中,我们使用了seq()函数来生成从0到5之间,以0.1为步长的一组F值。然后,我们指定了分子自由度为3,分母自由度为8,并使用pf()函数计算了对应的F分布CDF值。在本文中,我们将介绍如何使用R语言的plot函数来可视化F分布的累积分布函数(CDF)数据。通过使用plot函数,我们可以直观地了解F分布的累积分布函数,并对F值和对应的累积概率之间的关系有更深入的认识。运行上述代码后,我们将获得一个由F值和对应的累积概率构成的图形,其中x轴表示F值,y轴表示累积概率。

LogicGuruX的博客 502

【Linux多线程服务端编程】| 【07】TCP网络编程实例文件传输

【代码】【Linux多线程服务端编程】| 【07】TCP网络编程实例文件传输。

weixin_45926547的博客 1460

云原生监控:Prometheus+Grafana监控Nginx实战

在云原生环境中,应用的高可用性和性能优化至关重要。Nginx作为一款广泛使用的高性能Web服务器、反向代理服务器及电子邮件(IMAP/POP3)代理服务器,对其进行有效的监控能够及时发现潜在问题,保障服务的稳定运行。本文章的目的是详细介绍如何使用Prometheus和Grafana构建一套完整的监控系统来监控Nginx,范围涵盖从环境搭建、数据采集、存储到可视化展示的整个流程。

AI云原生与云计算技术学院 1574

muduo库使用示例之文件传输filetransfer

文件传输(发送)有下面3个版本,代码实现在muduo\examples\filetransfer 目录中。 版本1:一次性将文件读入内存并发送 一次性将文件读入内存中的string fileContent,一次性调用send(const string&amp;)发送完毕。==&gt; 缺点:消耗内存很大 注解1:send函数是非阻塞的,立刻返回。不用担心数据什么时候发送给对等方。这个由网络库mud...

JaweG 1428

vLLM专题(七)-支持的模型列表

vLLM支持跨多种任务的生成式和池化模型。如果一个模型支持多个任务,您可以通过–task参数来设置任务。对于每个任务,我们列出了已在vLLM中实现的模型架构。每个架构旁边,我们列出了一些使用该架构的流行模型。

1259

Mysql授权允许远程访问

前言 Mysql授权允许远程登录后,linux中安装的mysql可以与Navicat for Mysql(可视化)等连接,在可视化软件下管理数据库。 注意:不能使用临时密码 设置密码命令: alter user 'root'@'localhost' identified by '这里是你的密码'; 授权允许远程访问 一、授权命令: grant all privileges on *.* to '...

🏆 前端领域优质创作者 3万+

第7章 muduo编程示例(2)

本章将介绍如何用muduo网络库完成常见的TCP网络编程任务。内容如下: 1.[UNP]中的五个简单协议。2.文件传输,示范非阻塞TCP网络程序中如何完整地发送数据。3.Boost.Asio中的示例。4.muduo Buffer class的设计与使用。5.Protobuf编码解码器(codec)与消息分发器(dispatcher)。6.限制服务器的最大并发连接数。7.Java Netty中的示例。8.用于测试两台机器的往返延迟的roundtrip。9.用timing wheel踢掉空闲连接。等等。。。

chenyijun的专栏 812

【Realtek sdk-3.4.14b】RTL8197FH+RTL8363NB当WAN/LAN IP冲突的时候,再次切换回正常的WAN,出现电脑无法上网也无法ping通设备网关问题分析及解决方案

如上图所示,当WAN和LAN网段冲突的时候,会导致路由设备异常,无法上网,但是修改为不冲突的网段之后,依然无法上网,同时下级PC也无法ping通路由设备,正常的如下图所示如上图所示,正常网络拓扑是可以正常的,但是在上级和路由LAN冲突的时候,再次切换为正常的网络也是异常问题复现步骤1.路由设备WAN口接到一个和本级路由是相同网段的设备2.拔掉WAN口,将WAN口接到一个和本级路由是不同网段的设备3.此时PC无法访问路由器。

wgl307293845的博客 665

muduo网络库

muduo网络库摘要 muduo是一个专为Linux设计的高性能TCP网络编程库,采用主从Reactor模型实现高并发处理。核心架构包括: 事件驱动模型:基于epoll的事件循环机制,每个线程一个EventLoop,实现无锁高并发 分层设计:分为底层工具和网络框架两层,通过shared_ptr管理连接生命周期 核心组件: EventLoop:线程事件循环核心 Channel:文件描述符事件管理器 TcpServer/TcpClient:服务端/客户端框架 Acceptor/Connector:连接管理组件

CX33071的博客 424

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

SIGPIPE信号--解决Linux socket中接收端退出导致发送端send退出问题

在Linux下测试,然后用C++写了客户端用千万级别数量的短链接进行压力测试.  但是服务器总是莫名退出,没有core文件. 最后问题确定为, 对一个对端已经关闭的socket调用两次write, 第二次将会生成SIGPIPE信号, 该信号默认结束进程. 具体的分析可以结合TCP的"四次握手"关闭. TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条

guov100的专栏 1912

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

[TCP]向已关闭的socket执行send(),会出现“signal SIGPIPE, Broken pipe“错误,导致server进程退出的原因和对策

send失败导致reactor进程结束的问题。

zlllc的博客 1200

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

muduo库的使用

muduo库使用的简单例子

m0_62689947的博客 580

学习开源网络库muduo的一点心得

最近这一周在学习陈硕的muduo网络库。看到第八章时

limwz的专栏 1407

SIGPIPE

send或者write socket遭遇SIGPIPE信号 当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。 又或者当一个进程向某个已经收到RST的socket执行写操作是,内核向该进程发送一个SIGPIPE信号。该信号的缺省学位是终止进程,因此进程必须捕获它以免不情愿的被终止。 根据信号的默认处理规则...

神棍之路 2686

《muduo学习》总结

析构的安全性 在多线程中,两个裸指针指向同一个对象是不安全的,无法保证析构的正确性,一个线程在析构一个对象而同时另一个线程在使用它,即一个线程不能确定是否对象已经完全死掉,有可能另一处析构函数正在执行。 shared_ptr可以保证在所有地方都不再使用时才释放,之前保证资源的有效性。 2.shared_ptr的线程安全 shared_ptr可以实现线程安全的释放对象,但shared_ptr本身不是线程安全的!如果一个shared_ptr对象可以被几个线程同时看到,那么读写这个对象就需要加锁!因为它内部有两.

weixin_44708428的博客 244

服务器开发中的错误处理与连接关闭-结合muduo库

服务器开发中的错误处理与连接关闭 RST包和IO函数 收到RST包的情况 RST包即复位包,一般在连接发生错误的时候由传输层发送的一个包,R ST用来异常的关闭连接。发送RST包关闭连接时,不必等待缓冲区的数据都发出去(普通的close会先将发送缓冲区中的数据发送完毕后再发送fin包),直接就丢弃缓冲区的包发送RST包,而接收端收到RST后,也不必发送ACK确认。常见的收到RST包的情况有...

hdadiao的博客 809

【Muduo源码剖析笔记】 网络库之EventLoop

Muduo库源码阅读笔记

LeeEdgar的博客 405

关于SIGPIPE信号

转自:http://blog.sina.com.cn/s/blog_502d765f0100kopn.html 我写了一个服务器程序,在Linux下测试,然后用C++写了客户端用千万级别数量的短链接进行压力测试.  但是服务器总是莫名退出,没有core文件. 最后问题确定为, 对一个对端已经关闭的socket调用两次write, 第二次将会生成SIGPIPE信号, 该信号默认结束进程. ...

ice_beauty1的专栏 488

muduo库学习笔记-如何保证数据成功发送

如何保证数据一定能够被发出去 在网络编程中常常利用write将数据拷贝到内核的发送缓冲区,然后由协议将数据发送到对端,但是如何保证一次write能够将所有的数据都拷贝到内核缓冲区呢?如果还有数据没有被拷贝进去怎么办?如果拷贝的过程中内核缓冲区满了怎么办?如果拷贝的过程中write返回-1错误又怎么办?针对这些问题,muduo库都进行了解决。 muduo是怎么发送数据的? muduo库并不是将所...

hdadiao的博客 684

muduo 的 shutdown() 没有直接关闭 TCP 连接?

http://blog.youkuaiyun.com/Solstice/article/details/6208634 今天收到一位网友来信: 在 simple 中的 daytime 示例中,服务端主动关闭时调用的是如下函数序列,这不是只是关闭了连接上的写操作吗,怎么是关闭了整个连接? 1: void DaytimeServer::onConnection(const mu...

diaoqu4574的博客 144

TCP send数据时 "Program received signal SIGPIPE, Broken pipe."解决

一、现象描述 在利用librdkafka同kafka broker通信过程中,当kafka broker意外退出时(如kill -9),librdkafka接口的sendmsg接口报出了“Program received signal SIGPIPE, Broken pipe.” 这个错误具有典型性,根据网络搜索的结果,这个一般是由于向一个被破坏的socket连接或者pipe读写数据造成的,向...

xiaojun11的专栏 2172
上一篇: 判断一个单链表是否是回文链表
下一篇: muduo库的Connector以及TcpClient的使用
FreeeLinux
博客等级 码龄11年 421粉丝 356原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值