如何在ASP.NET Core中使用WebSockets

11

我正在开发一个游戏,其中一个得分板存储在一个文本文件中,该文件存储在服务器上(目前是 localhost)。我正在使用 http 的 get 和 post 调用与服务器通信,并获取和发送所需的数据。现在,我想实现 websockets,以便从服务器向 c# 客户端发送通知。该通知将仅在控制台上为用户显示一条消息,例如在我的情况下,每当将用户添加到记分板时,每次调用 UpdateScoreBoard 方法时都会显示消息。基于我在线找到的教程,我已经成功编写了以下代码,请问有谁能帮我更清楚地解释如何为服务器构建 websocket 并如何在客户端上初始化它?谢谢

Startup.cs (服务器)

        public void Configure(IApplicationBuilder app, IHostEnvironment env)
        {
          //deleted code

            var webSocketOptions = new WebSocketOptions()
            {
                KeepAliveInterval = TimeSpan.FromSeconds(120),
                ReceiveBufferSize = 4 * 1024
            };


            app.UseWebSockets(webSocketOptions);
        
            app.Use(async (context, next) =>
            {
                if (context.Request.Path == "/ws")
                {
                    if (context.WebSockets.IsWebSocketRequest)
                    {
                        WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
                        await Echo(context, webSocket);
                    }
                    else
                    {
                        context.Response.StatusCode = 400;
                    }
                }
                else
                {
                    await next();
                }

            });
        }

        private async Task Echo(HttpContext context, WebSocket webSocket)
        {
            var buffer = new byte[1024 * 4];
            WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            while (!result.CloseStatus.HasValue)
            {
                await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);

                result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            }
            await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
        }

HttpClass.cs(客户端)- 我在这里调用http post请求

public async override Task<List<Scoreboard>> UpdateScoreBoards(string username, int attempts, int seconds, DateTime date)
            {
                HttpResponseMessage response = null;
                //Creating a new instance of object Scoreboard
                //deleted code

                var url = "http://localhost:5000/api/Scoreboard";

                var socket_url = new Uri("ws://localhost:5000"); 
                var exitEvent = new ManualResetEvent(false);
                using (var client = new WebsocketClient(socket_url))
                {
                    client.ReconnectTimeout = TimeSpan.FromSeconds(30);
                    client.ReconnectionHappened.Subscribe(info =>
                        Log.Information($"Reconnection happened, type: {info.Type}"));

                    client.MessageReceived.Subscribe(msg => Log.Information($"Message received: {msg}"));
                    await client.Start();

                    await Task.Run(() => client.Send("test"));

                    exitEvent.WaitOne();
                }

// deleted code
            }
2个回答

8
有没有人能更清楚地告诉我如何在服务器上构建WebSocket以及如何在客户端上初始化WebSocket?
正如你参考的例子所示,通过在ASP.NET Core中使用WebSocket,我们可以在Configure方法中添加WebSockets中间件,然后添加/配置请求委托来检查和处理传入的WebSocket请求。
并且在使用AcceptWebSocketAsync()方法将请求转换为WebSocket连接之后,我们可以使用返回的WebSocket对象来发送和接收消息。
Echo方法中,我们还可以执行自定义代码逻辑,根据收到的消息生成并发送回复消息/通知。
//received message
var mes = Encoding.UTF8.GetString(buffer, 0, result.Count);

//code logic here
//...

//create reply message
var reply_mes = $"You sent {mes}.";

byte[] reply_mes_buffer = Encoding.UTF8.GetBytes(reply_mes);

await webSocket.SendAsync(new ArraySegment<byte>(reply_mes_buffer, 0, reply_mes.Length), result.MessageType, result.EndOfMessage, CancellationToken.None);

此外,ASP.NET Core SignalR是一个开源库,简化了实现实时通信功能的过程。它支持WebSockets传输协议,我们可以方便地向所有连接的客户端或指定的子集发送推送消息/通知。
关于ASP.NET Core SignalR的更多信息,请查看此文档:https://learn.microsoft.com/en-us/aspnet/core/fundamentals/websockets?view=aspnetcore-3.1

8

在你的Startup中,你所需要做的就是添加UseWebsockets中间件。然后你就可以定义自己的中间件并过滤掉那些连接类型为websocket的连接,如下所示:

启动

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
            app.UseWebSockets();
            app.UseMiddleware<SocketWare>();
        }

中间件

public class SocketWare {
        private RequestDelegate next;
        public SocketWare(RequestDelegate _next) {
            this.next = _next;
        }
        public async Task Invoke(HttpContext context) {
            if (!context.WebSockets.IsWebSocketRequest) {
                return;
            }
            var socket=await context.WebSockets.AcceptWebSocketAsync();
            await RunAsync(socket);
        }
        private async Task RunAsync(WebSocket socket) {
            try {
                var client = new ChatClient(socket);
                await client.RunAsync();
            } catch (Exception ex) {

                throw;
            }
            
        }
        

    }

在我的中间件中,我更喜欢将业务逻辑保存在一个单独的类中,该类会注入Websocket,如下所示: 客户端
public class ChatClient
{
   private Task writeTask;
   private Task readTask;
   private WebSocket socket;
   private CancellationTokenSource cts=new CancellationTokenSource();
   ChatClient(WebSocket socket)
   {
       this.socket=socket;
   }
   public async Task RunAsync()
   {
      this.readTask=Task.Run(async ()=>await ReadLoopAsync(cts.Token),cts.Token);
      this.writeTask=Task.Run(async()=>await WriteLoopAsync(cts.Token),cts.Token);
      await Task.WhenAny(this.readTask,this.writeTask);
   }
   public async Task WriteLoopAsync()
   {
       Memory<byte> buffer=ArrayPool<byte>.Shared.Rent(1024);
       try {
           while (true) {
              var result= await this.socket.ReceiveAsync(buffer,....);
              var usefulBuffer=buffer.Slice(0,result.Count).ToArray();
              var raw=Encoding.Utf8.GetString(usefulBuffer);
              //deserialize it to whatever you need
              //handle message as you please (store it somwhere whatever)
            }
        } catch (Exception ex) {

               //socket error handling
               //break loop or continue with go to
        }
   }
   public async Task ReadLoopAsync()
   {
          try {
            while (true) {
              
                var data = await this.[someMessageProvider].GetMessageAsync() //read below !!!
                var bytes = Encoding.UTF8.GetBytes(data);
                //send the message on the websocket
                await this.socket.SendAsync(data, WebSocketMessageType.Text, true, CancellationToken.None);
            }
        } catch (Exception ex) {

            //do incorrect message/socket disconnect logic
        }
   }
}

关于生产和消费消息的问题。在您的情况下,您可以定义您的生产者作为一些Controller路由,如下所示。您将访问一个路由,生成一条消息并将其发布到某个消息代理中。我会使用一个消息队列(RabbitMQ)或者甚至是一个Redis Pub/Sub作为消息总线。您将从您的route(s)发布消息,然后在WebSocketClientReadLoopAsync方法中消费它们(请参见上文)。 生产消息
public UpdateController:Controller
{
   private IConnection
   [HttpPost]
   [someroute]
   public void UpdateScoreboard(string someMessage)
   {
       this.connection.Publish("someChannel",someMessage);
   }
   [HttpPost]
   [someotherroute]
   public void DeletePlayer(string someOtherMessage)
   {
       this.connection.Publish("someChannel",someMessage);
   }
}
  • Redis pub/sub
    查看Redis pub/sub的相关信息,请点击此处。另外,可以查看我的GitHub库此处,其中我正在使用你需要的内容(WebSocket、Redis和pub/sub)。

  • RabbitMq
    还有一种选择作为消息总线,可以使用RabbitMQ,有关C#API的更多信息请点击此处

  • In Memory

    您还可以避免使用第三方工具,并使用像BlockingCollection这样的内存数据结构。可以将其注入为单例服务,同时在您的控制器(Controller)和套接字中间件(Middleware)中使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接