我一直觉得学习的最好方法就是先让程序能够正常运行,才去学习他的原理,剖析他的细节.
就好像这个图:
所以,我们先跟着官方文档,创建一个 SignalR 应用: https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/signalr?view=aspnetcore-2.2&tabs=visual-studio
这个例子一共涉及到下面几个步骤:
自定义中心 ChatHub ;
在启动类 Startup 中启用 SignalR 服务,并添加路由;
编写客户端JS
下载 SignalR 官方JS.
自定义中心 : ChatHub
public class ChatHub : Hub { public async Task SendMessage(string user, string message) { await Clients.All.SendAsync("ReceiveMessage", user, message); } }
"Hub" 一词,有的地方翻译成"集线器",有的地方翻译成"中心",对于我这种非科班出身的人来说,还是"中心"听起简单点.
对于基类 Hub ,xml 是这样说的 : A base class for a SignalR hub. SignalR 中心的基类.
这个很简单,看这些名字就知道他们是干嘛用的,具体的描述可以看官方文档.
中心的作用可以这样简单的描述:
通过中心,我们能在服务器的代码中定义客户端可以调用的方法(必须是 public);
通过中心,我们能在客户端的代码中定义服务器可以调用的方法.
上述代码,我们通过继承 Hub ,定义了一个自己的中心 : ChatHub (聊天中心) ,在这个类里面,我们做了下面两件事:
定义了客户端可以调用的方法 : SendMessage(string user, string message)
调用了所有连接上 Hub 的客户端的 ReceiveMessage 方法,并将 user,message 两个字符串作为入参传入该方法.
中心定义好了,肯定需要启用
在启动类 Startup 中启用 SignalR 服务,并添加路由
public void ConfigureServices(IServiceCollection services) { ...... //注册 SignalR 服务 services.AddSignalR(); }
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { ...... //设置 SignalR 中心路由 app.UseSignalR(routes => { routes.MapHub<ChatHub>("/chatHub"); }); app.UseMvc(); }
编写客户端JS
"use strict";//不太明白这句话是什么意思...//创建一个连接到我们创建的 ChatHub 的 connection.var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();//定义客户端的方法,方法名: ReceiveMessage ,两个入参.//注意,这个方法就是服务器要调用的方法,服务器和客户端的名字一定要一样.connection.on("ReceiveMessage", function (user, message) { var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); var encodedMsg = user + " says " + msg; var li = document.createElement("li"); li.textContent = encodedMsg; document.getElementById("messagesList").appendChild(li); });//开启连接.connection.start().catch(function (err) { return console.error(err.toString()); }); document.getElementById("sendButton").addEventListener("click", function (event) { var user = document.getElementById("userInput").value; var message = document.getElementById("messageInput").value; //调用服务器的 SendMessage 方法,并传入两个入参.这和委托的调用太像了. connection.invoke("SendMessage", user, message).catch(function (err) { return console.error(err.toString()); }); event.preventDefault(); });
上面的 ChatHub 类的 SendAsync 方法在调用客户端的方法时,方法名是直接写的 字符串 : "ReceiveMessage"
官方不推荐这样写,因为不是强类型,可能出现运行时错误,因此建议使用强类型的中心
(顺带附上了一些额外的功能):
//建议使用下面的强类型方式 //方法二 public interface IChatClient { //就算是这种强类型方式,客户端定义的方法名也必须和这个方法名一样,包括签名. Task ReceiveMessage(string user, string message); } public class StronglyTypedChatHub : Hub<IChatClient> { //[HubMethodName("hello")] 可以改名,如果改了名,前端也要跟着改,别忘了. public async Task SendMessage(string user, string message) { //调用客户端定义的 ReceiveMessage 方法. //throw new HubException("哈哈,出错了!");//可以向客户端发送异常.只会向当前调用的客户端发送,并且只发送 message ,不会发送堆栈信息. //Clients.Caller 当前客户端 //Clients.Others 当前客户端的其他客户端 await Clients.All.ReceiveMessage($"{GetHashCode()}" + user, message);//传递 hashCode 是为了证明,每次调用都是不同的实例.所以官方说不要在"中心"里面存状态. } //该方法可以在客户端连接上后,执行操作 public override async Task OnConnectedAsync() { await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users"); await base.OnConnectedAsync(); } //同理,当客户端断开连接时执行的操作 public override async Task OnDisconnectedAsync(Exception exception) { await Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users"); await base.OnDisconnectedAsync(exception); } }
当然,注册路由的代码也得换了 : app.UseSignalR(routes => { routes.MapHub<StronglyTypedChatHub>("/chatHub"); });
好困,明天继续
2019.1.9
复制一段官方的文档.
SignalR 是什么?
ASP.NET Core SignalR 是一个开源代码库,它简化了向应用添加实时 Web 功能的过程。 实时 Web 功能使服务器端代码能够即时将内容推送到客户端。
SignalR 的适用对象:
需要来自服务器的高频率更新的应用。 例如:游戏、社交网络、投票、拍卖、地图和 GPS 应用。
仪表板和监视应用。 示例包括公司仪表板、销售状态即时更新或行程警示。
协作应用。 协作应用的示例包括白板应用和团队会议软件。
需要通知的应用。 社交网络、电子邮件、聊天、游戏、行程警示以及许多其他应用都使用通知。
SignalR 提供了一个用于创建服务器到客户端远程过程调用(RPC)的 API。 RPC 通过服务器端 .NET Core 代码调用客户端上的 JavaScript 函数。
以下是 ASP.NET Core SignalR 的一些功能:
自动管理连接。
同时向所有连接的客户端发送消息。 例如,聊天室。
将消息发送到特定的客户端或客户端组。
扩展以处理增加的流量。
传输
SignalR 支持几种方法用于处理实时通信:
服务器发送事件
长轮询
SignalR 会从服务器和客户端支持的功能中自动选择最佳传输方法
中心
SignalR 使用中心在客户端和服务器之间进行通信。
“中心”是一种高级管道,允许客户端和服务器相互调用方法。 SignalR 自动处理跨计算机边界的调度,允许客户端和服务器相互调用方法。 可以将强类型参数传递给方法,从而启用模型绑定。 SignalR 提供两个内置中心协议:基于 JSON 的文本协议和基于 MessagePack 的二进制协议。 与 JSON 相比,MessagePack 创建的消息通常比较小。 旧版浏览器必须支持 XHR 2 才能提供 MessagePack 协议支持。
中心通过发送包含客户端方法的名称和参数的消息来调用客户端代码。 使用配置的协议对作为方法参数发送的对象进行反序列化。 客户端会尝试将方法名称与客户端代码中的方法匹配。 当客户端找到匹配项时,它会调用该方法并将反序列化的参数数据传递给它。
发送中心外的信息
上面讲的例子,客户端和服务器的消息都是在"中心"里面流动.实际上,中心还可以把中心之外的信息发送给客户端.(这句话可能有点绕),看示例就明白了.
新建一个测试控制器, 依然通过依赖注入得到 IHubContext 类的实例, StronglyTypedChatHub 类就是上面例子中的那个自定义的强类型中心.
[Route("api/[controller]")] [ApiController] public class TestController { private IHubContext<StronglyTypedChatHub, IChatClient> _context; public TestController(IHubContext<StronglyTypedChatHub, IChatClient> context) { _context = context; } public void Get() { _context.Clients.All.ReceiveMessage("admin", "在座的各位都是垃圾!"); } }
测试效果:
这个是强类型中心的例子,下面是普通中心的例子:
[Route("api/[controller]")] [ApiController] public class TestController { //强类型中心 //private IHubContext<StronglyTypedChatHub, IChatClient> _context; //public TestController(IHubContext<StronglyTypedChatHub, IChatClient> context) //{ // _context = context; //} //普通中心 private IHubContext<ChatHub> _context; public TestController(IHubContext<ChatHub> context) { _context = context; } public void Get() { //这种调用客户端方法的方式,好别扭 _context.Clients.All.SendAsync("ReceiveMessage", "admin", "在座的各位都是垃圾!!!"); } }