webserver--基于小根堆实现定时器,关闭超时的非活跃连接

        计算机在工作时,我们打开多个网页,但是不使用的时候,系统自动会进入休眠模式,这样会更加省电,节省资源。同样的, 服务器在工作时,建立好连接后,即使你不用,他也会一直为你服务吗?当然不是,不然可太消耗资源了。


        对于非活跃的用户,达到了设定的时间,那我们就关闭这个连接,等用户再次需要时再重新建立连接。

怎么实现?

通常有以下几个必要步骤:

  1. 连接管理:服务器会维护一个连接池,其中包含当前所有已建立的连接。
  2. 设置超时时间:每当服务器接受一个新的连接或者收到一个请求时,会为该连接设置一个超时时间。这个超时时间通常是从当前时间开始计算的,用于确定连接在多长时间内没有活动将会被认为是非活跃连接。
  3. 定时器管理:服务器会维护一个小根堆(也称为最小堆),其中存储了所有连接的超时时间。这个小根堆会根据连接的超时时间进行组织,保证堆顶元素是最小的超时时间。
  4. 定时器触发:服务器会周期性地检查小根堆的堆顶元素,如果发现堆顶元素所代表的连接已经超时(即连接变为非活跃状态),则会关闭该连接并从连接池中移除。
  5. 调整定时器:如果有新的连接建立或者有活动的连接被检测到,服务器会更新小根堆中相应连接的超时时间,以确保定时器仍然能够准确地反映连接的活跃状态。

采用小根堆实现定时器有以下好处:

  • 高效性:小根堆的插入、删除和查找最小值的时间复杂度都是 O(log n),使得定时器管理操作能够在较短的时间内完成。
  • 实时性:定时器的触发是基于连接的超时时间,因此能够及时发现并关闭非活跃连接,释放服务器资源,提高服务器的响应速度和并发能力。
  • 灵活性:可以根据需要动态调整超时时间,从而适应不同场景下连接的活跃性变化,提高了系统的适应性和灵活性。

采用小根堆实现定时器能够有效地管理和关闭非活跃连接,提高服务器的性能和稳定性。

代码部分

  • siftup_ 函数用于在向堆中插入新节点后,调整堆,使其仍然保持堆的性质(通常用于最小堆)。它将新插入的节点与其父节点进行比较,如果新节点的值小于父节点的值,则交换它们的位置,直到找到合适的位置或者达到堆顶。
void HeapTimer::siftup_(size_t i) {assert(i >= 0 && i < heap_.size()); // 确保索引 i 的合法性size_t j = (i - 1) / 2; // 计算父节点索引while(j >= 0) {if(heap_[j] < heap_[i]) { break; } // 如果父节点的超时时间小于当前节点的超时时间,则退出循环SwapNode_(i, j); // 交换当前节点与父节点的位置i = j; // 更新当前节点索引为父节点索引j = (i - 1) / 2; // 更新父节点索引为新的父节点索引}
}
  • SwapNode_ 函数用于交换堆中两个节点的位置,并更新节点索引的映射。
void HeapTimer::SwapNode_(size_t i, size_t j) {assert(i >= 0 && i < heap_.size());assert(j >= 0 && j < heap_.size());std::swap(heap_[i], heap_[j]); // 交换堆中索引为 i 和 j 的两个节点ref_[heap_[i].id] = i; // 更新交换后节点的索引映射ref_[heap_[j].id] = j;
} 
  • siftdown_ 函数用于在删除堆顶元素或者更新堆顶元素后,调整堆,使其仍然保持堆的性质。它将堆顶元素与其子节点进行比较,如果堆顶元素的值大于子节点的值,则将它们交换位置,直到找到合适的位置或者达到堆底。
bool HeapTimer::siftdown_(size_t index, size_t n) {assert(index >= 0 && index < heap_.size()); // 确保索引 index 的合法性assert(n >= 0 && n <= heap_.size()); // 确保堆大小的合法性size_t i = index;size_t j = i * 2 + 1; // 计算左子节点索引while(j < n) {if(j + 1 < n && heap_[j + 1] < heap_[j]) j++; // 如果右子节点存在且右子节点的超时时间小于左子节点,则选择右子节点if(heap_[i] < heap_[j]) break; // 如果当前节点的超时时间小于等于子节点的超时时间,则退出循环SwapNode_(i, j); // 否则,交换当前节点与子节点的位置i = j; // 更新当前节点索引为子节点索引j = i * 2 + 1; // 更新子节点索引为新的左子节点索引}return i > index; // 返回是否发生了节点的移动
}
  • add 函数用于向堆中添加新节点或者更新已有节点的超时时间和回调函数。如果添加的是新节点,则将其插入到堆的末尾并调用 siftup_ 函数;如果更新的是已有节点,则根据新的超时时间和当前堆中的情况,选择调用 siftdown_ 或者 siftup_ 函数来调整堆。
void HeapTimer::add(int id, int timeout, const TimeoutCallBack& cb) {assert(id >= 0); // 确保 id 非负size_t i;if(ref_.count(id) == 0) {/* 新节点:堆尾插入,调整堆 */i = heap_.size(); // 新节点的索引为堆的大小ref_[id] = i; // 更新索引映射heap_.push_back({id, Clock::now() + MS(timeout), cb}); // 将新节点添加到堆的末尾siftup_(i); // 调整堆,保持堆的性质} else {/* 已有结点:调整堆 */i = ref_[id]; // 获取已有节点的索引heap_[i].expires = Clock::now() + MS(timeout); // 更新节点的超时时间heap_[i].cb = cb; // 更新节点的回调函数if(!siftdown_(i, heap_.size())) { // 尝试向下调整堆siftup_(i); // 如果向下调整失败,则向上调整堆}}
}
  • void HeapTimer::doWork(int id): 执行指定id对应的定时任务,即触发回调函数。如果堆为空或者指定id不存在,则直接返回。

void HeapTimer::doWork(int id) {/* 删除指定id结点,并触发回调函数 */if(heap_.empty() || ref_.count(id) == 0) {return; // 如果堆为空或者指定id不存在,则直接返回}size_t i = ref_[id]; // 获取指定id在堆中的索引TimerNode node = heap_[i]; // 获取指定id对应的定时器节点node.cb(); // 执行该节点的回调函数del_(i); // 删除该节点
}
  • void HeapTimer::del_(size_t index): 删除指定位置的定时任务节点。首先检查堆是否为空,并确保索引合法,然后将要删除的节点与队尾节点交换位置,并调整堆使其保持堆的性质,最后删除队尾节点。

void HeapTimer::del_(size_t index) {/* 删除指定位置的结点 */assert(!heap_.empty() && index >= 0 && index < heap_.size()); // 断言堆不为空且索引合法size_t i = index; // 待删除节点的索引size_t n = heap_.size() - 1; // 堆的最后一个节点索引assert(i <= n); // 断言待删除节点索引不超过堆的最后一个节点索引if(i < n) {SwapNode_(i, n); // 将待删除节点与堆的最后一个节点交换位置if(!siftdown_(i, n)) { // 尝试向下调整堆siftup_(i); // 如果向下调整失败,则向上调整堆}}/* 队尾元素删除 */ref_.erase(heap_.back().id); // 从索引表中删除队尾节点对应的idheap_.pop_back(); // 删除队尾节点
}
  • void HeapTimer::adjust(int id, int timeout): 调整指定id对应的定时任务的超时时间。首先确保堆不为空且指定id存在,然后更新对应节点的超时时间,并调整堆以维持堆的性质。

void HeapTimer::adjust(int id, int timeout) {/* 调整指定id的结点的超时时间 */assert(!heap_.empty() && ref_.count(id) > 0); // 断言堆不为空且指定id存在于索引表中heap_[ref_[id]].expires = Clock::now() + MS(timeout); // 更新指定id对应节点的超时时间siftdown_(ref_[id], heap_.size()); // 调整堆以保持堆的性质
}
  • void HeapTimer::tick(): 检查并清除超时的定时任务节点。如果堆为空,则直接返回;否则,循环处理堆中的定时任务节点,直到堆为空或者堆顶节点未超时。在每次循环中,获取堆顶节点,检查其超时时间是否已到,如果到了则执行其回调函数并删除该节点。

void HeapTimer::tick() {/* 清除超时结点 */if(heap_.empty()) {return; // 如果堆为空,则直接返回}while(!heap_.empty()) {TimerNode node = heap_.front(); // 获取堆顶节点if(std::chrono::duration_cast<MS>(node.expires - Clock::now()).count() > 0) {break; // 如果堆顶节点未超时,则退出循环}node.cb(); // 执行堆顶节点的回调函数pop(); // 弹出堆顶节点}
}
  • void HeapTimer::pop(): 弹出堆顶的定时任务节点,即删除堆顶节点并重新调整堆。

void HeapTimer::pop() {assert(!heap_.empty()); // 堆不为空del_(0); // 删除堆顶节点
}
  • void HeapTimer::clear(): 清空堆定时器,即清空索引表和堆中的所有节点。

void HeapTimer::clear() {ref_.clear(); // 清空索引表heap_.clear(); // 清空堆
}
  • int HeapTimer::GetNextTick(): 获取下一个超时时间。首先调用tick()函数处理可能已超时的任务,然后检查堆是否为空,如果不为空则计算堆顶节点的剩余超时时间并返回,如果堆为空则返回-1表示无下一个超时任务。

int HeapTimer::GetNextTick() {tick(); // 先处理可能已超时的任务size_t res = -1;if(!heap_.empty()) {res = std::chrono::duration_cast<MS>(heap_.front().expires - Clock::now()).count(); // 计算堆顶节点的剩余超时时间if(res < 0) { res = 0; } // 如果剩余超时时间小于0,则设置为0}return res; // 返回下一个超时时间
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2906157.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

第22篇:异步复位D触发器

Q&#xff1a;本篇我们在基本D触发器中添加一个复位控制信号来实现带异步复位功能的D触发器。 A&#xff1a;带复位控制信号&#xff08;RST&#xff09;的D触发器&#xff0c;当RST为0时&#xff0c;输出Q为0&#xff1b;当RST为1时&#xff0c;Q取决于D和CLK的输入。 带复位…

MYSQL数字函数实操宝典:场景化SQL语句一网打尽

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 MYSQL数字函数&#xff1a;不可不知的数据处理利器 文章目录 Part 1: 准备 &#x…

python pytz是什么

pytz模块常用于时区的转换&#xff0c;常常配合datetime一起使用。我们知道datetime除了data方法生成的时间是没有时区概念&#xff0c;其他如time、datetime等都是有时区概念&#xff0c;即指定了tzinfo信息。 >>> import datetime >>> datetime.datetime.n…

Nomad Web更新没有最快只有更快

大家好&#xff0c;才是真的好。 很长时间没介绍运行在浏览器中的Notes客户端即Nomad Web更新情况。 不用安装&#xff0c;直接使用&#xff0c;还可以完美地兼容适应各种操作系统&#xff0c;Nomad Web一定是Notes/Domino产品现在和将来重点发展的用户访问模式。 不过&…

comfyui 插件

Stable Diffusion ComfyUI 基础教程&#xff08;一&#xff09; ComfyUI安装与常用插件 - 知乎最近发现很多人在搬运我的文章&#xff0c;&#xff0c;&#xff0c;&#xff0c;那我也发 前言&#xff1a;相信大家玩 Stable Diffusion&#xff08;以下简称SD&#xff09;都是用…

超全面!和弦图(Chord diagram) 的绘制方法汇总~~

今天这篇推文给大家介绍一下和弦图(Chord diagram) 的绘制方法&#xff0c;具体包括的内容如下&#xff1a; 和弦图(Chord diagram)简介 和弦图(Chord diagram)绘制方法(RPython) 更多详细的数据可视化教程&#xff0c;可订阅我们的店铺课程&#xff1a; 和弦图(Chord di…

5.11 Vue配置Element UI框架

Vue配置Element UI框架 目录一、 概要二、 开发前准备1. 搭建Vue框架 三、 安装 Element UI1. 引入 Element UI 依赖2. 在 mian.js 中引入 Element UI 和相关样式&#xff1a;3. 按需引入(非必须, 可忽略)4. 简单构建一个主页面 目录 一、 概要 Element UI 是一个基于 Vue.js …

Java学习记录第十三天

面向对象编程 核心思想就是OOP&#xff08;面向对象编程&#xff09; 面向过程&面向对象 面向过程思想 步骤清晰简单&#xff0c;第一步做什么&#xff0c;第二步做什么... 面对过程适合处理一些较为简单的问题 面向对象思想 物以类聚&#xff0c;分类的思维模式&…

基于Java在线考试系统系统设计与实现(源码+部署文档)

博主介绍&#xff1a; ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅 &#x1f447;&#x1f3fb; 不然下次找不到 Java项目精品实…

WebGIS概述

1.地图组成 底图(Map): 所有信息的载体 图层(Layer):将不同地理信息分类形成的一个集合 要素(Feature):表示不同的地物 几何(Geometry): 信息的数据模型和抽象 2.地图容器Container 即在准备阶段所创建的指定了id的div对象&#xff0c;这个div将作为承载所有图层、点标记、矢量…

常用类(String)

目录 字符串相关的类1.1、String类的概述1.2、理解String的不可变性1.3、String不同实例化方式的对比1.4、String不同拼接操作的对比1.4.1、String使用陷阱 1.5、String的常用方法1.6、String与基本数据类型、包装类、char[]、byte[]的转换1.7、StringBuffer和StringBuilder的介…

EasyBoss ERP上线实时数据大屏,Shopee本土店铺数据实时监测

近日&#xff0c;灵隐寺PPT汇报用上数据大屏疯狂刷屏&#xff0c;有做东南亚本土电商的老板发现这种数据大屏的模式可以很好地展现店铺运营状况。 所以就有老板来问&#xff1a;EasyBoss能不能也上线实时数据大屏的功能&#xff1f;没问题&#xff01;立马安排&#xff01; 要有…

鸿蒙OS开发实例:【应用级别文件浏览器】

介绍 HarmonyOS的沙盒机制完全屏蔽了应用对手机公共存储空间的访问&#xff0c;安全性提高已不言而喻。 本篇文章的主要目的是为了能通过一个简单工具&#xff0c;可视化的让一个新手能相对轻松的学习文件&数据存储。HarmonyOS 应用开发工具DevEco Studio也没有提供读取存…

.NET CORE 分布式事务(二) DTM实现TCC

目录 引言&#xff1a; 1. TCC事务模式 2. TCC组成 3. TCC执行流程 3.1 TCC正常执行流程 3.2 TCC失败回滚 4. Confirm/Cancel操作异常 5. TCC 设计原则 5.1 TCC如何做到更好的一致性 5.2 为什么只适合短事务 6. 嵌套的TCC 7. .NET CORE结合DTM实现TCC分布式事务 …

【Linux多线程】线程的同步与互斥

【Linux多线程】线程的同步与互斥 目录 【Linux多线程】线程的同步与互斥分离线程Linux线程互斥进程线程间的互斥相关背景概念问题产生的原因&#xff1a; 互斥量mutex互斥量的接口互斥量实现原理探究对锁进行封装(C11lockguard锁) 可重入VS线程安全概念常见的线程不安全的情况…

查询优化-提升子查询-UNION类型

瀚高数据库 目录 文档用途 详细信息 文档用途 剖析UNION类型子查询提升的条件和过程 详细信息 注&#xff1a;图片较大&#xff0c;可在浏览器新标签页打开。 SQL: SELECT * FROM score sc, LATERAL(SELECT * FROM student WHERE sno 1 UNION ALL SELECT * FROM student…

halcon例程学习——ball.hdev

dev_update_window (off) dev_close_window () dev_open_window (0, 0, 728, 512, black, WindowID) read_image (Bond, die/die_03) dev_display (Bond) set_display_font (WindowID, 14, mono, true, false) *自带的 提示继续 disp_continue_message (WindowID, black, true)…

Redis桌面客户端

3.4.Redis桌面客户端 安装完成Redis&#xff0c;我们就可以操作Redis&#xff0c;实现数据的CRUD了。这需要用到Redis客户端&#xff0c;包括&#xff1a; 命令行客户端图形化桌面客户端编程客户端 3.4.1.Redis命令行客户端 Redis安装完成后就自带了命令行客户端&#xff1…

MySQL Innodb 引擎中预防 Update 操作上升为表锁

一、MySQL 如何预防 Update 上升为表锁 在 MySQL 中&#xff0c;进行任何数据的 修改 操作都会进行一定的锁操作&#xff0c;而锁的不同直接导致性能的差异。例如 MyISAM 引擎&#xff0c;更新时采用表锁&#xff0c;并发性较差。而 Innodb 引擎支持事务&#xff0c;更新时采用…