SignalR:基本使用

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也允许客户端向服务器端发送消息。
image

使用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客户端中:

  1. 使用HubConnectionBuilder来创建从客户端到服务器端的连接;
  2. 通过withUrl方法来设置服务器端集线器的地址,该地址必须是包含域名等的全路径,必须和在服务器端MapHub设置的路径一致;
  3. 通过withAutomaticReconnect设置自动重连机制。虽然withAutomaticReconnect不是必须设置的,但是设置这个选项之后,如果连接被断开,客户端就会尝试重连,因此使用起来更方便。需要注意的是,客户端重连之后,由于这是一个新的连接,因此在服务器端获得的ConnectionId是一个新的值。
  4. 对HubConnectionBuilder设置完成后,我们调用build就可以构建完成一个客户端到集线器的连接。
  5. 我们通过build获得的到集线器的连接只是逻辑上的连接,还需要调用start方法来实际启动连接。
  6. 一旦连接建立完成,我们就可以通过连接对象的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技术内幕与项目实战

posted @ 2022-09-27 21:25  一纸年华  阅读(3003)  评论(0编辑  收藏  举报