写在前面:
微软官方对websocket的直接支持很差,教程也写得不用心。还要用户自己去转字节数组和字符串,太过分了!
毕竟主推SignalR。
本文是在官方教程的基础上,对其进行了一些简单的讲解,和方法提取、封装,以期降低学习难度。
…………
写完这篇文章之后,我挺诧异的:就没人在底层基础上进行封装吗?
翻了一会儿nuget官网,终于找到一个用着比较舒服的包,“ ”。还比较新,网站显示,2023年6月更新过。
至此,释怀了。耐心不好的读者看后面三分之一吧。
…………
在MDN(Mozilla Developer Network)官网上,有一篇文档,讲了使用“TcpListener”接受websocket请求的写法。感觉这个包,是沿用了这个思路。有兴趣的读者也可以参考尝试。
步骤描述:
1、随便建了个普通的mvc项目(任意带控制器的.net项目道理相同,都应该可以)。在Program.cs中,添加websocket支持。见以下代码第24行。
1 namespace WebApplication2 2 { 3 public class Program 4 { 5 public static void Main(string[] args) 6 { 7 var builder = WebApplication.CreateBuilder(args); 8 9 // Add services to the container. 10 builder.Services.AddControllersWithViews(); 11 12 var app = builder.Build(); 13 14 // Configure the HTTP request pipeline. 15 if (!app.Environment.IsDevelopment()) 16 { 17 app.UseExceptionHandler("/Home/Error"); 18 } 19 app.UseStaticFiles(); 20 21 app.UseRouting(); 22 23 //启用websocket 24 app.UseWebSockets(); 25 //app.UseAuthorization(); 26 27 app.MapControllerRoute( 28 name: "default", 29 pattern: "{controller=Home}/{action=Index}/{id?}"); 30 31 app.Run(); 32 } 33 } 34 }
只支持ws或wss请求。
2、控制器代码如下。14行注册路由(即要以类似 ws://xxxx/ws 的方式访问)
1 using Microsoft.AspNetCore.Http; 2 using Microsoft.AspNetCore.Mvc; 3 using System.Net.WebSockets; 4 using System.Text; 5 using WebApplication2.Function; 6 7 namespace WebApplication2.Controllers 8 { 9 //[Route("api/[controller]")] 10 //[ApiController] 11 public class WebSocketController : ControllerBase 12 { 13 //注册路由 14 [Route("/ws")] 15 public async Task Get() 16 { 17 //如果是websocket请求 18 if (HttpContext.WebSockets.IsWebSocketRequest) 19 { 20 //获得连接 21 using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); 22 //处理程序 23 await 处理程序(webSocket); 24 } 25 else 26 { 27 HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest; 28 } 29 } 30 31 async Task 处理程序(WebSocket w) 32 { 33 string s; 34 bool f; 35 //简单的处理,收消息,修改,发送。 36 (f,s)=await WebSocket操作.收(w); 37 while(f) 38 { 39 s = "server:" + s; 40 await WebSocket操作.发(w, s); 41 (f,s)= await WebSocket操作.收(w); 42 } 43 await WebSocket操作.关(w); 44 } 45 } 46 }
注释都比较清楚,不赘述。
31行开始的处理程序,从设计的角度,不应该出现在控制器里。介意的读者,请自行处理。
WebSocket操作类,见后面。
3、WebSocket操作类:
1 using System; 2 using System.Net.WebSockets; 3 using System.Text; 4 5 namespace WebApplication2.Function 6 { 7 //对微软官方示例的简单封装, 8 public class WebSocket操作 9 { 10 //接收消息,bool指示收到的是不是关闭消息,string是收到的内容。 11 public static async Task<(bool,string)> 收(WebSocket ws) 12 { 13 //接收大小最多4k。 14 var buffer = new byte[1024 * 4]; 15 string s; 16 //把消息收到字节数组中 17 var receiveResult = await ws.ReceiveAsync( 18 new ArraySegment<byte>(buffer), CancellationToken.None); 19 //如果不是关闭消息 20 if (!receiveResult.CloseStatus.HasValue) 21 { 22 //转换为字符串返回 23 s = Encoding.UTF8.GetString(buffer, 0, receiveResult.Count); 24 return (true,s); 25 } 26 else 27 { 28 s = "无"; 29 return (false,s); 30 } 31 } 32 33 //发送消息 34 public static async Task 发(WebSocket ws, string s) 35 { 36 //消息转换成字节数组 37 var buffer = Encoding.UTF8.GetBytes(s); 38 //发送 39 await ws.SendAsync( 40 new ArraySegment<byte>(buffer, 0, buffer.Length), 41 WebSocketMessageType.Text, 42 true, 43 CancellationToken.None); 44 } 45 public static async Task 关(WebSocket ws) 46 { 47 //官方例子里,status是接收到的关闭类型。这里偷懒,应该也不影响用。 48 var status = ws.CloseStatus == null ? WebSocketCloseStatus.Empty :ws.CloseStatus.Value; 49 //关闭 50 await ws.CloseAsync( 51 status, 52 ws.CloseStatusDescription, 53 CancellationToken.None); 54 } 55 } 56 }
其中17-20,39-44,50-53行的代码,基本来自官方例程。我不求甚解,大家随意。
至此,服务端完成。
使用Nuget上的WatsonWebsocket包,构建服务器。
注意:
1、VS需要用管理员权限启动,不然无法启动服务。
2、这个包,是用guid标识用户的。发送消息的时候请注意。
3、构造函数中的false,表示不适用SSL协议。
以下代码,基本上涵盖了它的常用功能。
1 using System.Text; 2 using WatsonWebsocket; 3 4 namespace ConsoleApp1 5 { 6 internal class Program 7 { 8 static void Main(string[] args) 9 { 10 WatsonWsServer server = new WatsonWsServer("192.168.1.104", 8080, false); 11 server.ClientConnected += ClientConnected; 12 server.ClientDisconnected += ClientDisconnected; 13 server.MessageReceived += MessageReceived; 14 server.Start(); 15 Console.WriteLine("server is running..."); 16 Console .ReadKey(); 17 server.Stop(); 18 19 void ClientConnected(object? sender, ConnectionEventArgs args) 20 { 21 Console.WriteLine("Client connected: " + args.Client.ToString()); 22 } 23 24 void ClientDisconnected(object? sender, DisconnectionEventArgs args) 25 { 26 Console.WriteLine("Client disconnected: " + args.Client.ToString()); 27 } 28 29 async void MessageReceived(object? sender, MessageReceivedEventArgs args) 30 { 31 string s = Encoding.UTF8.GetString(args.Data); 32 Console.WriteLine("server's clients:" + server.ListClients().Count()+ ": " + s); 33 await server.SendAsync(args.Client.Guid,"from server:"+s); 34 } 35 } 36 } 37 }
两个服务端的客户端,都可以完全照搬 使用js和nodejs完成websocket双向通讯 里的web客户端。注意下服务器地址即可。
微软官网服务器运行结果:
含WatsonWebsocket包的服务器运行结果:
成功,调试通过。