i want to make WebSockets chat in asp.net core. I decided to make it with middleware. However there is one thing i've been thinking for a day - i don't know how to make correct broadcast. The problem is that it doesn't show messages of other users, instead it just writes your message for user count times.
Here how it should be (if there are 2 users in 2 tabs of browser):
1: Hello 2
2: Hello 1
Instead of this i see only 2 same messages of 1 user and in other tab of browser i see nothing.
Here is middleware class:
public class WebSocketChatMiddleware
{
RequestDelegate _next;
ConcurrentDictionary<string, WebSocket> connections { get; set; }
public WebSocketChatMiddleware(RequestDelegate next)
{
_next = next;
connections = new ConcurrentDictionary<string, WebSocket>();
}
public async Task InvokeAsync(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await _next.Invoke(context);
return;
}
CancellationToken ct = context.RequestAborted;
WebSocket ws = await context.WebSockets.AcceptWebSocketAsync();
string wsID = Guid.NewGuid().ToString();
connections.TryAdd(wsID, ws);
while (true)
{
if (ct.IsCancellationRequested)
{
return;
}
string data = await ReadStringAsync(ws, ct);
if (string.IsNullOrEmpty(data))
{
if (ws.State != WebSocketState.Open)
{
break;
}
continue;
}
foreach (var item in connections) // not working broadcast
{
if (ws.State == WebSocketState.Open)
{
await SendStringAsync(ws, data, ct);
}
}
}
WebSocket dummy;
connections.TryRemove(wsID,out dummy);
await ws.CloseOutputAsync(WebSocketCloseStatus.NormalClosure,"UserDisconnected",ct);
ws.Dispose();
await _next(context);
}
async Task<string> ReadStringAsync(WebSocket ws, CancellationToken ct = default)
{
var buffer = new ArraySegment<byte>(new byte[1024 * 8]);
using (MemoryStream ms = new MemoryStream())
{
WebSocketReceiveResult receiveResult;
do
{
ct.ThrowIfCancellationRequested();
receiveResult = await ws.ReceiveAsync(buffer, ct);
ms.Write(buffer.Array, buffer.Offset, receiveResult.Count);
} while (!receiveResult.EndOfMessage);
ms.Seek(0, SeekOrigin.Begin); // Changing stream position to cover whole message
if (receiveResult.MessageType != WebSocketMessageType.Text)
return null;
using (StreamReader reader = new StreamReader(ms, System.Text.Encoding.UTF8))
{
return await reader.ReadToEndAsync(); // decoding message
}
}
}
Task SendStringAsync(WebSocket ws, string data, CancellationToken ct = default)
{
var buffer = System.Text.Encoding.UTF8.GetBytes(data);
var segment = new ArraySegment<byte>(buffer);
return ws.SendAsync(segment, WebSocketMessageType.Text, true, ct);
}
Configure method in Startup.cs :
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseWebSockets();
app.UseMiddleware<WebSocketChatMiddleware>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
I will be veeeeery grateful for your help c:
CodePudding user response:
Change the code like below:
foreach (var item in connections) // not working broadcast
{
//if (ws.State == WebSocketState.Open)
//{
// await SendStringAsync(ws, data, ct);
//}
if (item.Value.State != WebSocketState.Open)
{
continue;
}
await SendStringAsync(item.Value, data, ct);
}
Result: