我一直觉得学习的最好方法就是先让程序能够正常运行,才去学习他的原理,剖析他的细节.
就好像这个图:
所以,我们先跟着官方文档,创建一个 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 ,不会发送堆栈信息. 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"); });
好困,明天继续