服务器开发- Asp.Net Core中的websocket,并封装一个简单的中间件

本文涉及的产品
性能测试 PTS,5000VUM额度
云原生网关 MSE Higress,422元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 服务器开发- Asp.Net Core中的websocket,并封装一个简单的中间件

先拉开MSDN的文档,大致读一遍 (https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/websockets)

WebSocket 是一个协议,支持通过 TCP 连接建立持久的双向信道。 它可用于聊天、股票报价和游戏等应用程序,以及 Web 应用程序中需要实时功能的任何情景。 


使用方法  

  • 安装 Microsoft.AspNetCore.WebSockets 包。
  • 配置中间件。
  • 接受 WebSocket 请求。
  • 发送和接收消息。

如果是创建的asp.net core项目,默认会有一个all的包,里面默认带了websocket的包。所以,添加的时候,注意看一下

然后就是配置websocket的中间件

app.UseWebSockets();

 如果需要更细致的配置websocket,MSDN文档上也提供了一种配置缓冲区大小和ping的option

var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),  //向客户端发送“ping”帧的频率,以确保代理保持连接处于打开状态
    ReceiveBufferSize = 4 * 1024   //用于接收数据的缓冲区的大小。 只有高级用户才需要对其进行更改,以便根据数据大小调整性能。
};
app.UseWebSockets(webSocketOptions);

接受 WebSocket 请求

在请求生命周期后期(例如在 Configure 方法或 MVC 操作的后期),检查它是否是 WebSocket 请求并接受 WebSocket 请求。

该示例来自 Configure 方法的后期。

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
            await Echo(context, webSocket);
        }
        else
        {
            context.Response.StatusCode = 400;
        }
    }
    else
    {
        await next();
    }
});

WebSocket 请求可以来自任何 URL,但此示例代码只接受 /ws 的请求

(比如要测试websocket的连接,地址必须写上:ws://ip:端口/ws) 最后这个路径的ws是可以自己定义的,可以理解为MVC的路由,或者url地址,websocket第一次连接的时候,可以使用url传递参数

发送和接收消息

AcceptWebSocketAsync 方法将 TCP 连接升级到 WebSocket 连接,并提供 WebSocket 对象。 使用 WebSocket 对象发送和接收消息。

之前显示的接受 WebSocket 请求的代码将 WebSocket 对象传递给 Echo 方法;此处为 Echo 方法。 代码接收消息并立即发回相同的消息。 一直在循环中执行此操作,直到客户端关闭连接

private async Task Echo(HttpContext context, WebSocket webSocket)
{
    var buffer = new byte[1024 * 4];
    WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
    while (!result.CloseStatus.HasValue)
    {
        await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
        result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
    }
    await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}

如果在开始此循环之前接受 WebSocket,中间件管道会结束。 关闭套接字后,管道展开。 也就是说,如果接受 WebSocket ,请求会在管道中停止前进,就像点击 MVC 操作一样。 但是完成此循环并关闭套接字时,请求将在管道中后退。

如果要测试是否连上,那么可以自己写ws的客户端程序,当然也可以使用一些现成的工具辣

封装一个简单的中间件:

什么是中间件?MSDN对此的解释是:

中间件是一种装配到应用程序管道以处理请求和响应的软件。 每个组件:

  • 选择是否将请求传递到管道中的下一个组件。
  • 可在调用管道中的下一个组件前后执行工作。

请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。

使用 RunMapUse 扩展方法来配置请求委托。 可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件或中间件组件。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或在适当情况下使链发生短路。

新建一个WebSocketExtensions.cs的类

public static class WebSocketExtensions
    {
        public static IApplicationBuilder MapWebSocketManager(this IApplicationBuilder app,PathString path,WebSocketHandler handler)
        {
            return app.Map(path, (_app) => _app.UseMiddleware<WebSocketManagerMiddleware>(handler));
        }
        public static IServiceCollection AddWebSocketManager(this IServiceCollection services)
        {
            services.AddTransient<WebSocketConnectionManager>();
            foreach (var type in Assembly.GetEntryAssembly().ExportedTypes)
            {
                if (type.GetTypeInfo().BaseType == typeof(WebSocketHandler))
                {
                    services.AddSingleton(type);
                }
            }
            return services;
        }
    }

AddWebSocketManager这个方法主要是处理依赖注入的问题。通过反射把实现WebSocketHandler的类,统统注入

管理websocket连接

public class WebSocketConnectionManager
    {
        private ConcurrentDictionary<string, WebSocket> _sockets = new ConcurrentDictionary<string, WebSocket>();
        public int GetCount()
        {
            return _sockets.Count;
        }
        public WebSocket GetSocketById(string id)
        {
            return _sockets.FirstOrDefault(p => p.Key == id).Value;
        }
        public ConcurrentDictionary<string, WebSocket> GetAll()
        {
            return _sockets;
        }
        public WebSocket GetWebSocket(string key)
        {
            WebSocket _socket;
            _sockets.TryGetValue(key, out _socket);
            return _socket;
        }
        public string GetId(WebSocket socket)
        {
            return _sockets.FirstOrDefault(p => p.Value == socket).Key;
        }
        public void AddSocket(WebSocket socket,string key)
        {
            if (GetWebSocket(key)!=null)
            {
                _sockets.TryRemove(key, out WebSocket destoryWebsocket);
            }
            _sockets.TryAdd(key, socket);
            //string sId = CreateConnectionId();
            //while (!_sockets.TryAdd(sId, socket))
            //{
            //    sId = CreateConnectionId();
            //}
        }
        public async Task RemoveSocket(string id)
        {
            try
            {
                WebSocket socket;
                _sockets.TryRemove(id, out socket);
                await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
            }
            catch (Exception)
            {
            }
        }
        public async Task CloseSocket(WebSocket socket)
        {
            await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
        }
        private string CreateConnectionId()
        {
            return Guid.NewGuid().ToString();
        }
    }

这里我把客户的连接的管理都封装到一个连接类里面,我的思路是

  • 我使用webapi来验证身份,走http协议的接口
  • 验证成功后,服务器给客户端返回一个token
  • 客户端通过websocket连接服务器的时候,需要带上上次返回的token,这样我就可以在连接字典里面判断出重复的socket(因为我试过在.Net core里面如果多次连接,服务器不会走断开的事件,而是不断的出现多个socket对象)

WebSocketManagerMiddleware类的封装

public class WebSocketManagerMiddleware
    {
        private readonly RequestDelegate _next;
        private WebSocketHandler _webSocketHandler { get; set; }
        public WebSocketManagerMiddleware(RequestDelegate next,
                                          WebSocketHandler webSocketHandler)
        {
            _next = next;
            _webSocketHandler = webSocketHandler;
        }
        public async Task Invoke(HttpContext context)
        {
            if (!context.WebSockets.IsWebSocketRequest)
                return;
            var socket = await context.WebSockets.AcceptWebSocketAsync();
            string Key = context.Request.Query["Key"];
            Console.WriteLine("连接人:"+Key);
            _webSocketHandler.OnConnected(socket,Key);
            await Receive(socket, async (result, buffer) =>
            {
                if (result.MessageType == WebSocketMessageType.Text)
                {
                    await _webSocketHandler.ReceiveAsync(socket, result, buffer);
                    return;
                }
                else if (result.MessageType == WebSocketMessageType.Close)
                {
                    await _webSocketHandler.OnDisconnected(socket);
                    return;
                }
            });
            //TODO - investigate the Kestrel exception thrown when this is the last middleware
            //await _next.Invoke(context);
        }
        private async Task Receive(WebSocket socket, Action<WebSocketReceiveResult, byte[]> handleMessage)
        {
            try
            {
                var buffer = new byte[1024 * 4];
                while (socket.State == WebSocketState.Open)
                {
                    var result = await socket.ReceiveAsync(buffer: new ArraySegment<byte>(buffer),
                                                           cancellationToken: CancellationToken.None);
                    handleMessage(result, buffer);
                }
            }
            catch (Exception ex)
            {
                GsLog.E(ex.StackTrace);
            }
        }
    }

Invoke的时候,传递的key参数需要客户端验证身份后,传递进来:(ws://ip:端口/ws?key=xxx) 这样的格式来连接websocket

在这个类里面,我们主要处理三个事情

  • 如果客户端连接进来,那么我们把这个连接放到连接管理字典里面
  • 如果客户端断开连接,那么我们把这个连接冲连接管理字典里面移除
  • 如果是发送数据过来,那么我们就调用ReceiveAsync方法,并把连接对象和数据传递进去

WebSocketHandler类的封装

这个类主要关联游戏逻辑模块和websocket的一个纽带,我们封装的中间件,通过websockethandler把数据传递给实现这个类的子类

public abstract class WebSocketHandler
    {
        public WebSocketConnectionManager WebSocketConnectionManager { get; set; }
        public WebSocketHandler(WebSocketConnectionManager webSocketConnectionManager)
        {
            WebSocketConnectionManager = webSocketConnectionManager;
        }
        public virtual void OnConnected(WebSocket socket, string key)
        {
            //var ServerSocket = WebSocketConnectionManager.GetWebSocket(key);
            //if (ServerSocket != null)
            //{
            //    WebSocketConnectionManager.AddSocket();
            //    Console.WriteLine("已经存在当前的连接,断开。。");
            //}
            WebSocketConnectionManager.AddSocket(socket, key);
        }
        public virtual async Task OnDisconnected(WebSocket socket)
        {
            Console.WriteLine("Socket 断开了");
            await WebSocketConnectionManager.RemoveSocket(WebSocketConnectionManager.GetId(socket));
        }
        public async Task SendMessageAsync(WebSocket socket, string message)
        {
            if (socket.State != WebSocketState.Open)
                return;
            var bytes = Encoding.UTF8.GetBytes(message);
            await socket.SendAsync(buffer: new ArraySegment<byte>(array: bytes, offset: 0, count: bytes.Length), messageType: WebSocketMessageType.Text, endOfMessage: true, cancellationToken: CancellationToken.None);
        }
        public async Task SendMessageAsync(string socketId, string message)
        {
            try
            {
                await SendMessageAsync(WebSocketConnectionManager.GetSocketById(socketId), message);
            }
            catch (Exception)
            {
            }
        }
        public async Task SendMessageToAllAsync(string message)
        {
            foreach (var pair in WebSocketConnectionManager.GetAll())
            {
                if (pair.Value.State == WebSocketState.Open)
                    await SendMessageAsync(pair.Value, message);
            }
        }
        /// <summary>
        /// 获取一些连接
        /// </summary>
        /// <param name="keys"></param>
        /// <returns></returns>
        public IEnumerable<WebSocket> GetSomeWebsocket(string[] keys)
        {
            foreach (var key in keys)
            {
                yield return WebSocketConnectionManager.GetWebSocket(key);
            }
        }
        /// <summary>
        /// 给一堆人发消息
        /// </summary>
        /// <param name="webSockets"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task SendMessageToSome(WebSocket[] webSockets, string message)
        {
            webSockets.ToList().ForEach(async a => { await SendMessageAsync(a, message); });
        }
        public abstract Task ReceiveAsync(WebSocket socket, WebSocketReceiveResult result, byte[] buffer);
    }

需要把一些必须要重写的方法定义为abstract  给子类重写

使用我们写好的websocket管理中间件

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddWebSocketManager();
        }
var webSocketOptions = new WebSocketOptions()
            {
                KeepAliveInterval = TimeSpan.FromSeconds(20),
                ReceiveBufferSize = 4 * 1024
            };
            app.UseWebSockets(webSocketOptions);
            app.MapWebSocketManager("/zhajinhua", serviceProvider.GetService<ZjhGame>());

ZjhGame这个类,必须实现 WebSocketHandler,这样我们就能在ZjhGame这个类,处理游戏逻辑

好了,大致就是这样的,毕竟我也没有开发游戏的经验,有错误的地方,希望大佬们能指出

 

还有上一篇博客有大佬说加注,但是我没有收到加注的钱啊,你们到底加不加啊?不加我可要反悔了啊

http://www.cnblogs.com/boxrice/p/8570730.html

目录
相关文章
|
4天前
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
22 5
|
2月前
|
存储 开发框架 JSON
ASP.NET Core OData 9 正式发布
【10月更文挑战第8天】Microsoft 在 2024 年 8 月 30 日宣布推出 ASP.NET Core OData 9,此版本与 .NET 8 的 OData 库保持一致,改进了数据编码以符合 OData 规范,并放弃了对旧版 .NET Framework 的支持,仅支持 .NET 8 及更高版本。新版本引入了更快的 JSON 编写器 `System.Text.UTF8JsonWriter`,优化了内存使用和序列化速度。
|
23天前
|
开发框架 .NET C#
在 ASP.NET Core 中创建 gRPC 客户端和服务器
本文介绍了如何使用 gRPC 框架搭建一个简单的“Hello World”示例。首先创建了一个名为 GrpcDemo 的解决方案,其中包含一个 gRPC 服务端项目 GrpcServer 和一个客户端项目 GrpcClient。服务端通过定义 `greeter.proto` 文件中的服务和消息类型,实现了一个简单的问候服务 `GreeterService`。客户端则通过 gRPC 客户端库连接到服务端并调用其 `SayHello` 方法,展示了 gRPC 在 C# 中的基本使用方法。
35 5
在 ASP.NET Core 中创建 gRPC 客户端和服务器
|
13天前
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
22 3
|
2月前
|
监控 网络安全 调度
Quartz.Net整合NetCore3.1,部署到IIS服务器上后台定时Job不被调度的解决方案
解决Quartz.NET在.NET Core 3.1应用中部署到IIS服务器上不被调度的问题,通常需要综合考虑应用配置、IIS设置、日志分析等多个方面。采用上述策略,结合细致的测试和监控,可以有效地提高定时任务的稳定性和可靠性。在实施任何更改后,务必进行充分的测试,以验证问题是否得到解决,并监控生产环境的表现,确保长期稳定性。
90 1
|
2月前
|
网络协议 Unix Linux
一个.NET开源、快速、低延迟的异步套接字服务器和客户端库
一个.NET开源、快速、低延迟的异步套接字服务器和客户端库
|
2月前
mcr.microsoft.com/dotnet/core/aspnet:2.1安装libgdiplus
mcr.microsoft.com/dotnet/core/aspnet:2.1安装libgdiplus
32 1
|
2月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
3天前
|
弹性计算 安全 开发工具
灵码评测-阿里云提供的ECS python3 sdk做安全组管理
批量变更阿里云ECS安全组策略(批量变更)
|
21天前
|
存储 人工智能 弹性计算
阿里云弹性计算(ECS)提供强大的AI工作负载平台,支持灵活的资源配置与高性能计算,适用于AI训练与推理
阿里云弹性计算(ECS)提供强大的AI工作负载平台,支持灵活的资源配置与高性能计算,适用于AI训练与推理。通过合理优化资源分配、利用自动伸缩及高效数据管理,ECS能显著提升AI系统的性能与效率,降低运营成本,助力科研与企业用户在AI领域取得突破。
36 6

热门文章

最新文章