WebSocket messages will be sent to all connected

This commit is contained in:
Martin Berg Alstad 2023-05-17 12:57:24 +02:00
parent ad1b9b2ad9
commit d6cf7ac94d
9 changed files with 137 additions and 72 deletions

View File

@ -43,26 +43,26 @@ export default class WebSocketService {
}
set onOpen(onOpen: VoidFunction) {
this._onOpen = onOpen;
if (!this.ws) return;
this.ws.onopen = onOpen;
this._onOpen = onOpen;
}
set onReceive(onReceive: MessageEventFunction) {
this._onReceive = onReceive;
if (!this.ws) return;
this.ws.onmessage = onReceive;
this._onReceive = onReceive;
}
set onClose(onClose: VoidFunction) {
this._onClose = onClose;
if (!this.ws) return;
this.ws.onclose = onClose;
this._onClose = onClose;
}
set onError(onError: VoidFunction) {
this._onError = onError;
if (!this.ws) return;
this.ws.onerror = onError;
this._onError = onError;
}
}

View File

@ -3,20 +3,21 @@ import WebSocketService from "../classes/WebSocketService";
const ws = new WebSocketService({});
export const Counter: Component = () => { // TODO update values from different clients at the same time
export const Counter: Component = () => {
ws.onReceive = receiveMessage;
const [currentCount, setCurrentCount] = React.useState(0);
function incrementCounterAndSend() {
setCurrentCount(currentCount + 1);
if (ws.isOpen()) {
ws.send(`Current count: ${currentCount}`);
ws.send((currentCount + 1).toString());
}
}
function receiveMessage(data: MessageEvent<any>) {
setCurrentCount(currentCount + 1);
const count = parseInt(data.data);
if (!isNaN(count))
setCurrentCount(count);
}
React.useEffect(() => {

View File

@ -1,32 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace pacMan.Controllers;
[ApiController]
[Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries =
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}

View File

@ -1,6 +1,6 @@
using System.Net.WebSockets;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using pacMan.Interfaces;
namespace pacMan.Controllers;
@ -9,10 +9,14 @@ namespace pacMan.Controllers;
public class WsController : ControllerBase
{
private readonly ILogger<WsController> _logger;
private readonly IWebSocketService _wsService;
private const int BufferSize = 1024 * 4;
public WsController(ILogger<WsController> logger)
public WsController(ILogger<WsController> logger, IWebSocketService wsService)
{
_logger = logger;
_wsService = wsService;
_logger.Log(LogLevel.Debug, "WebSocket Controller created");
}
[HttpGet]
@ -22,6 +26,7 @@ public class WsController : ControllerBase
{
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
_logger.Log(LogLevel.Information, "WebSocket connection established to {}", HttpContext.Connection.Id);
_wsService.Add(webSocket);
await Echo(webSocket);
}
else
@ -32,37 +37,26 @@ public class WsController : ControllerBase
private async Task Echo(WebSocket webSocket)
{
try
{
var buffer = new byte[1024 * 4];
var buffer = new byte[BufferSize];
WebSocketReceiveResult? result;
do
{
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
_logger.Log(LogLevel.Information, "Message received from Client");
result = await _wsService.Receive(webSocket, buffer);
if (result.CloseStatus.HasValue) break;
var serverMsg = Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(buffer));
await webSocket.SendAsync(
new ArraySegment<byte>(serverMsg, 0, result.Count),
result.MessageType,
result.EndOfMessage, CancellationToken.None);
_logger.Log(LogLevel.Information, "Message sent to Client");
await _wsService.SendToAll(buffer, result.Count);
} while (true);
await webSocket.CloseAsync(
result.CloseStatus.Value,
result.CloseStatusDescription,
CancellationToken.None);
_logger.Log(LogLevel.Information, "WebSocket connection closed from {}", HttpContext.Connection.Id);
await _wsService.Close(webSocket, result.CloseStatus.Value, result.CloseStatusDescription ?? "No reason");
}
catch (WebSocketException e)
{
_logger.Log(LogLevel.Error, "{}", e.Message);
}
_wsService.Remove(webSocket);
}
}

View File

@ -0,0 +1,15 @@
using System.Net.WebSockets;
namespace pacMan.Interfaces;
public interface IWebSocketService
{
void Add(WebSocket webSocket);
void Remove(WebSocket webSocket);
Task Send(WebSocket webSocket, string message, int length);
Task Send(WebSocket webSocket, byte[] message, int length);
Task SendToAll(string message, int length);
Task SendToAll(byte[] message, int length);
Task<WebSocketReceiveResult> Receive(WebSocket webSocket, byte[] buffer);
Task Close(WebSocket webSocket, WebSocketCloseStatus closeStatus, string closeStatusDescription);
}

View File

@ -1,8 +1,12 @@
using pacMan.Interfaces;
using pacMan.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<IWebSocketService, WebSocketService>();
var app = builder.Build();

View File

@ -0,0 +1,84 @@
using System.Net.WebSockets;
using System.Text;
using pacMan.Interfaces;
using pacMan.Utils;
namespace pacMan.Services;
public class WebSocketService : IWebSocketService
{
private readonly ILogger<WebSocketService> _logger;
private readonly List<WebSocket> _webSockets = new();
public WebSocketService(ILogger<WebSocketService> logger)
{
_logger = logger;
logger.Log(LogLevel.Debug, "WebSocket Service created");
}
public void Add(WebSocket webSocket)
{
_webSockets.Add(webSocket);
_logger.Log(LogLevel.Debug, "WebSocket \"{}\" added to list", webSocket.GetHashCode());
}
public void Remove(WebSocket webSocket)
{
_webSockets.Remove(webSocket);
_logger.Log(LogLevel.Debug, "WebSocket \"{}\" removed from list", webSocket.GetHashCode());
}
public async Task Send(WebSocket webSocket, string message, int length)
{
var bytes = Encoding.UTF8.GetBytes(message);
await Send(webSocket, bytes, length);
}
public async Task Send(WebSocket webSocket, byte[] message, int length)
{
var msgSegment = new ArraySegment<byte>(message, 0, length);
await webSocket.SendAsync(
msgSegment,
WebSocketMessageType.Text,
true,
CancellationToken.None);
_logger.Log(LogLevel.Trace,
"Message \"{}\" sent to WebSocket {}",
message.GetString(length),
webSocket.GetHashCode());
}
public async Task SendToAll(string message, int length)
{
var serverMsg = Encoding.UTF8.GetBytes(message);
await SendToAll(serverMsg, length);
}
public async Task SendToAll(byte[] message, int length)
{
foreach (var ws in _webSockets) await Send(ws, message, length);
_logger.Log(LogLevel.Debug, "Message sent to all WebSockets");
}
public async Task<WebSocketReceiveResult> Receive(WebSocket webSocket, byte[] buffer)
{
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
_logger.Log(LogLevel.Debug,
"Message \"{}\" received from WebSocket {}",
buffer.GetString(result.Count),
webSocket.GetHashCode());
return result;
}
public async Task Close(WebSocket webSocket, WebSocketCloseStatus closeStatus,
string closeStatusDescription = "No reason")
{
await webSocket.CloseAsync(
closeStatus,
closeStatusDescription,
CancellationToken.None);
_logger.Log(LogLevel.Information, "WebSocket connection closed from {}", webSocket.GetHashCode());
}
}

View File

@ -0,0 +1,11 @@
using System.Text;
namespace pacMan.Utils;
public static class Extensions
{
public static string GetString(this byte[] bytes, int length)
{
return Encoding.UTF8.GetString(bytes, 0, length);
}
}

View File

@ -1,12 +0,0 @@
namespace pacMan;
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}