SuperSocket 2.0應用2:基於固定頭協議的WebSocket伺服器
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(); } } }