New UT to cover web socket. Use view component.
This commit is contained in:
+3
-1
@@ -3,6 +3,8 @@
|
||||
*.lock.json
|
||||
package-lock.json
|
||||
*.user
|
||||
*.min.css
|
||||
*.min.js
|
||||
bin/
|
||||
obj/
|
||||
node_modules/
|
||||
@@ -13,4 +15,4 @@ bundle.js
|
||||
appsettings.Production.json
|
||||
appsettings.Development.json
|
||||
*.log
|
||||
TestResults/
|
||||
TestResults/
|
||||
@@ -8,16 +8,13 @@ namespace Aiursoft.ChessServer.Controllers;
|
||||
[Route("games")]
|
||||
public class GamesController : Controller
|
||||
{
|
||||
private readonly ILogger<GamesController> _logger;
|
||||
private readonly WebSocketPusher _pusher;
|
||||
private readonly InMemoryDatabase _database;
|
||||
|
||||
public GamesController(
|
||||
ILogger<GamesController> logger,
|
||||
WebSocketPusher pusher,
|
||||
InMemoryDatabase database)
|
||||
{
|
||||
_logger = logger;
|
||||
_pusher = pusher;
|
||||
_database = database;
|
||||
}
|
||||
@@ -40,25 +37,16 @@ public class GamesController : Controller
|
||||
public async Task GetWebSocket([FromRoute] int id)
|
||||
{
|
||||
var game = _database.GetOrAddGame(id);
|
||||
ISubscription? subscription = null;
|
||||
|
||||
await _pusher.Accept(HttpContext);
|
||||
var subscription = game.Channel.Subscribe(_pusher.Send);
|
||||
try
|
||||
{
|
||||
subscription = game.Channel.Subscribe(async t =>
|
||||
{
|
||||
await _pusher.SendMessage(t);
|
||||
_logger.LogInformation("Message was sent to client with ID: {PusherId}", _pusher.Id);
|
||||
});
|
||||
|
||||
_logger.LogInformation("Game {Id} registering listener {PusherId} done. Waiting for close. Now it has {Count} subscribers", id, _pusher.Id, game.Channel.GetListenerCount());
|
||||
await _pusher.PendingClose();
|
||||
await _pusher.Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
await _pusher.Close();
|
||||
subscription!.UnRegister();
|
||||
_logger.LogInformation("Game {Id}'s pusher {PusherId} connection was interrupted. Now it has {Count} subscribers", id, _pusher.Id, game.Channel.GetListenerCount());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Aiursoft.ChessServer.Models;
|
||||
|
||||
public class Player
|
||||
{
|
||||
public string NickName { get; set; } = "Anonymous";
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
}
|
||||
@@ -9,8 +9,6 @@ public class WebSocketPusher : IScopedDependency
|
||||
private bool _dropped;
|
||||
private WebSocket? _ws;
|
||||
|
||||
public readonly Guid Id = Guid.NewGuid();
|
||||
|
||||
public bool Connected => !_dropped && _ws?.State == WebSocketState.Open;
|
||||
|
||||
public async Task Accept(HttpContext context)
|
||||
@@ -18,11 +16,11 @@ public class WebSocketPusher : IScopedDependency
|
||||
_ws = await context.WebSockets.AcceptWebSocketAsync();
|
||||
}
|
||||
|
||||
public async Task SendMessage(string message)
|
||||
public async Task Send(string message)
|
||||
{
|
||||
if (_dropped)
|
||||
{
|
||||
return;
|
||||
throw new InvalidOperationException("WebSocket is dropped!");
|
||||
}
|
||||
try
|
||||
{
|
||||
@@ -34,11 +32,11 @@ public class WebSocketPusher : IScopedDependency
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PendingClose()
|
||||
public async Task Wait()
|
||||
{
|
||||
try
|
||||
{
|
||||
var buffer = new ArraySegment<byte>(new byte[4096 * 20]);
|
||||
var buffer = new byte[1024 * 4];
|
||||
while (true)
|
||||
{
|
||||
await (_ws?.ReceiveAsync(buffer, CancellationToken.None) ?? throw new InvalidOperationException("WebSocket is not connected!"));
|
||||
|
||||
@@ -1,64 +1,5 @@
|
||||
@model int
|
||||
|
||||
@section Styles {
|
||||
<link href="~/node_modules/@@chrisoakman/chessboardjs/dist/chessboard-1.0.0.min.css" rel="stylesheet" />
|
||||
<style>
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div id="board" style="width: 100%"></div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
@* Make the following p auto warp *@
|
||||
|
||||
<p id="status" class="text-center text-wrap text-primary mt-1"></p>
|
||||
<p id="fen" class="text-wrap text-muted hidden"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="chooseCharacter" tabindex="-1" aria-labelledby="chooseCharacter" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">Choose your character</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<button type="button" id="chooseWhite" class="btn">
|
||||
<img src="/img/chesspieces/wikipedia/wK.png" alt="White">
|
||||
</button>
|
||||
<button type="button" id="chooseBlack" class="btn">
|
||||
<img src="/img/chesspieces/wikipedia/bK.png" alt="Black">
|
||||
</button>
|
||||
<button type="button" data-bs-dismiss="modal" id="choosePeek" class="btn btn-primary">Peek~👀</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script type="module" src="~/node_modules/@@chrisoakman/chessboardjs/dist/chessboard-1.0.0.min.js"></script>
|
||||
<script type="module" src="~/site.js"></script>
|
||||
<script type="module">
|
||||
// Ask the user to enter w or b:
|
||||
import Game from "/site.js";
|
||||
let game = new Game(@Model);
|
||||
game.chooseCharacter();
|
||||
|
||||
$("#chooseWhite").on("click", function () {
|
||||
game.setPlayer('w');
|
||||
game.startGame();
|
||||
});
|
||||
$("#chooseBlack").on("click", function () {
|
||||
game.setPlayer('b');
|
||||
game.startGame();
|
||||
});
|
||||
$('#choosePeek').on('click', function () {
|
||||
game.startGame();
|
||||
});
|
||||
</script>
|
||||
}
|
||||
<vc:chess-board game-id="@Model"></vc:chess-board>
|
||||
</div>
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Aiursoft.ChessServer.Views.Shared.Components;
|
||||
|
||||
public class ChessBoard : ViewComponent
|
||||
{
|
||||
public IViewComponentResult Invoke(int gameId)
|
||||
{
|
||||
return View(new ChessBoardModel
|
||||
{
|
||||
GameId = gameId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class ChessBoardModel
|
||||
{
|
||||
public int GameId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
@model Aiursoft.ChessServer.Views.Shared.Components.ChessBoardModel
|
||||
|
||||
|
||||
<div class="col-md-6">
|
||||
<div id="board" style="width: 100%" class="w-100"></div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<p id="status" class="text-center text-wrap text-primary mt-1"></p>
|
||||
</div>
|
||||
|
||||
|
||||
<script type="module">
|
||||
import initGameBoard from "/scripts/chessboard.js";
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
// Ask the user to enter w or b:
|
||||
const player = prompt("Please enter your color (w or b):");
|
||||
initGameBoard(player, @Model.GameId);
|
||||
});
|
||||
</script>
|
||||
@@ -1,44 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Aiursoft Chess Server</title>
|
||||
<link rel="stylesheet" href="~/node_modules/bootstrap/dist/css/bootstrap.min.css" />
|
||||
@await RenderSectionAsync("Styles", required: false)
|
||||
<link rel="stylesheet" href="~/node_modules/@@chrisoakman/chessboardjs/dist/chessboard-1.0.0.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" asp-controller="Home" asp-action="Index">Aiursoft 国际象棋服务器</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
|
||||
<ul class="navbar-nav flex-grow-1">
|
||||
@* <li class="nav-item"> *@
|
||||
@* <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">关于</a> *@
|
||||
@* </li> *@
|
||||
</ul>
|
||||
</div>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" asp-controller="Home" asp-action="Index">Aiursoft Chess Server</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
|
||||
<ul class="navbar-nav flex-grow-1">
|
||||
@* <li class="nav-item"> *@
|
||||
@* <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">关于</a> *@
|
||||
@* </li> *@
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="container">
|
||||
<main role="main" class="pb-3">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="border-top footer text-muted">
|
||||
<div class="container">
|
||||
© @DateTime.UtcNow.Year - Aiursoft.ChessServer
|
||||
</div>
|
||||
</footer>
|
||||
<script src="~/node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="~/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</nav>
|
||||
</header>
|
||||
<div class="container">
|
||||
<main role="main" class="pb-3">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="border-top footer text-muted">
|
||||
<div class="container">
|
||||
© @DateTime.UtcNow.Year - Aiursoft.ChessServer
|
||||
</div>
|
||||
</footer>
|
||||
<script type="module" src="~/node_modules/jquery/dist/jquery.min.js" defer></script>
|
||||
<script type="module" src="~/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" defer></script>
|
||||
<script type="module" src="~/node_modules/@@chrisoakman/chessboardjs/dist/chessboard-1.0.0.min.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -1,4 +1,5 @@
|
||||
@using System.Collections.Generic
|
||||
@using Microsoft.AspNetCore.Http
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@addTagHelper *, Aiursoft.ChessServer
|
||||
+9
-74
@@ -1,57 +1,4 @@
|
||||
import { Chess } from "/node_modules/chess.js/dist/esm/chess.js";
|
||||
|
||||
const WHITE = "w";
|
||||
const BLACK = "b";
|
||||
const SPECTATOR = "";
|
||||
|
||||
/**
|
||||
* representing current Game
|
||||
*
|
||||
* ## example
|
||||
* ```
|
||||
* let gameId = 0;
|
||||
* let game = new Game(gameId);
|
||||
* ```
|
||||
*
|
||||
* @param {number} gameId current id of game
|
||||
*/
|
||||
function Game(gameId) {
|
||||
this.gameId = gameId;
|
||||
this.player = "";
|
||||
this.chooseCharacterModal = new bootstrap.Modal(
|
||||
document.getElementById("chooseCharacter")
|
||||
);
|
||||
|
||||
this.chooseCharacter = () => {
|
||||
this.chooseCharacterModal.show();
|
||||
};
|
||||
|
||||
this.setPlayer = (player) => {
|
||||
if ([WHITE, BLACK].includes(player)) {
|
||||
this.player = player;
|
||||
} else {
|
||||
this.player = SPECTATOR;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* let's start to play!
|
||||
* but before, you need to choose a character
|
||||
*/
|
||||
this.startGame = () => {
|
||||
this.chooseCharacterModal.hide();
|
||||
|
||||
initGameBoard(this.player, this.gameId);
|
||||
|
||||
if (this.player === SPECTATOR) {
|
||||
document.getElementById("board").style.cursor = "not-allowed";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const statusControl = $("#status");
|
||||
const fenControl = $("#fen");
|
||||
|
||||
const initGameBoard = function (player, gameId) {
|
||||
$.get("/games/" + gameId + ".fen", function (fen) {
|
||||
let board = null;
|
||||
@@ -69,10 +16,9 @@ const initGameBoard = function (player, gameId) {
|
||||
|
||||
// only pick up pieces for the side to move
|
||||
if (
|
||||
(game.turn() === "w" && piece.search(/^b/) !== -1) ||
|
||||
(game.turn() === "b" && piece.search(/^w/) !== -1)
|
||||
) {
|
||||
return false;
|
||||
(game.turn() === "w" && piece.search(/^b/) !== -1) ||
|
||||
(game.turn() === "b" && piece.search(/^w/) !== -1)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,18 +44,14 @@ const initGameBoard = function (player, gameId) {
|
||||
board.position(game.fen());
|
||||
}
|
||||
|
||||
const statusControl = $("#status");
|
||||
function updateStatusText() {
|
||||
let status;
|
||||
let moveColor = "White";
|
||||
if (game.turn() === "b") {
|
||||
moveColor = "Black";
|
||||
}
|
||||
if (game.isCheckmate()) {
|
||||
status =
|
||||
"Game over, " +
|
||||
moveColor +
|
||||
" is in checkmate, and winner is " +
|
||||
(game.turn() === "w" ? "Black" : "White");
|
||||
} if (game.isCheckmate()) {
|
||||
status = "Game over, " + moveColor + " is in checkmate, and winner is " + (game.turn() === "w" ? "Black" : "White");
|
||||
} else if (game.isDraw()) {
|
||||
status = "Game over, drawn position";
|
||||
} else {
|
||||
@@ -119,7 +61,6 @@ const initGameBoard = function (player, gameId) {
|
||||
}
|
||||
}
|
||||
statusControl.html(status);
|
||||
fenControl.html(game.fen());
|
||||
}
|
||||
|
||||
const config = {
|
||||
@@ -129,18 +70,13 @@ const initGameBoard = function (player, gameId) {
|
||||
position: fen,
|
||||
onDragStart: onDragStart,
|
||||
onSnapEnd: onSnapEnd,
|
||||
onDrop: onDrop,
|
||||
onDrop: onDrop
|
||||
};
|
||||
board = ChessBoard("board", config);
|
||||
|
||||
function refresh(newFen) {
|
||||
game = new Chess(newFen);
|
||||
board.position(newFen);
|
||||
|
||||
// Hack here: Set the position again after a short delay to avoid thread conflicts.
|
||||
setTimeout(function () {
|
||||
board.position(newFen);
|
||||
}, 300);
|
||||
console.log("Got fen " + newFen + ". refreshing board...");
|
||||
updateStatusText();
|
||||
}
|
||||
@@ -149,7 +85,7 @@ const initGameBoard = function (player, gameId) {
|
||||
|
||||
const wsScheme = window.location.protocol === "https:" ? "wss://" : "ws://";
|
||||
const socket = new WebSocket(
|
||||
wsScheme + window.location.host + "/games/" + gameId + ".ws"
|
||||
wsScheme + window.location.host + "/games/" + gameId + ".ws"
|
||||
);
|
||||
socket.onmessage = function (event) {
|
||||
refresh(event.data);
|
||||
@@ -157,7 +93,6 @@ const initGameBoard = function (player, gameId) {
|
||||
|
||||
// Auto reconnect.
|
||||
socket.onclose = function () {
|
||||
alert("Socket closed. Reconnecting...");
|
||||
setTimeout(function () {
|
||||
initGameBoard(player, gameId);
|
||||
}, 1000);
|
||||
@@ -166,4 +101,4 @@ const initGameBoard = function (player, gameId) {
|
||||
};
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export default Game;
|
||||
export default initGameBoard;
|
||||
@@ -1,7 +1,9 @@
|
||||
using Aiursoft.CSTools.Tools;
|
||||
using System.Net.WebSockets;
|
||||
using Aiursoft.CSTools.Tools;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using static Aiursoft.WebTools.Extends;
|
||||
// ReSharper disable StringLiteralTypo
|
||||
|
||||
namespace Aiursoft.ChessServer.Tests;
|
||||
|
||||
@@ -73,4 +75,68 @@ public class BasicTests
|
||||
var response = await _http.PostAsync(_endpointUrl + url, new StringContent(""));
|
||||
Assert.AreEqual(400, (int)response.StatusCode);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(7)]
|
||||
[DataRow(8)]
|
||||
[DataRow(9)]
|
||||
public async Task TestConnect(int gameId)
|
||||
{
|
||||
var tester = new WebSocketTester();
|
||||
var socket = new ClientWebSocket();
|
||||
|
||||
await socket.ConnectAsync(new Uri(_endpointUrl.Replace("http", "ws") + $"/games/{gameId}.ws"),
|
||||
CancellationToken.None);
|
||||
await Task.Factory.StartNew(() => tester.Monitor(socket));
|
||||
|
||||
await _http.PostAsync(_endpointUrl + $"/games/{gameId}/move/w/e4", new StringContent(""));
|
||||
await Task.Delay(50);
|
||||
Assert.AreEqual("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1", tester.LastMessage);
|
||||
|
||||
await _http.PostAsync(_endpointUrl + $"/games/{gameId}/move/b/e5", new StringContent(""));
|
||||
await Task.Delay(50);
|
||||
Assert.AreEqual("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2", tester.LastMessage);
|
||||
|
||||
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(10)]
|
||||
public async Task TestGameWithReconnection(int gameId)
|
||||
{
|
||||
var socket1 = new ClientWebSocket();
|
||||
var tester1 = new WebSocketTester();
|
||||
await socket1.ConnectAsync(new Uri(_endpointUrl.Replace("http", "ws") + $"/games/{gameId}.ws"),
|
||||
CancellationToken.None);
|
||||
await Task.Factory.StartNew(() => tester1.Monitor(socket1));
|
||||
|
||||
var socket2 = new ClientWebSocket();
|
||||
var tester2 = new WebSocketTester();
|
||||
await socket2.ConnectAsync(new Uri(_endpointUrl.Replace("http", "ws") + $"/games/{gameId}.ws"),
|
||||
CancellationToken.None);
|
||||
await Task.Factory.StartNew(() => tester2.Monitor(socket2));
|
||||
|
||||
await _http.PostAsync(_endpointUrl + $"/games/{gameId}/move/w/e4", new StringContent(""));
|
||||
await Task.Delay(50);
|
||||
Assert.AreEqual("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1", tester1.LastMessage);
|
||||
Assert.AreEqual("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1", tester2.LastMessage);
|
||||
|
||||
await _http.PostAsync(_endpointUrl + $"/games/{gameId}/move/b/e5", new StringContent(""));
|
||||
await Task.Delay(50);
|
||||
Assert.AreEqual("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2", tester1.LastMessage);
|
||||
Assert.AreEqual("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2", tester2.LastMessage);
|
||||
|
||||
await socket1.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||||
var socket3 = new ClientWebSocket();
|
||||
var tester3 = new WebSocketTester();
|
||||
await socket3.ConnectAsync(new Uri(_endpointUrl.Replace("http", "ws") + $"/games/{gameId}.ws"),
|
||||
CancellationToken.None);
|
||||
await Task.Factory.StartNew(() => tester3.Monitor(socket3));
|
||||
|
||||
await _http.PostAsync(_endpointUrl + $"/games/{gameId}/move/w/Nf3", new StringContent(""));
|
||||
await Task.Delay(50);
|
||||
Assert.AreEqual("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2", tester1.LastMessage);
|
||||
Assert.AreEqual("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2", tester2.LastMessage);
|
||||
Assert.AreEqual("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2", tester3.LastMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
|
||||
namespace Aiursoft.ChessServer.Tests;
|
||||
|
||||
public class WebSocketTester
|
||||
{
|
||||
public string LastMessage { get; private set; } = string.Empty;
|
||||
|
||||
public async Task Monitor(WebSocket socket)
|
||||
{
|
||||
var buffer = new ArraySegment<byte>(new byte[2048]);
|
||||
while (true)
|
||||
{
|
||||
var result = await socket.ReceiveAsync(buffer, CancellationToken.None);
|
||||
switch (result.MessageType)
|
||||
{
|
||||
case WebSocketMessageType.Close:
|
||||
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||||
return;
|
||||
case WebSocketMessageType.Text:
|
||||
LastMessage = Encoding.UTF8.GetString(buffer.Array!, 0, result.Count);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user