muduo网络库学习之EventLoop(五):TcpConnection生存期管理(连接关闭)

监听套接字可读事件是POLLIN; 已连接套接字正常可读是POLLIN; 正常可写是POLLOUT; 对等方close/shutdown关闭连接,已连接套接字可读是POLLIN | POLLHUP;

时序图分析:

注意:将TcpConnectionPtr 在connections_ 中 erase 掉,时并不会马上 析构TcpConnection 对象(引用计数不为0),

因为此时正处于Channel::handleEvent() 中,如果析构了TcpConnection,那么它的成员channel_ 也会被析构,即导致

core dump.

也就是说TcpConnection 对象生存期要长于handleEvent() 函数,直到执行完connectDestroyed() 后才会析构。

在EventLoop(三)的基础上,在TcpConnection 构造函数中再添加:

// 连接关闭,回调TcpConnection::handleClose
channel_->setCloseCallback(
    boost::bind(&TcpConnection::handleClose, this));
// 发生错误,回调TcpConnection::handleError
channel_->setErrorCallback(
    boost::bind(&TcpConnection::handleError, this));

在 TcpServer::newConnection() 中再添加:

void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)
{
    .....
    conn->setCloseCallback(
        boost::bind(&TcpServer::removeConnection, this, _1));
}

在TcpConnection::handleRead() 中再添加:

void TcpConnection::handleRead(Timestamp receiveTime)
{
    ssize_t n = ::read(channel_->fd(), buf, sizeof buf);
    if (n > 0)
    {
        messageCallback_(shared_from_this(), buf, n);
    }
    else if (n == 0)
    {
        handleClose();
    }
    else
    {
        errno = savedErrno;
        LOG_SYSERR << "TcpConnection::handleRead";
        handleError();
    }
}

假设现在已经建立了一个新连接,经过几次收发数据后,对等方关闭close套接字,TcpConnection::channel_ 可读事件发生,poll返

回,调用Channel::handleEvent()处理活动通道,调用TcpConnection::handleRead(),::read() 返回0,进而调

用TcpConnection::handleClose()

void TcpConnection::handleClose()
{
    setState(kDisconnected);
    channel_->disableAll();

    TcpConnectionPtr guardThis(shared_from_this());
     connectionCallback_(guardThis);      

    // must be the last line
    closeCallback_(guardThis);  // 调用TcpServer::removeConnection
}

这里需要注意的是有关shared_from_this() 的使用:

class TcpConnection : boost::noncopyable,
    public boost::enable_shared_from_this<TcpConnection>

shared_from_this()  会用当前对象的裸指针构造一个临时智能指针对象,引用计数加1,但马上会被析构,又减1,故无论调用多少

次,对引用计数都没有影响。

TcpConnectionPtr guardThis(shared_from_this()); 为什么不能直接写成TcpConnectionPtr guardThis(this); ?

因为这样写的话,guardThis的引用计数就为1,而不是2,如下例所示:

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

class Y: public boost::enable_shared_from_this<Y>
{
public:
    boost::shared_ptr<Y> f()
    {
        return shared_from_this();
    }

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

int main(void)
{
    boost::shared_ptr<Y> p(new Y);
    boost::shared_ptr<Y> q = p->f();

    Y *r = p->f2();
    assert(p == q);
    assert(p.get() == r);

    std::cout << p.use_count() << std::endl; //2
    boost::shared_ptr<Y> s(r);
    std::cout << s.use_count() << std::endl; //1
    assert(p == s); //断言失败

    return 0;
}

直接用裸指针生成智能指针对象s后,s的引用计数只是为1,而不会将p引用计数提升为3;如前所述,TcpConnection的生存期就会

成为问题,不能在恰当的时候被释放。

进而调用TcpServer::removeConnection(), 

void TcpServer::removeConnection(const TcpConnectionPtr &conn)
{
    size_t n = connections_.erase(conn->name());

    loop_->queueInLoop(
        boost::bind(&TcpConnection::connectDestroyed, conn));

}

handleEvent() 处理完毕后,当前IO线程继续执行doPendingFunctors() 函数,取出 TcpConnection::connectDestroyed() 执行:

void TcpConnection::connectDestroyed()
{
    loop_->assertInLoopThread();
    if (state_ == kConnected)
    {
        setState(kDisconnected);
        channel_->disableAll();

        connectionCallback_(shared_from_this());
    }
    channel_->remove(); //poll 不再关注此通道
}

参考:

《UNP》

muduo manual.pdf

《linux 多线程服务器编程:使用muduo c++网络库》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java成神之路

Java微信公众平台开发_07_JSSDK图片上传

(2)对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式 (即 key1=value1&key2=value2…)拼接成...

1891
来自专栏余林丰

利用Java提供的Observer接口和Observable类实现观察者模式

对于观察者模式,其实Java已经为我们提供了已有的接口和类。对于订阅者(Subscribe,观察者)Java为我们提供了一个接口,JDK源码如下: 1 pack...

2048
来自专栏dotnet core相关

jquery调用javascript方法

本来想找个“优雅”一点的方法,类似C#在调用C++方法时候的Invoke之类的。没找到,后来想想,其实也没必要,直接写就好了,算最优雅了吧。只是少了VS的Int...

713
来自专栏开发与安全

muduo网络库学习之muduo_inspect 库涉及到的类

muduo inspect 库通过HTTP方式为服务器提供监控接口, 现在只实现进程相关信息的监控,通过成员ProcessInspector 实现。 Pro...

1935
来自专栏zcqshine's blog

SpringMVC下获取验证码

4308
来自专栏草根专栏

Rx.NET 简介

官网: http://reactivex.io/ 它支持基本所有的主流语言. 这里我简单介绍一下Rx.NET. 基本概念和RxJS是一样的. 下面开始切入正题....

3349
来自专栏Java成神之路

Java企业微信开发_08_素材管理之下载微信临时素材到本地服务器

请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&med...

1252
来自专栏小樱的经验随笔

HUST 1583 长度单位

1583 - 长度单位 时间限制:1秒 内存限制:128兆 536 次提交 103 次通过 题目描述我们生活中常用的长度单位有英尺、英寸和厘米,众所周知它们...

34310
来自专栏开发与安全

muduo网络库学习之EventLoop(三):Socket、Acceptor、TcpServer、TcpConnection(连接建立,接收消息)

1、Socket 操作封装 Endian.h 封装了字节序转换函数(全局函数,位于muduo::net::sockets名称空间中)。 Sock...

2020
来自专栏Java与Android技术栈

RxJava 线程模型分析

RxJava的被观察者在使用操作符时可以利用线程调度器--Scheduler来切换线程,例如

1772

扫码关注云+社区