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(); } } }