.Net 简单实现WebSocket通讯
使用框架:.net 8、winform
操作系统:Windows 11
编译器:vs 2022
内容:实现一个多客户端单服务端的WebSocket通讯,并可发布广播消息
本文使用.net
框架自带的WebSocket
WebSocketClient
服务类进行演示,实现一个简单的通讯。
服务端
服务端所要做的事情就是创建一个WebSocket服务器,并在有请求时进行响应。由于我们是要实现一个多客户端可广播的的通讯,而每个 WebSocket 连接都是独立处理的,若要实现广播,则需要维护一个连接列表,并在接收到消息时遍历这个列表来发送消息。
Storage类
public interface IWebSocketStorage
{
public ConcurrentDictionary<string, List<WebSocketObj>> WebSocketStorageList { get; }
Task<WebSocket> GetWebSocket(string UserId);
WebSocketObj AddWebSocketClient(string userId, WebSocket ws, string Name);
void DisconnectWebSocket(string userId, WebSocketObj ws);
}
public class WebSocketStorage : IWebSocketStorage
{
public WebSocketStorage()
{
WebSocketStorageList = new ConcurrentDictionary<string, List<WebSocketObj>>();
}
//WebSocket链接列表
public ConcurrentDictionary<string, List<WebSocketObj>> WebSocketStorageList { get; }
//将链接添加至列表
public WebSocketObj AddWebSocketClient(string userId, WebSocket ws, string Name)
{
var wsObj = new WebSocketObj()
{
WebSocket = ws,
ConnectionGuid = Guid.NewGuid(),
Name = Name,
};
if (WebSocketStorageList.ContainsKey(userId))
{
WebSocketStorageList[userId].Add(wsObj);
}
else
{
WebSocketStorageList.TryAdd(userId, new List<WebSocketObj>() { wsObj });
}
return wsObj;
}
//断开链接
public void DisconnectWebSocket(string userId, WebSocketObj ws)
{
if (WebSocketStorageList.TryGetValue(userId, out List<WebSocketObj> value))
{
value.Remove(ws);
}
}
//获取WebSocket链接
public async Task<WebSocket> GetWebSocket(string userId)
{
if (WebSocketStorageList.TryGetValue(userId, out List<WebSocketObj> value))
{
return value.FirstOrDefault().WebSocket;
}
return null;
}
}
public class WebSocketObj
{
[Newtonsoft.Json.JsonIgnore]
public WebSocket WebSocket { get; set; }
public Guid ConnectionGuid { get; set; }
public string Name { get; set; }
}
中间件
public class WebsocketHandlerMiddleware : IMiddleware
{
private readonly IWebSocketStorage _webSocketStorage;
private readonly PathString wsPath = new PathString("/ws");
public WebsocketHandlerMiddleware(IWebSocketStorage webSocketStorage)
{
_webSocketStorage = webSocketStorage;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (context.Request.Path.Equals(wsPath))
{
if (context.WebSockets.IsWebSocketRequest)
{
string name = "";
string userId = ""; //客户端传来
using WebSocket socket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
WebSocketObj clientObj1 = new WebSocketObj();
clientObj1 = _webSocketStorage.AddWebSocketClient(userInfo.UserId, socket, name);
try
{
while (socket.State != WebSocketState.Closed && socket.State != WebSocketState.Aborted)
{
byte[] buffer = new byte[1024 * 1];
WebSocketReceiveResult receiveResult = await socket.ReceiveAsync(buffer, context.RequestAborted).ConfigureAwait(false);
if (socket.State == WebSocketState.CloseReceived && receiveResult.MessageType == WebSocketMessageType.Close)
{
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Acknowledge Close frame", context.RequestAborted).ConfigureAwait(false);
}
if (socket.State == WebSocketState.Open && receiveResult.MessageType == WebSocketMessageType.Text)
{
string msgStr = Encoding.UTF8.GetString(buffer).Trim(); //客户端发来的消息
byte[] msgBytes = Encoding.UTF8.GetBytes("消息");
socket.SendAsync(new ArraySegment<byte>(msgBytes), WebSocketMessageType.Text, true, CancellationToken.None); //向客户端发送消息
var wsMessage = await _webSocketStorage.GetWebSocket("1", WebSocketType.Message);
}
}
}
catch (Exception)
{
//ignore
}
finally
{
_webSocketStorage.DisconnectWebSocket(userInfo.UserId, clientObj1);
}
}
else
{
context.Response.StatusCode = 404;
}
}
else
{
await next(context);
}
}
}
IOC
builder.Services.AddSingleton<IWebSocketStorage, WebSocketStorage>();
builder.Services.AddScoped<WebsocketHandlerMiddleware>();
如果想要广播消息,那么在该类库加上var wsStorage = App.GetService<IWebSocketStorage>();
,遍历其中的WebSocketStorageList
然后发消息即可。
客户端
客户端只需要连接WebSocket服务器,然后收发消息即可
public class Ws
{
private ClientWebSocket webSocket = new ClientWebSocket();
readonly ChromiumWebBrowser _browser;
public async void ConnectToWebSocketServer()
{
try
{
Uri serverUri = new Uri("ws://你的WebSocket地址");
await webSocket.ConnectAsync(serverUri, CancellationToken.None);
await SendMessageAsync(userId);
await ReceiveMessages();
}
catch (Exception ex)
{
MessageBox.Show($"无法连接到WebSocket服务器: {ex.Message}");
}
}
public async Task SendMessageAsync(string message)
{
if (webSocket?.State == WebSocketState.Open)
{
byte[] bytes = Encoding.UTF8.GetBytes(message);
ArraySegment<byte> segment = new ArraySegment<byte>(bytes);
await webSocket.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None);
}
}
public async Task ReceiveMessages()
{
var buffer = new byte[1024 * 4];
string message = string.Empty;
while (webSocket.State == WebSocketState.Open)
{
buffer = new byte[1024 * 4];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Text)
{
message = Encoding.UTF8.GetString(buffer, 0, result.Count).Trim();
}
else if (result.MessageType == WebSocketMessageType.Close)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
break;
}
}
}
public async Task Close()
{
if (webSocket != null && webSocket.State == WebSocketState.Open)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
}
}
总结
对于需要实时数据更新的场景,WebSocket通常是一个很好的选择。
当然,如果不考虑实时以及负载的话,长轮询其实也可以用来模拟长连接,具体情况具体分析吧大伙🤯