目录

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

csharp 复制代码
// 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

csharp 复制代码
// 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();//添加websocket
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

2.创建服务

首先定义一个Controller或者Service

注入IHttpContextAccessor以便获取socket请求

声明全局变量用于存储socket链接,这里没有将socket存储到类似redis或数据库中,因为socket链接不能被反序列化,如果有更好方法的小伙版可以留言。这里的socket链接池只要服务不停 链接就会一直存在。

csharp 复制代码
[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**去访问 ```csharp [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; } } ``` **消息处理方法** ```csharp ///

/// 对客户端的处理,接受消息,发送消息,关闭连接 /// /// /// /// /// private async Task Echo(WebSocket webSocket) { try { var buffer = new byte[1024 * 4]; //webSocket.SubProtocol = token; var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); while (!result.CloseStatus.HasValue) { var message = Encoding.UTF8.GetString(buffer, 0, buffer.Length); WebSocektDto socketRequest = JsonConvert.DeserializeObject(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;//获取接收者id string 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(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(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(rpJson, 0, rpJson.Length), WebSocketMessageType.Text, result.EndOfMessage, CancellationToken.None); } } //} //继续接受客户端消息 result = await webSocket.ReceiveAsync(new ArraySegment(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.完成以上步骤我们进行测试 启动服务 ![在这里插入图片描述](https://file.jishuzhan.net/article/1697422006619738114/04b849c557294660938e6c7ef619424a.png) **1.创建链接** 此步骤为建立socket的连接 并不是实际的工作,因为我们需要确保与服务器的socket通信所以先建立连接。 ![在这里插入图片描述](https://file.jishuzhan.net/article/1697422006619738114/fc712a2dda714aa0a90538c304d4dae1.png) 此步骤对应前端的代码 ws = new WebSocket("ws://192.168.0.107:8088"); 在创建ws服务后会自动创建监听 ws.onopen = function() { }; **2.让后台保存用户的socket链接** 在系统登录时保存起用户的链接,方便以后互相发送消息 这里我模拟两个用户登录后台保存其socket链接 ![在这里插入图片描述](https://file.jishuzhan.net/article/1697422006619738114/06e5d0cb315d450db93b23bf11b3444a.png) ![在这里插入图片描述](https://file.jishuzhan.net/article/1697422006619738114/73d48d6381d6478594d0b81dc51a6490.png) **3.1122用户给2211用户发送信息** 1122用户发送信息 ![在这里插入图片描述](https://file.jishuzhan.net/article/1697422006619738114/1f6bbdac4d004152a3fede9495e9f588.png) 我们回到2211的窗口查看,接收到了1122用户发送的信息 ![在这里插入图片描述](https://file.jishuzhan.net/article/1697422006619738114/2e7073780a7346e39416d50045c5183f.png) 当然我们2211也可以给1122回复信息 ![在这里插入图片描述](https://file.jishuzhan.net/article/1697422006619738114/c763b69a75624fa5aaf8653d00776184.png) 再回到1122的窗口 ![在这里插入图片描述](https://file.jishuzhan.net/article/1697422006619738114/77a0ac58145147f0a3cdfdbe418f9222.png)

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
风雅颂FYS32 分钟前
C# 经纬度坐标的精度及WGS84(谷歌)、GCJ02(高德)、BD09(百度)坐标相互转换(含高精度转换)
百度·c#
姜行运1 小时前
每日算法(双指针算法)(Day 1)
c++·算法·c#
00后程序员张4 小时前
Charles抓包-安装和IOS抓包指导
websocket·网络协议·tcp/ip·http·网络安全·https·udp
vil du4 小时前
c# 反射及优缺点
c#
三天不学习6 小时前
C# + Python混合开发实战:优势互补构建高效应用
开发语言·python·c#
谢道韫6666 小时前
37-串联所有单词的子串
开发语言·算法·c#
、我是男生。7 小时前
MQTT、HTTP短轮询、HTTP长轮询、WebSocket
websocket·网络协议·http
CodeCraft Studio7 小时前
PDF处理控件Aspose.PDF指南:使用 C# 从 PDF 文档中删除页面
前端·pdf·c#
全栈小58 小时前
【C#】Html转Pdf,Spire和iTextSharp结合,.net framework 4.8
pdf·c#·html
唐青枫9 小时前
dotnet 值拷贝、浅拷贝、深拷贝详解
c#·.net