WebsocketController.cs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Net.WebSockets;
  4. using System.Text;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using gameapi.Commands;
  8. using gameapi.Library;
  9. using Microsoft.AspNetCore.Http;
  10. using Microsoft.Extensions.Hosting;
  11. namespace gameapi.Controllers
  12. {
  13. public class WebsocketController : IMiddleware
  14. {
  15. private static CancellationTokenRegistration AppShutdownHandler;
  16. private WebsocketCommandFactory websocketCommandFactory;
  17. public WebsocketController(IHostApplicationLifetime hostLifetime)
  18. {
  19. if (AppShutdownHandler.Token.Equals(CancellationToken.None))
  20. {
  21. AppShutdownHandler = hostLifetime.ApplicationStopping.Register(ApplicationShutdownHandler);
  22. }
  23. websocketCommandFactory = new WebsocketCommandFactory();
  24. }
  25. public async Task InvokeAsync(HttpContext context, RequestDelegate next)
  26. {
  27. if (context.WebSockets.IsWebSocketRequest)
  28. {
  29. WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
  30. GameState.GetInstance().AddPlayer(webSocket);
  31. await SocketConnectionLoop(context, webSocket);
  32. }
  33. else
  34. {
  35. await next(context);
  36. }
  37. }
  38. private async Task SocketConnectionLoop(HttpContext context, WebSocket webSocket)
  39. {
  40. //https://mcguirev10.com/2019/08/18/minimal-full-feature-kestrel-websocket-server.html
  41. //https://github.com/MV10/WebSocketExample/tree/master/KestrelWebSocketServer
  42. try
  43. {
  44. var buffer = new byte[1024 * 4];
  45. var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
  46. while (!result.CloseStatus.HasValue)
  47. {
  48. switch (result.MessageType)
  49. {
  50. case WebSocketMessageType.Text:
  51. var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
  52. await websocketCommandFactory.Create(message).Run(webSocket);
  53. break;
  54. case WebSocketMessageType.Close:
  55. Console.WriteLine("Disconnecting");
  56. await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
  57. webSocket.Dispose();
  58. return;
  59. }
  60. result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
  61. }
  62. }
  63. catch (WebSocketException e)
  64. {
  65. Console.WriteLine("Something went wrong with this websocket." + e.Message + " " + e.StackTrace);
  66. if (webSocket.State == WebSocketState.Open)
  67. {
  68. await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "server error", CancellationToken.None);
  69. }
  70. }
  71. }
  72. public static async void ApplicationShutdownHandler()
  73. {
  74. List<Task> tasksToComplete = new List<Task>();
  75. foreach (var player in GameState.GetInstance().GetPlayers())
  76. {
  77. var timeout = new CancellationTokenSource();
  78. var websocket = player.GetConnection();
  79. var message = "server shutdown";
  80. tasksToComplete.Add(websocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(message), 0, message.Length), WebSocketMessageType.Text, true, CancellationToken.None));
  81. tasksToComplete.Add(websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "server shutdown", timeout.Token));
  82. }
  83. try
  84. {
  85. await Task.WhenAll(tasksToComplete);
  86. }
  87. catch (OperationCanceledException ex)
  88. {
  89. Console.WriteLine("Application Shutdown error: " + ex.Message);
  90. }
  91. }
  92. }
  93. }