SuperSocket:GitHub,SuperSocket 2.0 中文文档,官方WebSocket Server Demo。
本文开发环境:Win10 + VS2019 + .NET 5.0 + SuperSocket 2.0.0-beta.10。
Gitee:SuperSocketV2FixedHeaderSample。
续接“SuperSocket 2.0应用1:基于固定头协议的Socket服务器”,本文使用SuperSocket 2.0创建基于固定头协议的WebSocket服务器,通过PackageInfo、PackageMapper、IAsyncCommand、WebSocketSession、MiddlewareBase来说明如何构建一个完整的WebSocket服务器。
1、PackageInfo
同SocketServer,略。
2、PackageMapper
SuperSocke使用PackageMapper将WebSocketServer中的WebSocketPackage转换为自己感兴趣的包类型。通过WebSocketPackage的Map方法,可以将字节数组(package.Data)或字符串(package.Message)转换为目标PackageInfo。
示例代码如下:
public class MyPackageConverter : IPackageMapper<WebSocketPackage, MyPackageInfo> { /// +-------+---+----------------------+ /// |request| l | | /// | type | e | request body | /// | (2) | n | | /// | |(2)| | /// +-------+---+----------------------+ private const int HeaderSize = 4; //Header总长度 // ReSharper disable once UnusedMember.Local private const int HeaderLenOffset = 2; //长度offset public MyPackageInfo Map(WebSocketPackage package) { var reader = new SequenceReader<byte>(package.Data); reader.TryReadBigEndian(out short packageKey); var body = package.Data.Slice(HeaderSize).ToArray(); return new MyPackageInfo { Key = packageKey, Body = body }; } }
3、IAsyncCommand
同SocketServer,略。
4、WebSocketSession
这是连接WebSocketServer的会话类,如同AppSession,能够实现向客户端发送消息等功能。
5、MiddlewareBase
此为WebSocketServer在.Net Core中的中间件,类似SocketServer的SuperSocketService。通过注入SessionContainer(即使用UseInProcSessionContainer方法,在SuperSocket.SessionContainer包中声明)可以获取已连接的客户端Session列表,一般用于消息推送。
示例代码如下:
public class MyMiddleware : MiddlewareBase { private ISessionContainer _sessionContainer; private Task _sendTask; private bool _stopped; public override void Start(IServer server) { _sessionContainer = server.GetSessionContainer(); _sendTask = RunAsync(); //模拟消息推送 } private async Task RunAsync() { while (!_stopped) { var sent = await Push(); if (sent == 0 && !_stopped) { await Task.Delay(1000 * 5); } else { await Task.Delay(1000 * 2); } } } private async ValueTask<int> Push() { if (_sessionContainer == null) { return 0; } // about 300 characters var line = string.Join("-", Enumerable.Range(0, 10).Select(x => Guid.NewGuid().ToString())); var count = 0; foreach (var s in _sessionContainer.GetSessions<MyWebSocketSession>()) { await s.SendAsync(line); count++; if (_stopped) break; } return count; } public override void Shutdown(IServer server) { _stopped = true; _sendTask.Wait(); foreach (var s in _sessionContainer.GetSessions<MyWebSocketSession>()) { s.PrintStats(); } } }
6、Main示例
using System; using System.Collections.Generic; using System.Threading.Tasks; using FixedHeaderSample.WebSocketServer.Commands; using FixedHeaderSample.WebSocketServer.Server; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using SuperSocket; using SuperSocket.Command; using SuperSocket.WebSocket.Server; namespace FixedHeaderSample.WebSocketServer { class Program { static async Task Main() { var host = WebSocketHostBuilder.Create() //注册WebSocket消息处理器(使用UseCommand注册命令之后该处理器不起作用) .UseWebSocketMessageHandler(async (session, package) => { Console.WriteLine($@"{DateTime.Now:yyyy-MM-dd HH:mm:ss fff} Receive message: {package.Message}."); //Send message back var message = $@"{DateTime.Now:yyyy-MM-dd HH:mm:ss fff} Hello from WebSocket Server: {package.Message}."; await session.SendAsync(message); }) .UseCommand<MyPackageInfo, MyPackageConverter>(commandOptions => { //注册命令 commandOptions.AddCommand<MyCommand>(); }) .UseSession<MyWebSocketSession>() .UseInProcSessionContainer() .UseMiddleware<MyMiddleware>() .ConfigureAppConfiguration((hostCtx, configApp) => { configApp.AddInMemoryCollection(new Dictionary<string, string> { {"serverOptions:name", "TestServer"}, {"serverOptions:listeners:0:ip", "Any"}, {"serverOptions:listeners:0:port", "4041"} }); }) .ConfigureLogging((hostCtx, loggingBuilder) => { //添加控制台输出 loggingBuilder.AddConsole(); }) .Build(); await host.RunAsync(); } } }