基于WebSocket实现的后台服务
基于WebSocket实现的后台服务,用于接收客户端的心跳消息,并根据心跳消息来维护客户端连接。
具体实现中,服务启动后会创建一个HttpListener对象,用于监听客户端的WebSocket连接请求。当客户端连接成功后,服务会为每个连接创建一个Task实例,用于接收客户端发送的心跳消息,并根据心跳消息更新心跳时间戳。服务还会定期向客户端发送心跳消息,以保持连接的活跃状态。
如果服务在一定时间内没有收到客户端发送的心跳消息,就会认为客户端已经掉线,服务会关闭连接并从连接列表中移除该客户端。
此服务适用于需要实现长连接的场景,例如实时消息推送、在线游戏等。需要注意的是,此服务只能用于WebSocket通信,客户端必须实现WebSocket协议。
using Microsoft.Extensions.Hosting;
using MSEBP.Kernel.Common.Logging;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace Authorization.WebApi
{/// <summary>/// 此代码只能用于 websocket通信,客户端必须websocket实现,暂时无用。/// </summary>public class WebSocketBackgroundService : IHostedService, IDisposable{private const int _heartBeatInterval = 30000; // 心跳间隔(毫秒)private const int _heartBeatTimeout = 60000; // 心跳超时时间(毫秒)private const int _clientIdLength = 10;private readonly CancellationTokenSource _cts = new CancellationTokenSource();private readonly ConcurrentDictionary<string, WebSocket> _clients = new ConcurrentDictionary<string, WebSocket>();private readonly ILogger _logger;/// <summary>/// /// </summary>/// <param name="logger"></param>public WebSocketBackgroundService(ILogger logger){_logger = logger;}/// <summary>/// /// </summary>/// <param name="cancellationToken"></param>/// <returns></returns>public async Task StartAsync(CancellationToken cancellationToken){IPAddress localIp = Dns.GetHostEntry(Dns.GetHostName()).AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork);if (localIp == null){throw new Exception("Cannot find local IP address.");}IPEndPoint localEndPoint = new IPEndPoint(localIp, 8181);HttpListener listener = new HttpListener();//listener.Prefixes.Add($"http://{localEndPoint}/");listener.Start();_ = Task.Run(async () =>{try{while (!_cts.IsCancellationRequested){HttpListenerContext context = await listener.GetContextAsync();if (context.Request.IsWebSocketRequest){WebSocket webSocket = await AcceptWebSocketAsync(context);_ = Task.Run(async () =>{await ReceiveHeartbeatAsync(webSocket);}, _cts.Token);}else{context.Response.StatusCode = 400;context.Response.Close();}}}catch (Exception ex){_logger.Error(ex, "WebSocket server error.");}}, _cts.Token);}private async Task<WebSocket> AcceptWebSocketAsync(HttpListenerContext context){HttpListenerWebSocketContext wsContext = await context.AcceptWebSocketAsync(null);WebSocket webSocket = wsContext.WebSocket;return webSocket;}private async Task ReceiveHeartbeatAsync(WebSocket webSocket){byte[] buffer = new byte[1024];CancellationToken token = _cts.Token;DateTime lastHeartbeatTime = DateTime.UtcNow;try{while (webSocket.State == WebSocketState.Open && !token.IsCancellationRequested){WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);if (result.CloseStatus.HasValue){await CloseWebSocketAsync(webSocket, result.CloseStatus.Value, result.CloseStatusDescription);break;}else if (result.MessageType == WebSocketMessageType.Text){string message = Encoding.UTF8.GetString(buffer, 0, result.Count).Trim();if (message.StartsWith("heartbeat")){lastHeartbeatTime = DateTime.UtcNow;string clientId = message.Substring(0, Math.Min(message.Length, _clientIdLength));_clients.TryAdd(clientId, webSocket);}else if (string.IsNullOrEmpty(message)){await CloseWebSocketAsync(webSocket, WebSocketCloseStatus.NormalClosure, "Closed by client");break;}else{// 处理业务逻辑}}// 检测心跳超时if ((DateTime.UtcNow - lastHeartbeatTime).TotalMilliseconds > _heartBeatTimeout) { await CloseWebSocketAsync(webSocket, WebSocketCloseStatus.NormalClosure, "Heartbeat timeout");break;}}}catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely){// WebSocket 连接被意外关闭,忽略异常}catch (Exception ex){_logger.Error(ex, "WebSocket error.");}finally{// 移除客户端连接foreach (var item in _clients){if (item.Value == webSocket){_clients.TryRemove(item.Key, out _);break;}}await CloseWebSocketAsync(webSocket, WebSocketCloseStatus.NormalClosure, "Closed by server");}}private async Task CloseWebSocketAsync(WebSocket webSocket, WebSocketCloseStatus closeStatus, string closeStatusDescription){try{await webSocket.CloseAsync(closeStatus, closeStatusDescription, CancellationToken.None);}catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely){// WebSocket 连接已经关闭,忽略异常}catch (Exception ex){_logger.Error(ex, "Failed to close WebSocket.");}}public async Task StopAsync(CancellationToken cancellationToken){_cts.Cancel();await Task.CompletedTask;}public void Dispose(){_cts.Dispose();}}
}
相关文章:
基于WebSocket实现的后台服务
基于WebSocket实现的后台服务,用于接收客户端的心跳消息,并根据心跳消息来维护客户端连接。 具体实现中,服务启动后会创建一个HttpListener对象,用于监听客户端的WebSocket连接请求。当客户端连接成功后,服务会为每个…...

Go语言中的结构体详解
关于 Golang 结构体 Golang 中没有“类”的概念,Golang 中的结构体和其他语言中的类有点相似。和其他面向对 象语言中的类相比,Golang 中的结构体具有更高的扩展性和灵活性。 Golang 中的基础数据类型可以表示一些事物的基本属性,但是当我们…...

pytest自动化测试指定执行测试用例
1、在控制台执行 打开cmd,进入项目目录 指定执行某个模块 pytest testcases\Logistics\Platform\CarSource\test_CarSourceList.py 指定执行某个目录及其子目录的所有测试文件 pytest testcases\Logistics\Platform\CarSource 指定执行某个模块的某个类的某个测试用例 pyte…...

英伟达 H100 vs. 苹果M2,大模型训练,哪款性价比更高?
M1芯片 | Uitra | AMD | A100 M2芯片 | ARM | A800 | H100 关键词:M2芯片;Ultra;M1芯片;UltraFusion;ULTRAMAN;RTX4090、A800;A100;H100;LLAMA、LM、AIGC、CHATGLM、LLVM、LLM、LLM…...
var、let和const的区别
先简单了解一下 var声明的变量会挂载在window上,而let和const声明的变量不会: var a 100; console.log(a,window.a); // 100 100let b 10; console.log(b,window.b); // 10 undefinedconst c 1; console.log(c,window.c); // 1 undefined v…...

(css)AI智能问答页面布局
(css)AI智能问答页面布局 效果: html <!-- AI框 --><div class"chat-top"><div class"chat-main" ref"chatList"><div v-if"!chatList.length" class"no-message"><span>欢迎使…...
【Pytorch学习】pytorch中的isinstance() 函数
描述 isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()。 isinstance() 与 type() 区别: type() 不会认为子类是一种父类类型,不考虑继承关系。 isinstance() 会认为子类是一种父类类型,考虑继承关系。 如果要判…...

(树) 剑指 Offer 07. 重建二叉树 ——【Leetcode每日一题】
❓剑指 Offer 07. 重建二叉树 难度:中等 输入某二叉树的 前序遍历 和 中序遍历 的结果,请构建该二叉树并返回其根节点。 假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 示例 1: Input: preorder [3,9,20,15,7], inorder [9,3,15,20,7] …...

Gitlab 合并分支与请求合并
合并分支 方式一:图形界面 使用 GitGUI,右键菜单“GitExt Browse” - 菜单“命令” - 合并分支 方式二:命令行 在项目根目录下打开控制台,注意是本地 dev 与远程 master 的合并 // 1.查看本地分支,确认当前分支是否…...

【Matter】基于Ubuntu 22.04 编译chip-tool工具
前言 编译过程有点曲折,做下记录,过程中,有参考别人写的博客,也看github 官方介绍,终于跑通了~ 环境说明: 首先需要稳定的梯子,可以访问“外网”ubuntu 环境,最终成功实验在Ubunt…...
将 MongoDB 的 List<Document> 转换为对象列表
当我们使用 MongoDB 存储数据时,经常会涉及到将 MongoDB 的文档对象转换为对象列表的需求。在 Java 中,我们可以使用 MongoDB 的 Java 驱动程序和自定义类来实现这一转换过程。 本篇博客将介绍如何将 MongoDB 中的 List<Document> 转换为对象列表。…...

【Linux下6818开发板(ARM)】SecureCRT串口和交叉编译工具(巨细版!)
(꒪ꇴ꒪ ),hello我是祐言博客主页:C语言基础,Linux基础,软件配置领域博主🌍快上🚘,一起学习!送给读者的一句鸡汤🤔:集中起来的意志可以击穿顽石!作者水平很有限,如果发现错误&#x…...
应届生如何快速找Java开发工程师,先学会这17个基础问题
一、Java 基础 JDK 和 JRE 有什么区别? JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。 JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的…...

数学建模学习(5):数学建模各类题型及解题方案
一、数学建模常见的题型 总体来说,数学建模赛题类型主要分为:评价类、预测类和优化类三种,其中优化类是最常见的赛题类 型,几乎每年的地区赛或国赛美赛等均有出题,必须要掌握并且熟悉。 二、评价类赛题 综合评价是数学…...

【学习笔记】视频检测方法调研
目录 1 引言2 方法2.1 视频目标跟踪2.1.1 生成式模型方法2.1.2 判别式模型方法2.1.2.1 基于相关滤波跟踪2.1.2.2 基于深度学习跟踪 2.2 视频异常检测2.2.1 基于重构方法2.2.2 基于预测方法2.2.3 基于分类方法2.2.4 基于回归方法 2.3 深度伪造人脸视频检测2.3.1 基于RNN时空融合…...

idea terminal npm指令无效
文章目录 一、修改setting二、修改启动方式 一、修改setting 菜单栏:File->Settings 二、修改启动方式 快捷方式->右键属性->兼容性->勾选管理员身份运行...

低代码开发平台源码
什么是低代码开发平台? 低代码来源于英文“Low Code,它意指一种快速开发的方式,使用最少的代码、以最快的速度来交付应用程序。通俗的来说,就是所需代码数量低,开发人员门槛低,操作难度低。一般采用简单的图…...

【UE5 多人联机教程】04-加入游戏
效果 步骤 1. 新建一个控件蓝图,父类为“USC_Button_Standard” 控件蓝图命名为“UMG_Item_Room”,用于表示每一个搜索到的房间的界面 打开“UMG_Item_Room”,在图表中新建一个变量,命名为“Session” 变量类型为“蓝图会话结果…...
自然语言处理从入门到应用——LangChain:模型(Models)-[大型语言模型(LLMs):缓存LLM的调用结果]
分类目录:《自然语言处理从入门到应用》总目录 from langchain.llms import OpenAI在内存中缓存 import langchain from langchain.cache import InMemoryCachelangchain.llm_cache InMemoryCache()# To make the caching really obvious, lets use a slower mode…...
Python 算法基础篇之图的遍历算法:深度优先搜索和广度优先搜索
Python 算法基础篇之图的遍历算法:深度优先搜索和广度优先搜索 引言 1. 图的遍历概述2. 深度优先搜索( DFS )2.1 DFS 的实现2.2 DFS 的应用场景 3. 广度优先搜索( BFS )3.1 BFS 的实现3.2 BFS 的应用场景 4. 示例与实例…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...