muduo网络库学习笔记(13):TcpConnection生命期的管理

2023-11-17

本篇通过分析muduo中TcpConnection对断开连接事件的处理,来学习muduo网络库对TcpConnection生命期的管理。

TcpConnection对连接断开事件的处理

首先,我们来看一下TcpConnection处理连接断开事件时函数调用的流程:
这里写图片描述
我们这里所指的连接断开,都是指被动关闭,即对方先关闭连接,本地read(2)返回0,触发关闭逻辑。

分析:一个服务器(TcpServer)维护了一个连接列表,当一个连接断开时,TcpConnection中的通道处于活跃的状态,EventLoop的事件循环返回了这个活跃的通道,然后调用通道的handleEvent()函数来处理。连接关闭是可读事件,进而回调了TcpConnection的handleRead()函数,handleRead()中又调用了read()返回为0,判断read()返回为0又会调用handleClose()函数。handleClose()函数会回调TcpServer的removeConnection()函数,其中会调用erase()将该连接从连接列表移除。

这里我们需要注意的是——一般情况下,将连接从连接列表移除后,我们就可以将这个连接对象销毁(delete)掉了,但是在这里我们不能立即销毁这个连接对象,原因如下:

如果我们销毁了这个对象,TcpConnection所包含的Channel对象也就跟着被销毁了,而我们当前正在调用Channel对象的handleEvent()函数,就会出现core dump。所以,我们必须保证TcpConnection的生存期长于Channel::handleEvent()函数。

muduo选择用智能指针shared_ptr来管理TcpConnection的生命期,并且让TcpConnection类继承自boost::enable_shared_from_this。

源码分析

具体代码改动如下:

代码片段1:Channel的改动
文件名:Channel.cc

// 析构函数中会判断Channel是否仍处于事件处理状态
// 在事件处理期间Channel对象不会析构
Channel::~Channel()
{
  assert(!eventHandling_);
}

// 开始处理事件之前,会将事件处理标志位置为true
// 直到事件处理完,事件处理标志位再置为false
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
  eventHandling_ = true;
  if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
  {
    if (logHup_)
    {
      LOG_WARN << "Channel::handle_event() POLLHUP";
    }
    if (closeCallback_) closeCallback_();
  }
  if (revents_ & POLLNVAL)
  {
    LOG_WARN << "Channel::handle_event() POLLNVAL";
  }
  if (revents_ & (POLLERR | POLLNVAL))
  {
    if (errorCallback_) errorCallback_();
  }
  if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
  {
    if (readCallback_) readCallback_(receiveTime);
  }
  if (revents_ & POLLOUT)
  {
    if (writeCallback_) writeCallback_();
  }
  eventHandling_ = false;
}
代码片段2:TcpConnection::handleRead()函数的改动
文件名:TcpConnection.cc

void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_->assertInLoopThread();
  int savedErrno = 0;
  char buf[65536];
  ssize_t n = ::read(channel_->fd(), buf, sizeof buf);
  // 根据read(2)的返回值分别调用messageCallback_()、handleClose()和handleError()
  if (n > 0)
  {
    messageCallback_(shared_from_this(), buf, n);
  }
  else if (n == 0)
  {
    handleClose();
  }
  else
  {
    errno = savedErrno;
    LOG_SYSERR << "TcpConnection::handleRead";
    handleError();
  }  
}
代码片段3:TcpConnection::handleClose()函数
文件名:TcpConnection.cc

void TcpConnection::handleClose()
{
  loop_->assertInLoopThread();
  LOG_TRACE << "fd = " << channel_->fd() << " state = " << state_;
  // 断定此时连接处于已连接状态
  assert(state_ == kConnected);
  // we don't close fd, leave it to dtor, so we can find leaks easily.
  channel_->disableAll();

  // must be the last line
  // 调用所设置的连接关闭回调函数
  closeCallback_(shared_from_this()); 
}
代码片段4:TcpServer::removeConnection()函数
文件名:TcpServer.cc

void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
  loop_->assertInLoopThread();
  LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_
           << "] - connection " << conn->name();
  // 将断开的连接从连接列表中移除
  size_t n = connections_.erase(conn->name());
  (void)n;
  assert(n == 1);
  // 此处一定要用EventLoop::queueInLoop(),避免Channel对象被提前销毁
  // 这里用boost::bind让TcpConnection的生命期长到调用connectDestroyed()的时刻
  // 使用boost::bind得到一个boost::function对象,会把conn传递进去,引用计数会加1
  loop_->queueInLoop(
      boost::bind(&TcpConnection::connectDestroyed, conn));
}

Channel::handleEvent()事件处理完后就会调用functors(见博客“muduo网络库学习笔记(11):有用的runInLoop()函数”)即调用TcpConnection::connectDestroyed()。

代码片段5:TcpConnection::connectDestroyed()函数
文件名:TcpConnection.cc

// connectDestroyed()是TcpConnection析构前最后调用的一个成员函数
// 它通知用户连接已断开
void TcpConnection::connectDestroyed()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnected);
  setState(kDisconnected);
  channel_->disableAll();
  connectionCallback_(shared_from_this());  // 回调用户设定的回调函数
  loop_->removeChannel(get_pointer(channel_));  // 从poll/epoll中移除channel
}

boost::enable_shared_from_this

由于TcpConnection模糊的生命期,我们用到了shared_ptr来管理它的生命期,并让TcpConnection类继承自boost::enable_shared_from_this。那么,boost::enable_shared_from_this的作用是什么呢?

使用示例:

#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <cassert>

// class Y继承自boost::enable_shared_from_this<Y>
class Y: public boost::enable_shared_from_this<Y>
{
public:
    boost::shared_ptr<Y> f()
    {
        return shared_from_this();  // 返回指向自身的shared_ptr
    }

    Y* f2()
    {
        return this; 
    }
};

int main()
{
    boost::shared_ptr<Y> p(new Y);  // p的引用计数为1
    boost::shared_ptr<Y> q = p->f();  // 将当前对象转换为一个shared_ptr,赋值给q,此时p/q引用计数为2

    Y* r = p->f2(); 
    assert(p == q);  // 断言正确
    assert(p.get() == r);  // 断言正确

    std::cout << p.use_count() << std::endl;  // 输出:2

    // 构造一个shared_ptr对象s,将r赋给s
    // 打印出的s的引用计数应该为1,而不是3
    // 因为此时构造了一个新的、独立的shared_ptr对象,而不是将一个shared_ptr对象赋值给另一个shared_ptr对象
    boost::shared_ptr<Y> s(r); 
    std::cout << s.use_count() << std::endl;
    assert(p == s);  // 断言失败

    return 0;
}

测试结果如图:
这里写图片描述

所以,在TcpConnection的生命期管理过程中,如果我们直接用this指针传递对象,可能会构建一个新的shared_ptr对象,并不是直接将我们之前管理的对象的shared_ptr拷贝过去从而使引用计数加1,故我们需要用到boost::enable_shared_from_this的shared_from_this()函数。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

muduo网络库学习笔记(13):TcpConnection生命期的管理 的相关文章

  • docker 入门指南

    docker Docker is an open platform for developing shipping and running applications Docker enables you to separate your a
  • Matlab零基础入门

    前言 本篇是随笔 一段时间没用Matlab 简单复习了下 都是入门知识 零基础可读 文章目录 1 初步认识界面和命名 2 数据类型和矩阵 3 元胞数组和结构体 3 1 元胞数组 3 2 eye 3 3 3 magic 3 4 结构体 4 矩
  • RNA-seq——学习路线、学习经验、实战项目、学习总结

    1 参考课程和博客 B站 RNA seq转录组数据分析入门实战 生信技能树 转录组测序数据分析 简书 RNA seq 1 用conda安装RNA seq所需要的工具 简书 RNA seq 2 1 原始数据下载的几种方法 简书 RNA seq
  • python接口自动化(三)--如何设计接口测试用例(详解)

    简介 上篇我们已经介绍了什么是接口测试和接口测试的意义 在开始接口测试之前 我们来想一下 如何进行接口测试的准备工作 或者说 接口测试的流程是什么 有些人就很好奇 接口测试要流程干嘛 不就是拿着接口文档直接利用接口 测试工具测试嘛 其实 如
  • 开发EduSoho v8.7.10 本地播放视频超时或者快进后网络错误导致视频下载中途失败。鉴权播放次数问题

    EduSoho v8 7 10 本地播放视频超时或者快进后网络错误导致视频下载中途失败 鉴权播放次数问题 文件路径 src AppBundle Twig WebExtension php protected function makeTok
  • CFileDialog 多文件选择注意事项

    当选择文件数量比较多的时候 发现CFileDialog返回文件名并不完整 翻阅MSDN发现文件名长度是有限制的 解决思路 CFileDialog dlgOpen TRUE T txt NULL OFN HIDEREADONLY OFN RE
  • 【转】游戏汉化之Tile全格式解读 by 阿一

    最近在破解一些图片的格式 并想导出PNG 不过老是记不住bpp的格式 转载之 方便查看 做些锚记 标准1BPP NDS 1BPP 标准2BPP VB 2BPP NGP 2BPP NES 2BPP 1BPP 1BPP GB 2BPP 1BPP
  • SpringCloud2架构图

    先来个简洁版 1 外部或者内部的非Spring Cloud项目都统一通过API网关 Zuul 来访问内部服务 zuul是对外暴露的唯一接口相当于路由的是controller的请求 2 网关接收到请求后 从注册中心 Eureka 获取可用服务
  • Unity泛光效果消失问题

    关于Unity泛光效果消失问题解决过程 问题描述 第一次尝试解决 第二次尝试解决 第三次尝试解决 问题描述 之前一直在做的一个项目 在一次想要添加UI泛光效果失败后 发现项目中已有的泛光效果也消失了 第一次尝试解决 因为问题是在添加插件Po
  • linux服务器编译报错:DSO missing from command line原因及解决办法

    报错信息提示包含以下两行 undefined reference to symbol libfastrtps so 1 error adding symbols DSO missing from command line 原因 提示说符号没
  • SpringMVC异常处理

    为了统一处理代码运行过程中出现的异常 给用户一个更友好的异常界面 需要引入springMVC的异常处理功能 为了演示这个功能 本文实现一个比较常用的需求 将所有的异常归为两类 一类是程序员自己创建的异常类 另一类是系统或框架定义的异常类 程
  • junit如何测试没有返回值的方法

    方法里总有些操作 只要测试结果对就可以了 没有必要说非要有返回值 马士兵

随机推荐

  • 深入理解 SQL 中的 Grouping Sets 语句

    前言 SQL 中 Group By 语句大家都很熟悉 根据指定的规则对数据进行分组 常常和聚合函数一起使用 比如 考虑有表 dealer 表中数据如下 id Int city String car model String quantity
  • Linux系统下ping命令报错 name or service not know

    问题描述 CentOS 但是当执行ping命令的时候 提示name or service not known 解决方法 1 添加DNS服务器 1 vi etc resolv conf 进入编辑模式 增加如下两行内容 分别是首选DNS服务器和
  • logback--进阶--05--自定义Appenders

    logback 进阶 05 自定义Appenders 代码位置 https gitee com DanShenGuiZu learnDemo tree master logback learn 1 介绍 1 1 继承关系图 可以看到Appe
  • C++ 多态和虚函数

    一 先搞清override overload overwrite的区别 1 overload 重载 不是多态 在C 程序中 可以将语义 功能相似的几个函数用同一个名字表示 但参数不同 包括类型 顺序不同 即函数重载 1 相同的范围 在同一个
  • 药明康德成都研发中心投入运营;中国白酒行业净利润将迎来七年来首次下滑

    今日看点 药明康德成都研发中心正式投入运营 该研发中心将成为药明康德上海研发总部以外 又一个覆盖化学及生物学的新药发现整体研发平台 将为客户提供从小分子药物设计 合成 分析 体内体外生物学 肿瘤免疫学等全方位 一体化的新药研发服务 该研发中
  • 【编译原理】LR(1)分析方法(c++实现)

    前文回顾 编译原理 LR 0 分析方法 c 实现 编译原理 SLR 1 分析方法 c 实现 算法 来自龙书第二版 代码 和SLR的区别其实只是DFA中多了一个搜索符 构建分析表的时候规约项的列是相应的搜索符而已 代码基本上就在SLR的代码上
  • 拼多多anti_content算法

    最近拼多多的anti content算法更新了 闲着无聊就看了看 总思路如下 首先我们抓包 看到我们今天的目标 anti content 现在 我们通过堆栈入口随便下个断点 然后我们在CallStack中向下寻找 经过漫长的寻找 我们可以在
  • route命令的详细使用介绍

    1 命令格式 route f p Command Destination mask Netmask Gateway metric Metric if Interface 2 命令功能 Route命令是用于操作基于内核ip路由表 它的主要作用
  • es单机数据迁移到另一个es单机

    将一个Elasticsearch单机的数据迁移到另一个Elasticsearch单机主要可以分为两步 第一步 备份原有数据 将原有Elasticsearch单机中的数据备份到一个文件中 可以使用 Elasticsearch 提供的 snap
  • 第三十讲:神州路由器路由重分发配置

    RIP和OSPF协议是目前应用最广泛的路由协议 两种协议交接的场合也很多见 两种协议的重分布是比较常见的配置 主校区原来所采用的网络协议为OSPF 而分校区采用的路由协议是RIP 采用RIP和OSPF重分发技术可以解决此问题 实验拓扑图如下
  • PostgreSQL、Greenplum 日常监控 和 维护任务

    背景 Greenplum的日常监控点 评判标准 日常维护任务 展示图层 由于一台主机可能跑多个实例 建议分层展示 另外 即使是ON ECS虚拟机 一个虚拟机一个实例一对一的形态 的产品形态 实际上也建议分层展示 以示通用性 主机级图层 1
  • vue项目全局内引入外部CDN地址的js文件

    通过vue cli 构建的vue项目的根目录下 有一个index html文件 例如
  • 跳过selenium检测爬取淘宝直通车

    最近 有对阿里商家端进行一些数据爬取 这次爬取的是直通车人群溢价数据 发现对selenium的检测相当厉害 然而我的回答是 你强任你强 清风拂山岗 咱人工登录怕过谁 什么cokies user agent selenium检测 token
  • Android构建问题解决方案

    1 问题一 解决方法 在app下的build gradle中加上 packagingOptions exclude META INF DEPENDENCIES exclude META INF NOTICE exclude META INF
  • 怎么计算union和struct中字节数计算

    首先我的运行结果都是在64位系统的Xcode中运行的 然后 这个只是由于对于标准的位移量方法看得头疼 自己总结出来的 如果有错误或者不明欢迎留言 字节 一般成8位为一个字节 在Xcode中sizeof int 等于4 在这里也就采用int占
  • fastjson(七)处理超大对象和超大JSON文本

    当需要处理超大JSON文本时 需要Stream API 在fastjson 1 1 32版本中开始提供Stream API 来看一下示例代码 示例对象 package json fastjson StreamApi import java
  • java金额的正则表达式,【转】关于金额应验的Java方法(采用正则表达式)

    public static boolean isNumber String str java util regex Pattern pattern java util regex Pattern compile 0 9 java util
  • 2016阿里云121款产品和解决方案全向图(9月制)

    2016阿里云121款产品和解决方案全向图 9月制 摘要 云栖社区在9月底又更新了 2016阿里云产品全向图和解决方案全向图 导语 DT时代 一切都将走向数据化 可视化 在阿里云所阐述的 技术拓展商业的边界 商业驱动技术的变革 理念中 密集
  • 汇编语言(王爽第三版) 实验5编写、调试具体多个段的程序

    参考 http blog sina com cn s blog 171daf8e00102xclx html 汇编语言实验答案 王爽 https wenku baidu com view a1cd7c6c1fb91a37f111f18583
  • muduo网络库学习笔记(13):TcpConnection生命期的管理

    本篇通过分析muduo中TcpConnection对断开连接事件的处理 来学习muduo网络库对TcpConnection生命期的管理 TcpConnection对连接断开事件的处理 首先 我们来看一下TcpConnection处理连接断开