muduo 源码分析(四)一些有趣的设计

本文深入分析muduo网络库中如何利用Time Wheel设计断开空闲连接的机制,包括原理与实现细节。通过循环队列和智能指针管理连接状态,确保高效且准确地断开无活动的连接。
摘要由CSDN通过智能技术生成

目录

1. Buffer

2. 断开空闲连接

2.1 原理

2.1.1 自生自灭

2.1.2 临时续命

2.1.3 多个连接

2.2 实现

2.2.1 新连接到达

2.2.2 定时器超时

2.2.3 新消息到来

参考文献

 


1. Buffer

muduo的作者对Buffer解释得非常清楚:https://blog.csdn.net/Solstice/article/details/6329080

2. 断开空闲连接

为了减轻server的负载,除了限制最大连接数以外,还可以关闭空闲的连接。如果一个连接连续N秒内没收到消息,就认为此连接太闲,应该断开。

2.1 原理

连接超时用一个循环队列来处理。假如超时时间定为8秒,则队列由8个桶(bucket)组成。第一个桶放置下一秒将要timeout的连接,第二个桶放置下两秒将要timeout的连接 .....第八个桶放置最新创建的连接。此外,每秒钟定时地往队列里添加一个空桶,这样之前的桶就往前移动,直到掉出队列。

这种方式称为Time wheel (时间盘)。下面模拟一下Time wheel的两种工作过程。

2.1.1 自生自灭

1. 在某时刻,conn1建立,被放到队列的最后一个桶。

2. 时间流逝,conn1没有新的数据发来,tail指向新的桶,conn1所在的桶变成了倒数第二个桶。

3. 又过了几秒钟,不知不觉conn1所在的桶已经走到淘汰的边缘了。

4. 再等了一秒,conn1还是没有消息发来,宣告out.

2.1.2 临时续命

若在conn1被断开前收到数据,则被再次加到最后一个桶。

时间继续流逝,但至少conn1获得了重生一次的机会。

2.1.3 多个连接

1.假设在同一秒内好几个连接被创建了,它们会被放在同一个桶里,具有相同的寿命。

2.但后来,conn1有消息发来了,得以续命,conn2却默默无闻,于是它们的距离拉开了。

 

2.2 实现

算法的实现可以说是充分利用了智能指针的特性。

队列:boost::circular_buffer

桶:boost::unordered_set

桶里面装的东西:Entry (定义如下,Entry里包含了connection的week ptr)

  struct Entry : public muduo::copyable
  {
    explicit Entry(const WeakTcpConnectionPtr& weakConn)
      : weakConn_(weakConn)
    {
    }

    ~Entry()
    {
      muduo::net::TcpConnectionPtr conn = weakConn_.lock();
      if (conn)
      {
        conn->shutdown(); // 如果conn还活着,就断开它
      }
    }

    WeakTcpConnectionPtr weakConn_; // connection的弱引用
  };


  typedef boost::shared_ptr<Entry> EntryPtr;
  typedef boost::weak_ptr<Entry> WeakEntryPtr;
  typedef boost::unordered_set<EntryPtr> Bucket;   // 桶
  typedef boost::circular_buffer<Bucket> WeakConnectionList; // 队列

  muduo::net::TcpServer server_;
  WeakConnectionList connectionBuckets_;

下面讲述算法的过程:

2.2.1 新连接到达

void EchoServer::onConnection(const TcpConnectionPtr& conn)
{
  LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> "
           << conn->localAddress().toIpPort() << " is "
           << (conn->connected() ? "UP" : "DOWN");

  if (conn->connected())
  {
    EntryPtr entry(new Entry(conn));
    connectionBuckets_.back().insert(entry);
    dumpConnectionBuckets();
    WeakEntryPtr weakEntry(entry);
    conn->setContext(weakEntry);
  }
  else
  {
    assert(!conn->getContext().empty());
    WeakEntryPtr weakEntry(boost::any_cast<WeakEntryPtr>(conn->getContext()));
    LOG_DEBUG << "Entry use_count = " << weakEntry.use_count();
  }
}

读者可能会有几个问题:

1.为什么要Entry要持有connection的弱引用?

因为Entry析构时要负责断开connection,另外,Entry不应该管理connection的生命周期,所以不用shared_ptr.

2. 为什么要把Entry的强引用(shared_ptr) 放到bucket里?

试想一下,当bucket被挤出队列时,bucket会析构,如果Entry 引用计数为0,就会析构,从断开connection,所以要用shared_ptr.

3. 为什么要把Entry的弱引用传给connection 持有?

持有是为了在新消息到来时更新bucket,不持shared_ptr是因为connection不应该增加Entry的引用计数,否则如果connection一直活着,Entry就无法析构。

综上,Entry 和 connection互相持有对方的弱引用,bucket持有Entry的强引用,当Entry引用计数为0时,Entry析构,连接断开。

2.2.2 定时器超时

谁能想到,超时函数就一行代码:

void EchoServer::onTimer()
{
  connectionBuckets_.push_back(Bucket());
  dumpConnectionBuckets(); // 只是打印所有connection的状态,可忽视
}

boost::circular_buffer 是一个定长的列表,添加一个bucket,所有其它已存在的bucket都会向前移一个位置,相当于时间盘的转动。

2.2.3 新消息到来

新消息的到来扮演着救火员的角色:

void EchoServer::onMessage(const TcpConnectionPtr& conn,
                           Buffer* buf,
                           Timestamp time)
{
  string msg(buf->retrieveAllAsString());
  LOG_INFO << conn->name() << " echo " << msg.size()
           << " bytes at " << time.toString();
  conn->send(msg);

  assert(!conn->getContext().empty());
  WeakEntryPtr weakEntry(boost::any_cast<WeakEntryPtr>(conn->getContext()));
  EntryPtr entry(weakEntry.lock());
  if (entry)
  {
    connectionBuckets_.back().insert(entry);
    dumpConnectionBuckets();
  }
}

从connection对象中取出Entry的弱引用 , 将其提升为强引用EntryPtr,加到队列的最后一个桶,Entry的引用计数加1.

然后,就结束了。程序竟然已经实现了我们想要的功能。上述代码能够处理我们之前分析的每一种情况。

当timer 超时时,Entry被挤出队列,如果在这之前没有新消息到达,则它的引用计数减一,变成0,Entry被析构,连接断开。如果在这之前有新消息来过,则Entry的另一次强引用被加到队列的最后一个桶,即使这次被挤出,但引用计数不是0,不会被析构,连接还活着。

参考文献

第2节的内容和图片均参考:https://blog.csdn.net/Solstice/article/details/6395098

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值