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 条评论
登录 后参与评论

相关文章

来自专栏岑玉海

hbase源码系列(十三)缓存机制MemStore与Block Cache

这一章讲hbase的缓存机制,这里面涉及的内容也是比较多,呵呵,我理解中的缓存是保存在内存中的特定的便于检索的数据结构就是缓存。 之前在讲put的时候,put是...

3177
来自专栏Spark生态圈

[spark] spark推测式执行

推测任务是指对于一个Stage里面拖后腿的Task,会在其他节点的Executor上再次启动这个task,如果其中一个Task实例运行成功则将这个最先完成的Ta...

682
来自专栏深度学习计算机视觉

java用内部类构造链表实现相关方法

代码 import com.sun.corba.se.impl.orbutil.graph.Node; /** * Created by junyi.pc ...

2689
来自专栏linux驱动个人学习

SMP多核启动

在 Linux系统中,对于多核的ARM芯片而言,在Biotron代码中,每个CPU都会识别自身ID,如果ID是0,则引导Bootloader和 Linux内核执...

925
来自专栏Linux驱动

5.分析内核中断运行过程,以及中断3大结构体:irq_desc、irq_chip、irqaction(详解)

本节目标:    分析在linux中的中断是如何运行的,以及中断3大结构体:irq_desc、irq_chip、irqaction 在裸板程序中(参考stmdb...

2548
来自专栏武培轩的专栏

Runtime源码解析(JDK1.8)

package java.lang; import sun.reflect.CallerSensitive; import sun.reflect.Refle...

3359
来自专栏码匠的流水账

聊聊kafka的partition分配

本文主要研究一下kafka的partition分配,主要是key到parition的映射,partition对consumer的分配,以及partition的r...

401
来自专栏小狼的世界

Taints 与 Tolerations

节点亲和性是描述Pods如何分配到一个或一组节点的策略,与之相反 Taints 描述节点拒绝一个或一组Pods的策略。其实现原理为首先通过kubectl tai...

311
来自专栏开发与安全

中断机制和中断描述符表、中断和异常的处理

注:本分类下文章大多整理自《深入分析linux内核源代码》一书,另有参考其他一些资料如《linux内核完全剖析》、《linux c 编程一站式学习》等,只是为了...

1860
来自专栏ImportSource

单线程的Redis为什么辣么快?

你之所以问这样的问题。是因为你认为只有多线程分别接收connection才可以更快,就像过去的tomcat那样,同时开多个线程来响应。

622

扫码关注云+社区