当前位置: 首页 > news >正文

C#搭建WebSocket服务实现通讯

在学习使用websocket之前我们先了解一下websocket:
WebSocket是一种在单个TCP连接上进行全双工通信的通信协议。与HTTP协议不同,它允许服务器主动向客户端发送数据,而不需要客户端明确地请求。这使得WebSocket非常适合需要实时或持续通信的应用程序,例如在线聊天、实时游戏、股票市场更新等。

websocket介绍

以下是WebSocket的一些关键特点
全双工通信:WebSocket允许客户端和服务器在同一时间内彼此发送数据,而不需要等待对方的响应。这种实时性使其成为许多实时应用程序的首选协议。
持久连接:与HTTP请求-响应模型不同,WebSocket连接在客户端和服务器之间保持打开状态,直到一方选择关闭连接。这消除了频繁地建立和终止连接的开销。
较低的开销:WebSocket在已建立连接的基础上传输数据,减少了与每个请求都要建立新连接的HTTP协议的开销。
轻量级标头:WebSocket协议的标头数据相对较小,这有助于减少数据传输时的开销。
跨域支持:WebSocket协议支持跨域通信,这意味着您可以在不同域之间建立WebSocket连接。
安全性:与HTTP一样,WebSocket可以通过使用TLS/SSL来加密通信,确保数据的安全性。
要建立WebSocket连接,客户端和服务器之间的初始握手是通过HTTP完成的。一旦握手成功,连接升级为WebSocket连接,允许双方直接交换数据帧。数据帧可以是文本数据或二进制数据,具体取决于应用程序的需求。

下面是一个示意性的WebSocket握手过程
客户端发起WebSocket握手请求,包含特定的HTTP头信息。
服务器响应握手请求,同样包含特定的HTTP头信息。
握手成功后,连接从HTTP协议升级到WebSocket协议。
客户端和服务器可以通过发送数据帧进行实时通信,直到其中一方关闭连接。

Ajax长轮询介绍

在websocket不被广泛使用之前Ajax的长轮询比较流行,本质上两者都是用于实现实时通信的技术,但是它们之间有以下区别:
连接方式:Ajax长轮询使用的是HTTP协议,而Websocket使用的是WebSocket协议。
性能:Ajax长轮询中客户端需要不断向服务器发送HTTP请求,服务器在收到请求后才能返回数据给客户端。这种方式会导致不必要的网络延迟和服务器压力,性能相对较差。Websocket采用双向通信方式,只需要建立一次连接,即可实现实时通信,性能较好。
兼容性:Ajax长轮询在大多数浏览器中都可以使用,但是在一些较老的浏览器中可能会有兼容性问题。Websocket需要浏览器支持HTML5才能使用。
实时性:Ajax长轮询的实时性较差,因为客户端需要不断向服务器发送请求,而服务器在收到请求后才能返回数据。Websocket的实时性较好,因为它采用双向通信方式。

完整代码https://download.csdn.net/download/qq_39569480/88264479

websocket用例

后端代码

1.注册服务

首先要在Startup文件中的ConfigureServices方法中注册HttpContextAccessor

// This method gets called by the runtime. Use this method to add services to the container.public void ConfigureServices(IServiceCollection services){services.AddControllers();services.AddSwaggerGen(c =>{c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication3", Version = "v1" });});services.AddHttpContextAccessor();//注册http}

其次在Startup文件中的Configure方法中添加wesocket

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();app.UseSwagger();app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication2 v1"));}app.UseWebSockets();//添加websocketapp.UseHttpsRedirection();app.UseRouting();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});}

2.创建服务

首先定义一个Controller或者Service
注入IHttpContextAccessor以便获取socket请求
声明全局变量用于存储socket链接,这里没有将socket存储到类似redis或数据库中,因为socket链接不能被反序列化,如果有更好方法的小伙版可以留言。这里的socket链接池只要服务不停 链接就会一直存在。

[ApiController]public class WebSocketDemoController : ControllerBase{private readonly IHttpContextAccessor _httpContextAccessor;private static Dictionary<string, WebSocket> _dic = new Dictionary<string, WebSocket>();public WebSocketDemoController(IHttpContextAccessor httpContextAccessor){this._httpContextAccessor = httpContextAccessor;}}

编写socket接口
[HttpGet(“/WsService”)]这里定义了socket的名字
http协议:访问时直接通过ws://Ip:Port/WsService去访问
https协议:访问时直接通过wss://Ip:Port/WsService去访问

[HttpGet("/WsService")]public async Task Get(){//Logger.LogInformation(CurrentUser.Id + CurrentUser.Name);if (_httpContextAccessor.HttpContext.WebSockets.IsWebSocketRequest){//string token= _httpContextAccessor.HttpContext.Request.Headers["Sec-WebSocket-Protocol"];Console.WriteLine("有链接进入");//接受websocket客户端连接var webSocket = await _httpContextAccessor.HttpContext.WebSockets.AcceptWebSocketAsync();//await ReadWriteWebSocektAsync("Add",CurrentUser.Id.ToString(),webSocket); //如果用户的连接不存在加进缓存await Echo(webSocket);}else{//不是websocket客户端请求 ]_httpContextAccessor.HttpContext.Response.StatusCode = 400;}}

消息处理方法

		/// <summary>/// 对客户端的处理,接受消息,发送消息,关闭连接/// </summary>/// <param name="webSocket"></param>/// <param name="dic"></param>/// <param name="userId"></param>/// <returns></returns>private async Task Echo(WebSocket webSocket){try{var buffer = new byte[1024 * 4];//webSocket.SubProtocol = token;var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);while (!result.CloseStatus.HasValue){var message = Encoding.UTF8.GetString(buffer, 0, buffer.Length);WebSocektDto socketRequest = JsonConvert.DeserializeObject<WebSocektDto>(message);buffer = new byte[1024 * 4];//Logger.LogInformation($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}=接收到的信息=" + message);//我们可以通过自定义方法去验证socket请求是否通过了验证才可以掉我们的服务//var (sysUserId, sysUserName) = GetSystemUserId(socketRequest.auth);//if (string.IsNullOrWhiteSpace(sysUserId))//{//如果进入此判断那么下面的代码全部不走//		await UncertifiedProcessing(webSocket, $"{socketRequest.type}_response", "请认证");//}//else{if (socketRequest.type == "create_connect"){//创建链接 //var (sysUserId, sysUserName) = GetSystemUserId(socketRequest.auth);/***************************************//*******重要****看一下下面两行注释******//***************************************///这里只是举了一个简单的示例,如果实际应用建议发起者传送token信息 比如上一行解析发起者传过来的token信息  把发起者的id和socket连接保存起来//这里特别说明一下 为什么token信息要通过参数的形式传送而不是直接写到websocket请求的head中,是因为websocket不支持head传送信息StoreWebSocekt("Add", socketRequest.sponsorUserId, webSocket);}else if (socketRequest.type == "dialing"){//发起者打电话//Logger.LogInformation($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}=发起通话");string targetId = socketRequest.receiverUserId;//获取接收者idstring sponsorUserId = socketRequest.sponsorUserId;//发起者string sponsorUserName = socketRequest.sponsorUserName;//发起者的姓名 //var (sysUserId, sysUserName) = GetSystemUserId(socketRequest.auth);//切记! 不要在当前接收消息的内容体中抛出异常否则会直接断开socket链接,下面注释的代码端是不可取的//if (string.IsNullOrWhiteSpace(sysUserId)) throw new Exception("请认证");//这一步是确保接收信息方的socket链接是否存在,如果不存在信息也找不到接收方//首先在系统登录或者系统初始化时所有的用户都需要建立起链接  并且链接保存起来,以便此刻接收响应信息if (_dic.ContainsKey(targetId)){//向客户端发送消息if (_dic.TryGetValue(targetId, out WebSocket ReceiverSocket)){//在我们自定义的链接池中如果找到了接收者的链接   那么我们给接收者发送信息string sponsor = socketRequest.sponsorUserId;//获取连接池中的接收者 连接    然后给接收者发送 通话信息WebSocektDto PushMessageDto = new WebSocektDto(){type = "called",//类型可以自己定义  为了方便知道自己的每个链接的作用ok = true,msg = "",data =new ResponseDetailsData { sponsorUserId = sponsorUserId, sponsorUserName = sponsorUserName, field1 = "", field2 = "", field3 = "" }//这里定义了一个实体是用于给接收者传递消息的,里边包含发起者的信息和发送的信息};byte[] serverMsg = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(PushMessageDto));await ReceiverSocket.SendAsync(new ArraySegment<byte>(serverMsg, 0, serverMsg.Length), WebSocketMessageType.Text, result.EndOfMessage, CancellationToken.None);}else{//如果在我们自定义的链接池中没有找到接收者的链接 那么我们需要给发起者响应告诉他为什么WebSocektDto rp = new WebSocektDto() { type = "dialing_response", ok = false, msg = "对方意外断开连接", data = new ResponseDetailsData { sponsorUserId = null } };byte[] rpJson = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rp));await webSocket.SendAsync(new ArraySegment<byte>(rpJson, 0, rpJson.Length), WebSocketMessageType.Text, result.EndOfMessage, CancellationToken.None);}}else{//如果在我们自定义的链接池中没有找到接收者   那么我们需要给发起者响应告诉他为什么WebSocektDto rp = new WebSocektDto() { type = "dialing_response", ok = false, msg = "对方未建立链接", data = new ResponseDetailsData { sponsorUserId = null } };byte[] rpJson = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rp));await webSocket.SendAsync(new ArraySegment<byte>(rpJson, 0, rpJson.Length), WebSocketMessageType.Text, result.EndOfMessage, CancellationToken.None);}}//}//继续接受客户端消息  result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);}//关闭释放与客户端连接await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);//await ReadWriteWebSocektAsync("Edit", CurrentUser.Id.ToString(), webSocket); //如果用户的连接不存在加进缓存string keys = _dic.Where(q => q.Value == webSocket).Select(q => q.Key).FirstOrDefault();//通过当前socket获取链接池中的key  然后删掉连接池中的链接if (!string.IsNullOrWhiteSpace(keys)) StoreWebSocekt("Edit", keys, webSocket);}catch (Exception e){}}

3.完成以上步骤我们进行测试

启动服务
在这里插入图片描述

1.创建链接
此步骤为建立socket的连接 并不是实际的工作,因为我们需要确保与服务器的socket通信所以先建立连接。
在这里插入图片描述
此步骤对应前端的代码
ws = new WebSocket(“ws://192.168.0.107:8088”);
在创建ws服务后会自动创建监听
ws.onopen = function() {
};

2.让后台保存用户的socket链接
在系统登录时保存起用户的链接,方便以后互相发送消息
这里我模拟两个用户登录后台保存其socket链接
在这里插入图片描述
在这里插入图片描述
3.1122用户给2211用户发送信息
1122用户发送信息
在这里插入图片描述
我们回到2211的窗口查看,接收到了1122用户发送的信息
在这里插入图片描述
当然我们2211也可以给1122回复信息
在这里插入图片描述

再回到1122的窗口
在这里插入图片描述

相关文章:

C#搭建WebSocket服务实现通讯

在学习使用websocket之前我们先了解一下websocket&#xff1a; WebSocket是一种在单个TCP连接上进行全双工通信的通信协议。与HTTP协议不同&#xff0c;它允许服务器主动向客户端发送数据&#xff0c;而不需要客户端明确地请求。这使得WebSocket非常适合需要实时或持续通信的应…...

eclipse/STS(Spring Tool Suite)安装CDT环境(C/C++)

在线安装 help -> eclipse marketplace 可以发现&#xff0c;我所使用eclipse给我推荐安装的CDT是10.5版本 离线安装 下载离线安装包 下载地址&#xff1a;https://github.com/eclipse-cdt/cdt/blob/main/Downloads.md 可以看到利息安装包主要有如下四大类&#xff0c;…...

Python爬虫抓取经过JS加密的API数据的实现步骤

随着互联网的快速发展&#xff0c;越来越多的网站和应用程序提供了API接口&#xff0c;方便开发者获取数据。然而&#xff0c;为了保护数据的安全性和防止漏洞&#xff0c;一些API接口采用了JS加密技术这种加密技术使得数据在传输过程中更加安全&#xff0c;但也给爬虫开发带来…...

Nacos基础(2)——nacos的服务器和命名空间 springBoot整合nacos 多个nacos配置的情况

目录 引出nacos服务器和命名空间Nacos服务器命名空间 springBoot整合nacosspringcloud Alibaba 版本与springcloud对应关系引包配置maincontroller 报错以及解决【报错】错误&#xff1a;缺少服务名称报错&#xff1a;9848端口未开放 启动测试引入多个nacos配置多个配置的情况没…...

Win7设备和打印机里空白,0个对象,但是可以打印的处理办法

呉師傅 你是不是遇到过Win7系统打开设备和打印机的时候显示是空白的&#xff0c;0个设备的情况&#xff1f;要怎么操作才能解决这一问题呢&#xff0c;下面就分享一下如何处理这个问题的小方法大家可以尝试一下。 问题如下&#xff1a; 解决方法&#xff1a; 1、点击桌面左下…...

Python基础学习第六天:Python 数据类型

内置数据类型 在编程中&#xff0c;数据类型是一个重要的概念。 变量可以存储不同类型的数据&#xff0c;并且不同类型可以执行不同的操作。 在这些类别中&#xff0c;Python 默认拥有以下内置数据类型&#xff1a; 获取数据类型 您可以使用 type() 函数获取任何对象的数据…...

C++信息学奥赛1184:明明的随机数

#include <bits/stdc.h> using namespace std; int main() {int n; // 数组长度cin >> n; // 输入数组长度int arr[n]; // 定义整数数组&#xff0c;用于存储输入的整数// 输入数组元素for (int i 0; i < n; i){cin >> arr[i];}int e 0; // 计数器&…...

NoSQL技术——Redis

简单介绍 Redis是当下最流行的NoSQL数据库。在Redis中&#xff0c;数据的存储格式是以键值对的方式进行存储的。在键值对的存储形式中&#xff0c;值除了是常见的字符串&#xff0c;也可以是类似于Json对象的形式&#xff0c;或者是List&#xff0c;Map等数组格式&#xff0c;…...

【探索SpringCloud】服务发现-Nacos服务端数据结构和模型

前言 上一文中&#xff0c;我们从官方的图示了解到Nacos的服务数据结构。但我关心的是&#xff0c;Nacos2.x不是重构了吗&#xff1f;怎么还是这种数据结构&#xff1f;我推测&#xff0c;必然是为了对Nacos1.x的兼容&#xff0c;实际存储应该不是这样的。于是&#xff0c;沿着…...

基于简单的信息变换实现自然语言模型

题目:基于简单的信息变换实现自然语言模型 摘要:在自然语言处理中,自然语言模型是至关重要的。本论文提出了一种基于简单的信息变换实现自然语言模型的方法。该方法将输入信息进行一系列的信息变换,如分割、属性、等效替换、增加删除等变换,与原始信息进行比较,得知信息是…...

低配版消息队列,redis——Stream

消息队列实现&#xff1a;群发效果 redis原有的消息队列数据不会持久化&#xff0c;所以它加了一个Stream数据结构。 添加数据&#xff1a;XADD s1(组名) *(*为自动生成) key val 取出数据&#xff1a; XREAD COUNT 1(取出个数) BLOCK 1(队列为空时等待时间0为一直等) STREA…...

【OpenCV入门】第五部分——图像运算

文章结构 掩模图像的加法运算图像的位运算按位与运算按位或运算按位取反运算按位异或运算图像位运算的运用 合并图像加权和覆盖 掩模 当计算机处理图像时&#xff0c;有些内容需要处理&#xff0c;有些内容不需要处理。能够覆盖原始图像&#xff0c;仅暴露原始图像“感兴趣区域…...

【Seata】00 - Seata Server 部署(Windows、Docker 基于 Jpom)

文章目录 前言参考目录版本说明Windows 部署 seata-server1&#xff1a;下载压缩包2&#xff1a;文件存储模式3&#xff1a;db 存储模式3.1&#xff1a;建表3.2&#xff1a;修改配置文件3.3&#xff1a;启动脚本4&#xff1a;源码部署 Docker 部署 seata-server &#xff08;基…...

菜鸟教程第一天

1、Java变量类型 1.1 成员变量可以声明在使用前或者使用后。这句话怎么理解&#xff1f; 大家都知道&#xff0c;如果你在代码中使用这个变量&#xff0c;但是在此之前没有声明这个变量&#xff0c;是会报错的。如果是局部变量&#xff0c;在使用前&#xff0c;必须得声明并初…...

数据结构--5.2马踏棋盘算法(骑士周游问题)

题目渊源&#xff1a; 马踏棋盘问题&#xff08;又称骑士周游问题或骑士漫游问题&#xff09;是算法设计的经典问题之一。 题目要求&#xff1a; 国际象棋的棋盘为8*8的方格棋盘&#xff0c;现将“马”放在任意指定的方格中&#xff0c;按照“马”走棋的规则将“马”进行移动。…...

如何使用CSS实现一个响应式图片幻灯片(Responsive Image Slider)效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 响应式图片幻灯片⭐ HTML结构⭐ CSS样式⭐ JavaScript交互⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个…...

Linux学习之lvm删除

umount /mnt/logicvolumntest卸载挂载。 lvremove /dev/vgname/my_lv可以删除逻辑卷&#xff0c;其中vgname是指定逻辑卷所在的卷组名称&#xff0c;my_lv是逻辑卷的名称。 注意&#xff1a;使用lvremove命令会永久删除逻辑卷和其中的数据&#xff0c;因此请在使用之前进行适当…...

bazel介绍以及其发展历史

简介 Bazel Google开源的&#xff0c;是一款与 Make、Maven 和 Gradle 类似的开源构建和测试工具。 它使用人类可读的高级构建语言。Bazel 支持多种语言的项目&#xff0c;可为多个平台构建输出。Bazel支持任意大小的构建目标&#xff0c;并支持跨多个代码库和大量用户的大型代…...

固定资产管理分析怎么写?

对企业内的固定资产进行全面的统计和分析&#xff0c;包括设备、装修、维修等方面的信息&#xff0c;有助于企业进行资产管理和风险控制。  通过该软件&#xff0c;用户可以实现对资产的跟踪和管理&#xff0c;如实时监测设备的使用情况&#xff0c;提高设备利用率和维护效率…...

【项目源码】一套基于springboot+Uniapp框架开发的智慧医院3D人体导诊系统源码

智慧医院3D人体导诊系统源码 开发语言&#xff1a;java 开发工具&#xff1a;IDEA 前端框架&#xff1a;Uniapp 后端框架&#xff1a;springboot 数 据 库&#xff1a;mysql 移 动 端&#xff1a;微信小程序、H5 “智慧导诊”以人工智能手段为依托&#xff0c;为…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...

android13 app的触摸问题定位分析流程

一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...