简体   繁体   English

如何在 asp.net 内核中使用 websockets

[英]How to use websockets in asp.net core

im trying to develop a game where i store a scoreboard in a text file which is stored on server (currently on localhost).我正在尝试开发一个游戏,我将记分牌存储在一个文本文件中,该文件存储在服务器上(当前在本地主机上)。 I am using http get and post calls in order to communicate with the server and get and send the data that i want.我正在使用 http 获取和发布调用,以便与服务器通信并获取和发送我想要的数据。 Now i want to implement websockets in order to send a notification from the server to the c# client.现在我想实现 websockets 以便从服务器向 c# 客户端发送通知。 The notification will just display on the console a message for the user, for example in mu case i want to display a message to the user each time a user is added to the scoreboard, each time the UpdateScoreBoard method is called.通知只会在控制台上为用户显示一条消息,例如,在 mu 情况下,我想在每次将用户添加到记分牌时,每次调用 UpdateScoreBoard 方法时向用户显示一条消息。 Based on tutorials i found online i have managed to build the following code, can anyone make it more clear for me how the i will build the websocket for the server and how i will initialize the websocket on the client?根据我在网上找到的教程,我设法构建了以下代码,谁能让我更清楚我将如何为服务器构建 websocket 以及如何在客户端上初始化 websocket? Thank you谢谢

Startup.cs (Server) 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 (Client) - where i call the http post request HttpClass.cs(客户端) - 我称之为 http 发布请求

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
            }

can anyone make it more clear for me how the i will build the websocket for the server and how i will initialize the websocket on the client?谁能让我更清楚我将如何为服务器构建 websocket 以及如何在客户端上初始化 websocket?

As the example that you referenced demonstrated, making use of WebSocket in ASP.NET Core, we can add the WebSockets middleware in the Configure method, then add/configure request delegate to check and handle incoming WebSocket requests.如您所引用的示例所示,利用 ASP.NET 内核中的 WebSocket,我们可以在Configure方法中添加WebSockets 中间件,然后添加/配置请求委托来检查和处理传入的 Z45EDC1B96407D9D213Z112A 请求。

And after transitioned a request to a WebSocket connection with AcceptWebSocketAsync() method, we can use the returned WebSocket object to send and receive messages.在使用AcceptWebSocketAsync()方法将请求转换为 WebSocket 连接后,我们可以使用返回的 WebSocket object 来发送和接收消息。

In Echo method, we can also perform custom code logic to generate and send reply message/notification based on received message(s).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);

Besides, ASP.NET Core SignalR is an open-source library that simplifies implementing real-time communication functionality.此外,ASP.NET Core SignalR 是一个开源库,可简化实时通信功能的实现。 And it does support WebSockets transport and we can easily achieving push messages/notifications to all connected clients or specified subsets of connected clients.它确实支持 WebSockets 传输,我们可以轻松地将消息/通知推送到所有连接的客户端或连接的客户端的指定子集。

For more information about ASP.NET Core SignalR, you can check this doc: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/websockets?view=aspnetcore-3.1有关 ASP.NET 内核 SignalR 的更多信息,您可以查看此文档: https://docs.microsoft.com/en-us/aspnet/netcore-fundamentals?

The only thing you need in your Startup is to add the UseWebsockets middleware.您在Startup中唯一需要的就是添加UseWebsockets中间件。 Then you can define your own middleware and filter connections if they are websocket type like below:如果它们是websocket类型,您可以定义自己的中间件和过滤器连接,如下所示:

Startup启动

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

Middleware中间件

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, this.store,this.channelRegistry);
                await client.RunAsync();
            } catch (Exception ex) {

                throw;
            }
            
        }
        

    }

In my middleware i prefer to keep my business logic in a separate class that gets the Websocket injected in it like below:在我的中间件中,我更喜欢将我的业务逻辑保存在一个单独的 class 中,该Websocket如下所示:

Client客户

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
        }
   }
}

Now regarding producing messages and consuming them.现在关于生成消息和使用它们。 In your case you could define your producers as some Controller routes like below.You would hit a route, produce a message and publish it to some message broker.在您的情况下,您可以将您的生产者定义为一些Controller路由,如下所示。您将点击一条路由,生成一条消息并将其发布到某个消息代理。 I would use a Message Queue (RabbitMQ) or even a Redis Pub/Sub as a message bus.我会使用消息队列 (RabbitMQ) 甚至Redis Pub/Sub作为消息总线。 You would publish messages from your route (s) and then consume them in your ReadLoopAsync method from WebSocketClient (look above).您将从您的route发布消息,然后在WebSocketClientReadLoopAsync方法中使用它们(见上图)。

Producing messages产生消息

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 发布/订阅
    Check redis pub/sub here在此处检查 redis 发布/订阅
    Also check my repository on github here in which i am using exactly what you need (websockets, redis,pub sub)还要检查我在 github 上的存储库,在这里我正在使用你需要的东西(websockets,redis,pub sub)

  • RabbitMq RabbitMq
    Another option as a message bus is to use RabbitMQ , for more info regarding C# API here作为消息总线的另一个选项是使用RabbitMQ ,有关 C# API的更多信息在这里

  • In Memory在 Memory

    You could also avoid using a third party and use some in memory data structure like a BlockingCollection .You could inject it as a Singleton service both in your Controller(s) and your socket Middleware(s)您还可以避免使用第三方并在 memory 数据结构中使用一些第三方,例如BlockingCollection 。您可以在Controller(s)和套接字Middleware(s)中将其作为Singleton服务注入

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM