最近在维护老项目,感觉内存一直都有问题,定位到问题是WebSocketServer的问题,了解了 Fleck、SuperSocket、TouchSocke 等开源项目 ,这里记录一下。可能今后都不会用些轮子了,.net5、.net6、.net7、.net8 项目已经集成了WebSocket,只要 app.UseWebSockets() 代码就可以了,详情见 WebSockets support in ASP.NET Core | Microsoft Learn。
0. 控制台运行的代码
代码:https://gitee.com/Karl_Albright/csharp-web-socket-server
internal class Program { static void Main(string[] args) { WebSockSvr server = new WebSockSvr(); server.Start(); server.SendDatas(); Console.ReadLine(); } }
1. Fleck
兼容 .NetFramework V4.0、.NetFramework V4.5、.NetCoreApp V2.0、.NetStandard V2.0
dotnet add package Fleck --version 1.2.0
usingFleck;namespaceFleckDemo{
publicclass WebSockSvr { public List<IWebSocketConnection> ClinetList = new();private WebSocketServer service; public WebSockSvr() { service =newWebSocketServer("ws://0.0.0.0:4040"); } publicvoid Start() { service.Start(socket => { socket.OnOpen = () => { Console.WriteLine("Open!"); ClinetList.Add(socket); }; socket.OnClose = () => { Console.WriteLine("Close!"); ClinetList.Remove(socket); }; socket.OnMessage = message => { Console.WriteLine(message); ClinetList.ToList().ForEach(s => s.Send("Echo:"+ message)); }; }); } publicvoid SendDatas() { for(int i = 0; i < 200; i++) { Task.Run(async () =>{while(true) { try{for(int j = 0; j < ClinetList.Count; j++){var sock =ClinetList[j];if (sock.IsAvailable) { awaitsock.Send($"Dev[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}, 12.34, 34.56, 56.78, \"77705683\"]"); } } } catch (Exception ex) { Console.WriteLine("出现异常:" + ex.Message + "\r\n"+ ex.StackTrace); } finally{awaitTask.Delay(1000); } } }); } } }}
2. SuperSocket1.6
截止到现在 superSocket 2.0版本还没正式发布,有beta.26版本,和1.6相比改动挺大的。
兼容 .NetFramework V4.6.1、.NetFramework V4.6.2、.NetFramework V4.7、.NetFramework V4.7.1、.NetFramework V4.7.2、.NetFramework V4.8、.NetFramework V4.8.1
dotnet add package SuperSocket --version 1.6.6.1dotnet add package SuperSocket.Engine --version1.6.6.1dotnet add package SuperSocket.WebSocket --version1.6.6.1
usingSuperSocket.SocketBase;usingSuperSocket.WebSocket;usingSystem;usingSystem.Collections.Generic;usingSystem.Threading.Tasks;namespaceSuperSocketDemo{publicclass WebSockSvr { public List<WebSocketSession> ClinetList { get;set; } = newList<WebSocketSession>();private WebSocketServer server; public WebSockSvr() { server =new WebSocketServer(); server.NewMessageReceived += Ws_NewMessageReceived;//当有信息传入时 server.NewSessionConnected += Ws_NewSessionConnected;//当有用户连入时 server.SessionClosed += Ws_SessionClosed;//当有用户退出时 server.NewDataReceived += Ws_NewDataReceived;//当有数据传入时}publicvoid Start() { if(server.Setup(4040))//绑定端口server.Start();//启动服务}//public void SendDatas() //{////对当前已连接的所有会话进行广播// foreach (var session in server.GetAllSessions()) //{// session.Send($"Dev[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}, 12.34, 34.56, 56.78, \"77705683\"]"); //Thread.Sleep(1000);//}//}publicvoid SendDatas() { for(int i = 0; i < 200; i++) { Task.Run(async () =>{while(true) { try{for(int j = 0; j < ClinetList.Count; j++) { var sock =ClinetList[j];if (sock.Connected) { sock.TrySend($"Dev[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}, 12.34, 34.56, 56.78, \"77705683\"]"); } } } catch (Exception ex) { Console.WriteLine("出现异常:" + ex.Message + "\r\n"+ ex.StackTrace); } finally{awaitTask.Delay(10); } } }); } } privatevoid Ws_NewSessionConnected(WebSocketSession session) { ClinetList.Add(session); } privatevoid Ws_NewMessageReceived(WebSocketSession session, string value) { } privatevoid Ws_SessionClosed(WebSocketSession session, CloseReason value) { ClinetList.Remove(session); } privatevoid Ws_NewDataReceived(WebSocketSession session, byte[] value) { } }}
3. SuperSocket2.0.0-beta.26
兼容 .NetStandard V2.1、.Net5、.Net6、.Net7、.Net8
dotnet add package SuperSocket.WebSocket.Server --version 2.0.0-beta.26
usingMicrosoft.Extensions.Hosting;usingSuperSocket.Server;usingSuperSocket.Server.Abstractions;usingSuperSocket.Server.Host;usingSuperSocket.WebSocket.Server;namespaceSuperSocket2Demo{
publicclass WebSockSvr { public List<WebSocketSession> ClinetList { get;set; } = new();private IServer server; public WebSockSvr() { server = WebSocketHostBuilder.Create() .ConfigureSuperSocket(opts => { opts.AddListener(new ListenOptions { Ip ="127.0.0.1", Port =4040 }); }) .UseSessionHandler((session) =>{var sess = (WebSocketSession)session; ClinetList.Add(sess); return ValueTask.CompletedTask; }, (session, reason) =>{var sess = (WebSocketSession)session; ClinetList.Remove(sess); return ValueTask.CompletedTask; }) .UseWebSocketMessageHandler(async (session, message) => { }) .BuildAsServer(); } public Task Start() { return server.StartAsync(); } publicvoid SendDatas() { for(int i = 0; i < 200; i++) { Task.Run(async () =>{while(true) { try{for(int j = 0; j < ClinetList.Count; j++) { var sock =ClinetList[j];if (sock.State == SessionState.Connected) { awaitsock.SendAsync($"Dev[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}, 12.34, 34.56, 56.78, \"77705683\"]"); } } } catch (Exception ex) { Console.WriteLine("出现异常:" + ex.Message + "\r\n"+ ex.StackTrace); } finally{awaitTask.Delay(10); } } }); } } }}
4. TouchSocket
目前兼容 .NetFramework V4.5、.NetFramework V4.6.2、.NetFramework V4.7.2、.NetFramework V4.8.1、.NetStandard V2.0、.NetStandard V2.1、.Net6、.Net7、.Net8
dotnet add package TouchSocket --version 2.1.5dotnet add package TouchSocket.Http --version2.1.5dotnet add package TouchSocket.WebApi --version2.1.5
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Threading.Tasks;usingTouchSocket.Core;usingTouchSocket.Http;usingTouchSocket.Http.WebSockets;usingTouchSocket.Rpc;usingTouchSocket.Sockets;usingTouchSocket.WebApi;namespaceTouchSocketDemo.Core{publicclass WebSockSvr { public List<IHttpSession> ClinetList { get;set; } = newList<IHttpSession>();private HttpService service; public WebSockSvr() { service =new HttpService(); } publicasync Task Start() { awaitservice.SetupAsync(newTouchSocketConfig()//加载配置.SetListenIPHosts(4040) .ConfigureContainer(a =>{a.AddConsoleLogger(); }) .ConfigurePlugins(a => { a.UseWebSocket().SetWSUrl(null).UseAutoPong();//a.Add<MyWebSocketPlugin>();a.Add(typeof(IWebSocketHandshakedPlugin),async (IWebSocket client, HttpContextEventArgs e) => { ClinetList.Add(client.Client); await e.InvokeNext(); }); a.Add(typeof(IWebSocketClosingPlugin),async (IWebSocket client, ClosedEventArgs e) => { ClinetList.Remove(client.Client); await e.InvokeNext(); }); a.Add(typeof(IWebSocketReceivedPlugin),async (IWebSocket client, WSDataFrameEventArgs e) =>{switch (e.DataFrame.Opcode) { case WSDataType.Close: {awaitclient.CloseAsync("断开"); } return;caseWSDataType.Ping:awaitclient.PongAsync();//收到ping时,一般需要响应pongbreak;caseWSDataType.Pong:break;default:break; } await e.InvokeNext(); }); a.UseWebSocketReconnection();//a.Add<MyWebSocketPlugin>();}));await service.StartAsync(); } publicvoid SendDatas() { for(int i = 0; i < 200; i++) { Task.Run(async () =>{while(true) { try{var clientList =ClinetList.ToList();for(int j = 0; j < clientList.Count; j++) { var sock =(HttpSessionClient)clientList[j];if (sock.Online) {awaitsock.WebSocket.SendAsync($"Dev[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}, 12.34, 34.56, 56.78, \"77705683\"]"); } } } catch (Exception ex) { Console.WriteLine("出现异常:" + ex.Message + "\r\n"+ ex.StackTrace); } finally{awaitTask.Delay(10); } } }); } }
publicclass MyWebSocketPlugin : PluginBase, IWebSocketHandshakingPlugin, IWebSocketHandshakedPlugin, IWebSocketReceivedPlugin { public MyWebSocketPlugin(ILog logger) { this.m_logger = logger; } publicasync Task OnWebSocketHandshaking(IWebSocket client, HttpContextEventArgs e) { if(client.Clientis IHttpSessionClient socketClient) { //服务端var id = socketClient.Id; } elseif(client.Clientis IHttpClient httpClient) { //客户端}this.m_logger.Info("WebSocket正在连接");await e.InvokeNext(); } publicasync Task OnWebSocketHandshaked(IWebSocket client, HttpContextEventArgs e) { this.m_logger.Info("WebSocket成功连接");await e.InvokeNext(); } privatereadonly ILog m_logger; publicasync Task OnWebSocketReceived(IWebSocket client, WSDataFrameEventArgs e) { switch (e.DataFrame.Opcode) { case WSDataType.Close: {awaitclient.CloseAsync("断开"); } return;caseWSDataType.Ping:awaitclient.PongAsync();//收到ping时,一般需要响应pongbreak;caseWSDataType.Pong:this.m_logger.Info("Pong");break;default: { //其他报文,需要考虑中继包的情况。所以需要手动合并 WSDataType.Cont类型的包。 //或者使用消息合并器//获取消息组合器var messageCombinator =client.GetMessageCombinator();try{//尝试组合if(messageCombinator.TryCombine(e.DataFrame,outvar webSocketMessage)) { //组合成功,必须using释放模式using (webSocketMessage) { //合并后的消息var dataType =webSocketMessage.Opcode;//合并后的完整消息var data =webSocketMessage.PayloadData;if (dataType == WSDataType.Text) { //按文本处理}elseif (dataType == WSDataType.Binary) { //按字节处理}else{//可能是其他自定义协议 } } } } catch (Exception ex) { this.m_logger.Exception(ex); messageCombinator.Clear();//当组合发生异常时,应该清空组合器数据 } } break; } await e.InvokeNext(); } } }}