先拉开MSDN的文档,大致读一遍 (https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/websockets)
WebSocket 是一个协议,支持通过 TCP 连接建立持久的双向信道。 它可用于聊天、股票报价和游戏等应用程序,以及 Web 应用程序中需要实时功能的任何情景。
使用方法
- 安装 Microsoft.AspNetCore.WebSockets 包。
- 配置中间件。
- 接受 WebSocket 请求。
- 发送和接收消息。
如果是创建的asp.net core项目,默认会有一个all的包,里面默认带了websocket的包。所以,添加的时候,注意看一下
然后就是配置websocket的中间件
app.UseWebSockets();
如果需要更细致的配置websocket,MSDN文档上也提供了一种配置缓冲区大小和ping的option
var webSocketOptions = new WebSocketOptions()
{
KeepAliveInterval = TimeSpan.FromSeconds(), //向客户端发送“ping”帧的频率,以确保代理保持连接处于打开状态
ReceiveBufferSize = * 1024 //用于接收数据的缓冲区的大小。 只有高级用户才需要对其进行更改,以便根据数据大小调整性能。
};
app.UseWebSockets(webSocketOptions);
接受 WebSocket 请求
在请求生命周期后期(例如在 Configure
方法或 MVC 操作的后期),检查它是否是 WebSocket 请求并接受 WebSocket 请求。
该示例来自 Configure
方法的后期。
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 = ;
}
}
else
{
await next();
} });
WebSocket 请求可以来自任何 URL,但此示例代码只接受 /ws
的请求
(比如要测试websocket的连接,地址必须写上:ws://ip:端口/ws) 最后这个路径的ws是可以自己定义的,可以理解为MVC的路由,或者url地址,websocket第一次连接的时候,可以使用url传递参数
发送和接收消息
AcceptWebSocketAsync
方法将 TCP 连接升级到 WebSocket 连接,并提供 WebSocket 对象。 使用 WebSocket 对象发送和接收消息。
之前显示的接受 WebSocket 请求的代码将 WebSocket
对象传递给 Echo
方法;此处为 Echo
方法。 代码接收消息并立即发回相同的消息。 一直在循环中执行此操作,直到客户端关闭连接
private async Task Echo(HttpContext context, WebSocket webSocket)
{
var buffer = new byte[ * ];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
await webSocket.SendAsync(new ArraySegment<byte>(buffer, , 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);
}
如果在开始此循环之前接受 WebSocket,中间件管道会结束。 关闭套接字后,管道展开。 也就是说,如果接受 WebSocket ,请求会在管道中停止前进,就像点击 MVC 操作一样。 但是完成此循环并关闭套接字时,请求将在管道中后退。
如果要测试是否连上,那么可以自己写ws的客户端程序,当然也可以使用一些现成的工具辣
封装一个简单的中间件:
什么是中间件?MSDN对此的解释是:
中间件是一种装配到应用程序管道以处理请求和响应的软件。 每个组件:
- 选择是否将请求传递到管道中的下一个组件。
- 可在调用管道中的下一个组件前后执行工作。
请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。
使用 Run、Map 和 Use 扩展方法来配置请求委托。 可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件或中间件组件。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或在适当情况下使链发生短路。
新建一个WebSocketExtensions.cs的类
public static class WebSocketExtensions
{
public static IApplicationBuilder MapWebSocketManager(this IApplicationBuilder app,PathString path,WebSocketHandler handler)
{
return app.Map(path, (_app) => _app.UseMiddleware<WebSocketManagerMiddleware>(handler));
}
public static IServiceCollection AddWebSocketManager(this IServiceCollection services)
{
services.AddTransient<WebSocketConnectionManager>(); foreach (var type in Assembly.GetEntryAssembly().ExportedTypes)
{
if (type.GetTypeInfo().BaseType == typeof(WebSocketHandler))
{
services.AddSingleton(type);
}
} return services;
}
}
AddWebSocketManager这个方法主要是处理依赖注入的问题。通过反射把实现WebSocketHandler的类,统统注入
管理websocket连接
public class WebSocketConnectionManager
{
private ConcurrentDictionary<string, WebSocket> _sockets = new ConcurrentDictionary<string, WebSocket>(); public int GetCount()
{
return _sockets.Count;
} public WebSocket GetSocketById(string id)
{
return _sockets.FirstOrDefault(p => p.Key == id).Value;
} public ConcurrentDictionary<string, WebSocket> GetAll()
{
return _sockets;
}
public WebSocket GetWebSocket(string key)
{
WebSocket _socket;
_sockets.TryGetValue(key, out _socket);
return _socket; } public string GetId(WebSocket socket)
{
return _sockets.FirstOrDefault(p => p.Value == socket).Key;
}
public void AddSocket(WebSocket socket,string key)
{
if (GetWebSocket(key)!=null)
{
_sockets.TryRemove(key, out WebSocket destoryWebsocket);
}
_sockets.TryAdd(key, socket);
//string sId = CreateConnectionId();
//while (!_sockets.TryAdd(sId, socket))
//{
// sId = CreateConnectionId();
//} } public async Task RemoveSocket(string id)
{
try
{
WebSocket socket; _sockets.TryRemove(id, out socket); await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None); }
catch (Exception)
{ } } public async Task CloseSocket(WebSocket socket)
{
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
} private string CreateConnectionId()
{
return Guid.NewGuid().ToString();
}
}
这里我把客户的连接的管理都封装到一个连接类里面,我的思路是
- 我使用webapi来验证身份,走http协议的接口
- 验证成功后,服务器给客户端返回一个token
- 客户端通过websocket连接服务器的时候,需要带上上次返回的token,这样我就可以在连接字典里面判断出重复的socket(因为我试过在.Net core里面如果多次连接,服务器不会走断开的事件,而是不断的出现多个socket对象)
WebSocketManagerMiddleware类的封装
public class WebSocketManagerMiddleware
{
private readonly RequestDelegate _next;
private WebSocketHandler _webSocketHandler { get; set; } public WebSocketManagerMiddleware(RequestDelegate next,
WebSocketHandler webSocketHandler)
{
_next = next;
_webSocketHandler = webSocketHandler;
} public async Task Invoke(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
return; var socket = await context.WebSockets.AcceptWebSocketAsync();
string Key = context.Request.Query["Key"];
Console.WriteLine("连接人:"+Key); _webSocketHandler.OnConnected(socket,Key); await Receive(socket, async (result, buffer) =>
{
if (result.MessageType == WebSocketMessageType.Text)
{
await _webSocketHandler.ReceiveAsync(socket, result, buffer);
return;
} else if (result.MessageType == WebSocketMessageType.Close)
{
await _webSocketHandler.OnDisconnected(socket);
return;
} }); //TODO - investigate the Kestrel exception thrown when this is the last middleware
//await _next.Invoke(context);
} private async Task Receive(WebSocket socket, Action<WebSocketReceiveResult, byte[]> handleMessage)
{
try
{
var buffer = new byte[ * ]; while (socket.State == WebSocketState.Open)
{
var result = await socket.ReceiveAsync(buffer: new ArraySegment<byte>(buffer),
cancellationToken: CancellationToken.None); handleMessage(result, buffer);
}
}
catch (Exception ex)
{
GsLog.E(ex.StackTrace);
} }
}
Invoke的时候,传递的key参数需要客户端验证身份后,传递进来:(ws://ip:端口/ws?key=xxx) 这样的格式来连接websocket
在这个类里面,我们主要处理三个事情
- 如果客户端连接进来,那么我们把这个连接放到连接管理字典里面
- 如果客户端断开连接,那么我们把这个连接冲连接管理字典里面移除
- 如果是发送数据过来,那么我们就调用ReceiveAsync方法,并把连接对象和数据传递进去
WebSocketHandler类的封装
这个类主要关联游戏逻辑模块和websocket的一个纽带,我们封装的中间件,通过websockethandler把数据传递给实现这个类的子类
public abstract class WebSocketHandler
{
public WebSocketConnectionManager WebSocketConnectionManager { get; set; } public WebSocketHandler(WebSocketConnectionManager webSocketConnectionManager)
{
WebSocketConnectionManager = webSocketConnectionManager;
} public virtual void OnConnected(WebSocket socket, string key)
{
//var ServerSocket = WebSocketConnectionManager.GetWebSocket(key);
//if (ServerSocket != null)
//{
// WebSocketConnectionManager.AddSocket();
// Console.WriteLine("已经存在当前的连接,断开。。");
//}
WebSocketConnectionManager.AddSocket(socket, key);
} public virtual async Task OnDisconnected(WebSocket socket)
{
Console.WriteLine("Socket 断开了");
await WebSocketConnectionManager.RemoveSocket(WebSocketConnectionManager.GetId(socket));
} public async Task SendMessageAsync(WebSocket socket, string message)
{
if (socket.State != WebSocketState.Open)
return;
var bytes = Encoding.UTF8.GetBytes(message);
await socket.SendAsync(buffer: new ArraySegment<byte>(array: bytes, offset: , count: bytes.Length), messageType: WebSocketMessageType.Text, endOfMessage: true, cancellationToken: CancellationToken.None);
} public async Task SendMessageAsync(string socketId, string message)
{
try
{
await SendMessageAsync(WebSocketConnectionManager.GetSocketById(socketId), message);
}
catch (Exception)
{ } } public async Task SendMessageToAllAsync(string message)
{
foreach (var pair in WebSocketConnectionManager.GetAll())
{
if (pair.Value.State == WebSocketState.Open)
await SendMessageAsync(pair.Value, message);
}
}
/// <summary>
/// 获取一些连接
/// </summary>
/// <param name="keys"></param>
/// <returns></returns>
public IEnumerable<WebSocket> GetSomeWebsocket(string[] keys)
{
foreach (var key in keys)
{
yield return WebSocketConnectionManager.GetWebSocket(key);
}
} /// <summary>
/// 给一堆人发消息
/// </summary>
/// <param name="webSockets"></param>
/// <param name="message"></param>
/// <returns></returns>
public async Task SendMessageToSome(WebSocket[] webSockets, string message)
{
webSockets.ToList().ForEach(async a => { await SendMessageAsync(a, message); });
} public abstract Task ReceiveAsync(WebSocket socket, WebSocketReceiveResult result, byte[] buffer);
}
需要把一些必须要重写的方法定义为abstract 给子类重写
使用我们写好的websocket管理中间件
public void ConfigureServices(IServiceCollection services)
{
services.AddWebSocketManager();
}
var webSocketOptions = new WebSocketOptions()
{
KeepAliveInterval = TimeSpan.FromSeconds(),
ReceiveBufferSize = *
};
app.UseWebSockets(webSocketOptions);
app.MapWebSocketManager("/zhajinhua", serviceProvider.GetService<ZjhGame>());
ZjhGame这个类,必须实现 WebSocketHandler,这样我们就能在ZjhGame这个类,处理游戏逻辑
好了,大致就是这样的,毕竟我也没有开发游戏的经验,有错误的地方,希望大佬们能指出
还有上一篇博客有大佬说加注,但是我没有收到加注的钱啊,你们到底加不加啊?不加我可要反悔了啊
http://www.cnblogs.com/boxrice/p/8570730.html
如果要请我喝水,欢迎打赏哈,有钱的捧个钱场,没钱的捧个人场。打赏点赞吐槽都可以
网络游戏开发-服务器(01)Asp.Net Core中的websocket,并封装一个简单的中间件的更多相关文章
-
ASP.NET Core 中的SEO优化(2):中间件中渲染Razor视图
前言 上一篇文章<ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存>中介绍了中间件的使用方法.以及使用中间件实现服务端静态化缓存的功能.本系列文章的这些技巧都是我 ...
-
ASP.NET Core 中的 WebSocket 支持(转自MSDN)
本文介绍 ASP.NET Core 中 WebSocket 的入门方法. WebSocket (RFC 6455) 是一个协议,支持通过 TCP 连接建立持久的双向信道. 它用于从快速实时通信中获益的 ...
-
ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存
分享 最近在公司成功落地了一个用ASP.NET Core 开发前台的CMS项目,虽然对于表层的开发是兼容MVC5的,但是作为爱好者当然要用尽量多的ASP.NET Core新功能了. 背景 在项目开发的 ...
-
【Blazor】在ASP.NET Core中使用Blazor组件 - 创建一个音乐播放器
前言 Blazor正式版的发布已经有一段时间了,.NET社区的各路高手也创建了一个又一个的Blazor组件库,其中就包括了我和其他小伙伴一起参与的AntDesign组件库,于上周终于发布了第一个版本0 ...
-
【ABP开发】:asp.net core 中使用mysql
EntityFrameworkCore项目--Nuget包管理,卸载包: Microsoft.EntityFrameworkCore.SqlServer: EntityFrameworkCore项目和 ...
-
Redis系列文章总结:ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁
引言:最近回头看了看开发的.Net Core 2.1项目的复盘总结,其中在多处用到Redis实现的分布式锁,虽然在OnResultExecuting方法中做了防止死锁的处理,但在某些场景下还是会发生死 ...
-
理解ASP.NET Core 中的WebSocket
在本文中,我们将详细介绍RFC 6455 WebSocket规范,并配置一个通用的.NET 5应用程序通过WebSocket连接与SignalR通信. 我们将深入底层的概念,以理解底层发生了什么. 关 ...
-
玩转ASP.NET Core中的日志组件
简介 日志组件,作为程序员使用频率最高的组件,给程序员开发调试程序提供了必要的信息.ASP.NET Core中内置了一个通用日志接口ILogger,并实现了多种内置的日志提供器,例如 Console ...
-
ASP.NET Core 中 HttpContext 详解与使用 | Microsoft.AspNetCore.Http 详解 (转载)
“传导体” HttpContext 要理解 HttpContext 是干嘛的,首先,看图 图一 内网访问程序 图二 反向代理访问程序 ASP.NET Core 程序中,Kestrel 是一个基于 li ...
随机推荐
-
websql的添加和查询
openDatabase 我们可以使用这样简单的一条语句,创建或打开一个本地的数据库对象 var db = openDatabase('testDB', '1.0', 'Test DB', 2 * 1 ...
-
MVC列表页通过CheckBox进行批量选择删除
1.Html代码,将所有CheckBox包含于删除表单,并且所有列表项的CheckBox使用相同的Name,并且Value设置为数据项主键ID @using (Html.BeginForm(" ...
-
A + B Problem,hdu-1000
A + B Problem Problem Description Calculate A + B. Input Each line will contain two integers A and ...
-
深入浅出—JAVA(6)
6.认识JAVA的API Arraylist的操作
-
C++中的set
总结一下: vector:封装了数组 list:封装了列表 map,set:封装了二叉树 set:用来存储同一类型的数据类型 非关联容器相对关联型容器插入效率高,原因是:不需要内存拷贝和内存移动, ...
-
MyEclipse 10 下在线安装插件
昨天不知道怎么就删除了电脑中的eclipse 我x,还原不回来了. 今天就安装了最新版本的myeclipse10,大家都知道,MyEclipse 中有一个烦人的 Software and Worksp ...
-
UnicodeDecodeError: &#39;gbk&#39; codec can&#39;t decode byte 0xae in position 120: illegal multibyte sequence
UnicodeDecodeError: 'gbk' codec can't decode byte 0xae in position 120: illegal multibyte sequence f ...
-
Android 性能监控系列一(原理篇)
欢迎关注微信公众号:BaronTalk,获取更多精彩好文! 一. 前言 性能问题是导致 App 用户流失的罪魁祸首之一,如果用户在使用我们 App 的时候遇到诸如页面卡顿.响应速度慢.发热严重.流量电 ...
-
【Select2】带搜索框的下拉框美化插件
1 引入js css 文件 <script src="js/jquery-1.11.1.min.js"></script> <script src= ...
-
SQL Server修改表结构后批量更新所有视图
最近修改了数据库表结构,数据同步的时候出了问题,发现很多数据明明已经修改,但是通过视图筛选出来的还是原来的数据,所以怀疑应该是视图缓存了数据,在园子里找到下面的博文,在这里做个记录备忘. 原文链接:h ...