SignalR的介绍
WebSocket
在传统的HTTP中,只能客户端主动向服务器端发起请求,服务器端无法主动向客户端发送消息。有的业务场景下,我们需要服务器端主动向客户端发送消息,比如Web聊天室、OA系统、站内消息等。
为了实现服务器端向客户端推送消息,在2008年诞生了WebSocket协议,并且该协议在2011年成为国际标准。目前所有的主流浏览器都已经支持WebSocket协议。WebSocket基于TCP(transmission control protocol,传输控制协议),支持二进制通信,因此通信效率非常高,它可以让服务器处理大量的并发WebSocket连接;WebSocket是双工通信,因此服务器可以高效地向客户端推送消息。
SignalR
ASP.NET Core SignalR(以下简称SignalR)是.NET Core平台中对WebSocket的封装,从而让开发人员可以更简单地进行WebSocket开发。
虽然WebSocket是独立于HTTP的,但是我们一般仍然把WebSocket服务器端部署到Web服务器上,因为我们需要借助HTTP完成初始的握手,并且共享HTTP服务器的端口,这样就可以避免为WebSocket单独打开新的服务器端口。因此,SignalR的服务器端一般运行在ASP.NET Core项目中。
SignalR中一个重要的组件是集线器(hub),它用于在WebSocket服务器端和所有客户端之间进行数据交换,所有连接到同一个集线器上的程序都可以互相通信。我们既可以通过集线器来完成服务器端向客户端的消息推送,也可以完成客户端之间的消息推送,当然WebSocket也允许客户端向服务器端发送消息。
使用SignalR的简单聊天室
下面通过开发一个简单的聊天室来了解SignalR的基本使用。
第1步:
创建一个ASP.NET Core Web API项目,安装Nuget包Microsoft.AspNetCore.SignalR.StackExchangeRedis
,并且在项目中创建一个继承自Hub类的ChatRoomHub类,所有的客户端和服务器端都通过这个集线器进行通信。
public class ChatRoomHub : Hub
{
public Task SendPublicMessage(string message)
{
string connId = this.Context.ConnectionId;
string msg = $"{connId}{DateTime.Now}:{message}";
return Clients.All.SendAsync("ReceivePublicMessage", msg);
}
}
ChatRoomHub类中定义的方法可以被客户端调用,也就是客户端可以向服务器端发送请求,方法的参数就是客户端向服务器端传送的消息,参数的个数原则上来讲不受限制,而且参数的类型支持string、bool、int等常用的数据类型。
在ChatRoomHub类中,我们定义了一个方法SendPublicMessage,方法的参数message为客户端传递过来的消息。在第5行代码中,我们获得了当前发送消息的客户端连接的唯一标识ConnectionId;在第6行代码中拼接出一个包含连接ID、当前时间、客户端消息的字符串;随后我们把msg字符串以名字为“ReceivePublicMessage”的消息发送到所有连接到集线器的客户端上。
第2步:
编辑Program.cs,在builder.Build之前调用builder.Services.AddSignalR
注册所有SignalR的服务,在app.MapControllers之前调用app.MapHub<ChatRoomHub>("/Hubs/ChatRoomHub")
启用SignalR中间件,并且设置当客户端通过SignalR请求“/Hubs/ChatRoomHub”这个路径的时候,由ChatRoomHub进行处理。
builder.Services.AddSignalR(); //注册所有SignalR的服务
string[] urls = new[] { "http://localhost:3000" };
builder.Services.AddCors(options =>
options.AddDefaultPolicy(builder =>
builder.WithOrigins(urls).AllowAnyMethod()
.AllowAnyHeader().AllowCredentials())
);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapHub<ChatRoomHub>("/Hubs/ChatRoomHub"); //启用SignalR中间件
app.MapControllers();
第3步:
我们需要编写一个静态HTML页面提供交互界面。按照前后端分离的理念,我们应该把HTML页面放到一个单独的前端项目中。创建一个前端项目,然后执行如下命令安装SignalR的JavaScript客户端SDK(software development kit,软件开发工具包):npm install @microsoft/signalr。
在SignalR的JavaScript客户端中:
- 使用
HubConnectionBuilder
来创建从客户端到服务器端的连接; - 通过
withUrl
方法来设置服务器端集线器的地址,该地址必须是包含域名等的全路径,必须和在服务器端MapHub设置的路径一致; - 通过
withAutomaticReconnect
设置自动重连机制。虽然withAutomaticReconnect不是必须设置的,但是设置这个选项之后,如果连接被断开,客户端就会尝试重连,因此使用起来更方便。需要注意的是,客户端重连之后,由于这是一个新的连接,因此在服务器端获得的ConnectionId是一个新的值。 - 对HubConnectionBuilder设置完成后,我们调用
build
就可以构建完成一个客户端到集线器的连接。 - 我们通过build获得的到集线器的连接只是逻辑上的连接,还需要调用start方法来实际启动连接。
- 一旦连接建立完成,我们就可以通过连接对象的invoke函数来调用集线器中的方法,我们也可以通过
on
函数来注册监听服务器端使用SendAsync
发送的消息的代码。
<template>
<input
type="text"
v-model="state.userMessage"
v-on:keypress="txtMsgOnkeypress"
/>
<div>
<ul>
<li v-for="(msg, index) in state.messages" :key="index">{{ msg }}</li>
</ul>
</div>
</template>
<script>
import { reactive, onMounted } from "vue";
import * as signalR from "@microsoft/signalr";
let connection;
export default {
name: "Login",
setup() {
const state = reactive({ userMessage: "", messages: [] });
const txtMsgOnkeypress = async function (e) {
if (e.keyCode != 13) return;
await connection.invoke("SendPublicMessage", state.userMessage);
state.userMessage = "";
};
onMounted(async function () {
connection = new signalR.HubConnectionBuilder() // 创建从客户端到服务器端的连接
.withUrl("https://localhost:7002/Hubs/ChatRoomHub") // 设置服务器端集线器的地址
.withAutomaticReconnect() // 设置自动重连机制
.build(); // 构建完成
await connection.start(); // 启动
// 通过on函数来注册监听服务器端使用SendAsync发送的消息的代码
connection.on("ReceivePublicMessage", (msg) => {
state.messages.push(msg);
});
});
return { state, txtMsgOnkeypress };
},
};
</script>
可以看到,在onMounted方法中,我们创建并且启动了客户端到服务器端集线器的连接,并且监听了服务器端向客户端发送的“ReceivePublicMessage”消息,客户端还会把收到的消息添加到页面上。
在23到27行中,对用户在输入框内的按键进行监听,当用户按Enter键的时候,我们就调用集线器中的SendPublicMessage方法把用户输入的消息发送给服务器端,服务器端再把消息转发给连接到这个集线器的全部客户端。这样我们就实现了一个简单的聊天室。
最后:
启动ASP.NET Core项目和前端项目,然后打开两个聊天室页面,并分别在两个页面中发送一些消息。我们可以发现,在A页面中发送的消息,在B页面中能立即看到;在B页面中发送的消息,在A页面中也能立即看到。
本文学习参考自:ASP.NET Core技术内幕与项目实战