C
C#12mo ago
VeQox

❔ Raw Websockets, ref alternative in async programming

public async Task HandleUpgrade()
{
if (!HttpContext.WebSockets.IsWebSocketRequest)
{
Logger.LogInformation("Request not a websocket request");
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}

var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
var connection = new WebSocketConnection(webSocket);
Logger.LogInformation("Connection established");

try
{
Room? room = null;
Client? client = null;

while (!webSocket.CloseStatus.HasValue)
{
var raw = await connection.ReceiveAsync();

Logger.LogInformation("Received {Message} from connection[{Guid}]", raw, connection.Guid);

if (raw is null) continue;

var (webSocketClientMessage, _) = JsonUtils.Deserialize<WebSocketClientMessage>(raw);
if (webSocketClientMessage is null) continue;

await OnMessage(webSocketClientMessage, raw, connection, room, client);
}
}
finally
{
await connection.CloseAsync();
Logger.LogInformation("Connection closed with connection[{Guid}]", connection.Guid);
}
}
public async Task HandleUpgrade()
{
if (!HttpContext.WebSockets.IsWebSocketRequest)
{
Logger.LogInformation("Request not a websocket request");
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}

var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
var connection = new WebSocketConnection(webSocket);
Logger.LogInformation("Connection established");

try
{
Room? room = null;
Client? client = null;

while (!webSocket.CloseStatus.HasValue)
{
var raw = await connection.ReceiveAsync();

Logger.LogInformation("Received {Message} from connection[{Guid}]", raw, connection.Guid);

if (raw is null) continue;

var (webSocketClientMessage, _) = JsonUtils.Deserialize<WebSocketClientMessage>(raw);
if (webSocketClientMessage is null) continue;

await OnMessage(webSocketClientMessage, raw, connection, room, client);
}
}
finally
{
await connection.CloseAsync();
Logger.LogInformation("Connection closed with connection[{Guid}]", connection.Guid);
}
}
6 Replies
VeQox
VeQox12mo ago
private async Task OnMessage(WebSocketClientMessage webSocketClientMessage, string raw, WebSocketConnection connection, Room? room, Client? client)
{
switch (webSocketClientMessage.Event)
{
case WebSocketClientEvent.CreateRoom:
OnCreateRoom(raw, connection, ref room, ref client);
break;

case WebSocketClientEvent.JoinRoom:
OnJoin(raw, connection, ref room, ref client);
break;

case WebSocketClientEvent.StartGame:
case WebSocketClientEvent.DealerAcceptCards:
case WebSocketClientEvent.DealerRejectCards:
case WebSocketClientEvent.PlayerSwapCard:
case WebSocketClientEvent.PlayerSkipTurn:
case WebSocketClientEvent.PlayerLockTurn:
default:
if(room is null) return;
if(client is null) return;

await room.OnMessage(client, webSocketClientMessage, raw);
break;
}
}

private void OnCreateRoom(string raw, WebSocketConnection connection, ref Room? room, ref Client? client)
{
var (createRoomMessage, _) = JsonUtils.Deserialize<CreateRoomMessage>(raw);
if (createRoomMessage is null) return;

var (roomName, capacity, isPublic, name) = createRoomMessage;
if(roomName is null || capacity is null || isPublic is null || name is null) return;

room = RoomRepository.CreateRoom(
roomName,
capacity.Value,
isPublic.Value,
Logger);

client = new Client(connection, name);
client.SendAsync(new CreateRoomResponse(room.Id));
room.Join(client);
}
private async Task OnMessage(WebSocketClientMessage webSocketClientMessage, string raw, WebSocketConnection connection, Room? room, Client? client)
{
switch (webSocketClientMessage.Event)
{
case WebSocketClientEvent.CreateRoom:
OnCreateRoom(raw, connection, ref room, ref client);
break;

case WebSocketClientEvent.JoinRoom:
OnJoin(raw, connection, ref room, ref client);
break;

case WebSocketClientEvent.StartGame:
case WebSocketClientEvent.DealerAcceptCards:
case WebSocketClientEvent.DealerRejectCards:
case WebSocketClientEvent.PlayerSwapCard:
case WebSocketClientEvent.PlayerSkipTurn:
case WebSocketClientEvent.PlayerLockTurn:
default:
if(room is null) return;
if(client is null) return;

await room.OnMessage(client, webSocketClientMessage, raw);
break;
}
}

private void OnCreateRoom(string raw, WebSocketConnection connection, ref Room? room, ref Client? client)
{
var (createRoomMessage, _) = JsonUtils.Deserialize<CreateRoomMessage>(raw);
if (createRoomMessage is null) return;

var (roomName, capacity, isPublic, name) = createRoomMessage;
if(roomName is null || capacity is null || isPublic is null || name is null) return;

room = RoomRepository.CreateRoom(
roomName,
capacity.Value,
isPublic.Value,
Logger);

client = new Client(connection, name);
client.SendAsync(new CreateRoomResponse(room.Id));
room.Join(client);
}
private void OnJoin(string raw, WebSocketConnection connection, ref Room? room, ref Client? client)
{
var (joinRoomMessage, _) = JsonUtils.Deserialize<JoinRoomMessage>(raw);
if (joinRoomMessage is null) return;

var (name, roomId) = joinRoomMessage;
if(name is null || roomId is null) return;

room = RoomRepository.GetRoom(roomId);
if(room is null) return; // Send error message (no room found)

client = new Client(connection, name);
room.Join(client);
}
private void OnJoin(string raw, WebSocketConnection connection, ref Room? room, ref Client? client)
{
var (joinRoomMessage, _) = JsonUtils.Deserialize<JoinRoomMessage>(raw);
if (joinRoomMessage is null) return;

var (name, roomId) = joinRoomMessage;
if(name is null || roomId is null) return;

room = RoomRepository.GetRoom(roomId);
if(room is null) return; // Send error message (no room found)

client = new Client(connection, name);
room.Join(client);
}
What im trying to achieve with these functions is that when a connection receives an a CreateRoomEvent the scoped room in HandleUpgrade should get set. But since ref params dont work with async code this currently doesnt work and the room in HandleUpgrade stays null Is there an elegant solution for this?
Sossenbinder
Sossenbinder12mo ago
I didn't read through all of the code, but usually a workaround which achieves a similar behaviour would be to introduce by-ref semantics by passing delegates with a setter, or you simply wrap room / client structure in a container you can then pass around and change the reference inside I think in this case a dictionary based approach might work, passing a room and client map around?
VeQox
VeQox12mo ago
you mean storing a dictionary with <Guid, Room> where i can get the room on every message?
Sossenbinder
Sossenbinder12mo ago
Yeah, would that work for you? If you can pass around a container for the room and client then you don't have the problem of requiring a ref
VeQox
VeQox12mo ago
yeah that should work gonna experiment a lil bit on which solution fits here the best
Accord
Accord12mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.