im 尝试开发一款游戏,我将记分牌存储在存储在服务器上(当前在本地主机上(的文本文件中。我正在使用http get和post调用,以便与服务器通信并获取和发送我想要的数据。现在我想实现 websockets,以便将通知从服务器发送到 c# 客户端。通知只会在控制台上向用户显示一条消息,例如,在 mu 情况下,我希望每次将用户添加到记分板时,每次调用 UpdateScoreBoard 方法时,都向用户显示一条消息。根据我在网上找到的教程,我已经设法构建了以下代码,任何人都可以让我更清楚地了解我将如何为服务器构建 websocket 以及如何在客户端上初始化 websocket?谢谢
启动.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
}
谁能更清楚地告诉我我将如何为服务器构建 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 核心 SignalR 是一个开源库,可简化实时通信功能的实现。它确实支持 WebSockets 传输,我们可以轻松地实现向所有连接的客户端或连接的客户端的指定子集推送消息/通知。
有关 ASP.NET 核心 SignalR 的更多信息,可以查看此文档:https://learn.microsoft.com/en-us/aspnet/core/fundamentals/websockets?view=aspnetcore-3.1
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
的消息,然后从WebSocketClient
(如上图(在ReadLoopAsync
方法中使用它们。
生成消息
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 发布/订阅 在此处检查Redis 发布/订阅
还检查我的存储库 在我所在的 GitHub 上 使用您所需要的(WebSockets,Redis,pub sub(RabbitMq
作为消息总线的另一个选项是使用RabbitMQ
,以获取有关 C#API
的更多信息 这里在内存中
您还可以避免使用第三方并使用一些内存数据 结构像
BlockingCollection
.您可以将其作为Controller(s)
和插座中Singleton
服务Middleware(s)