SignalR服务器端消息推送 — 基本使用

我们知道,在传统的HTTP中,只能客户端主动向服务器端发起请求,服务器端无法主动向客户端发送消息。

有的业务场景下,我们需要服务器端主动向客户端发送消息。

比如 Web聊天室就需要服务器端主动向客户端发送新收到的消息,OA系统就需要服务器端主动向客户端发送请求申请审批结果,站内消息系统就需要服务器端主动通知客户端”有新消息”。

我们可以用长轮询(long polling)来实现这样的功能,也就算浏览器端先向服务器端发送AJAX请求。

但是服务器端不立即给浏览器端发送响应,而是一直挂起这个请求。

直到服务器端有需要推送给客户端的消息,服务器端才把要推送的消息作为响应发送给浏览器端。

由于HTTP并不是为这种长轮询机制设计的,因此长轮询对服务器的资源消耗非常大;

而且由于HTTP是文本传输协议,因此数据传输效率低。

为了实现服务器端向客户端推送消息,在2008年诞生了WebSocket协议,并且该协议在2011年成为国际标准。

目前所有的主流浏览器都已经支持WebSocket协议。

WebSocket基于TCP,支持二进制通信,因此通信效率非常高,它可以让服务器处理大量的并发WebSocket连接;

WebSocket是双工通信,因此服务器可以高效地向客户端推送消息。

ASP.NET Core SingnalR是.NET Core平台中对WebSocket的封装,从而让开发人员可以更简单地进行WebSocket开发。

1. SignalR基本使用

虽然WebSocket是独立于HTTP的,但是我们一般仍然把WebSocket服务器端部署到Web服务器上。

因为我们需要借助HTTP完成初始的握手,并且共享HTTP服务器的端口。

这样就可以避免为WebSocket单独打开新的服务器端口。

因此,SignalR的服务器端一般运行在ASP.NET Core项目中。

SignalR中一个重要的组件是集线器(hub),它用于在WebSocket服务器端和所有客户端之间进行数据交换,所有连接到同一个集线器上的程序都可以互相通信。

我们既可以通过集线器来完成服务器端向客户端的消息推送,也可以完成客户端之间的消息推送,当然WebSocket也允许客户端向服务器端发送消息。

下面通过开发一个简单的聊天室来了解SignalR的基本使用。

第一步:

创建一个WebAPI项目,并且在项目中创建一个继承自Hub类的ChatRoomHub类,所有的客户端和服务器端都通过这个集线器进行通信,如以下代码:

C#
  public class ChatRoomHub:Hub
  {


      public Task SendPublicMessage(string message)
      {
          //获取唯一标识
          string connId = this.Context.ConnectionId;

          string receivePublicMessage = $"{connId} {DateTime.Now}{message}";

          //发送消息
          return Clients.All.SendAsync("ReceivePublicMessage", receivePublicMessage);

      }

  }

ChatRoomHub类中定义的方法可以被客户端调用,也就是客户端可以向服务器端发送请求。

方法的参数就是客户端向服务器端传送的消息,参数的个数原则上来讲不受限制,而且参数的类型支持string、int等常用数据类型。

第二步:

编辑Program.cs,在bulider.Build之前调用builder.Services.AddSignalR注册所有SignalR的服务。

在app.MapControllers之前调用app.MapHub<ChatRoomHub>(“/Hubs/ChatRoomHub”)启用SignalR中间件,并且设置客户端请求此路径时,由ChatRoomHub处理。

修改后的Program.cs的主干内容如以下代码所示:

C#
var builder = WebApplication.CreateBuilder(args);


builder.Services.AddControllers();

builder.Services.AddEndpointsApiExplorer();

builder.Services.AddSwaggerGen();


//启用SignalR
builder.Services.AddSignalR();


//跨域
builder.Services.AddCors(policy =>
{
    policy.AddPolicy("CorsPolicy", opt => opt
    .WithOrigins("http://127.0.0.1:5173")
    .AllowAnyHeader()
    .AllowAnyMethod().AllowCredentials());
    
});






var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseCors();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapHub<ChatRoomHub>("/Hubs/ChatRoomHub");

app.MapControllers();

app.Run();

第三步:

我们需要编写一个静态HTML页面提供交互界面。

按照前后端分离的理念,应把HTML页面放到一个单独的前端项目中。

我们在这里使用Vue项目并按照软件开发工具包 microsoft/singnalr

在SignalR的JavaScript客户端中,我们使用HubConnectionBuilder来创建从客户端到服务器端的连接:

通过withUrl方法来设置服务器端集线器的地址,该地址必须是包含域名等的全路径,必须和服务器端MapHub设置的路径一致;

通过withAutomaticReconnect设置自动重连机制。

虽然withAutomaticReconnect不是必须设置的,但是设置这个选项之后,如果连接被断开,客户端就会尝试重连,使用更方便。

需要注意的是,客户端重连之后,由于这是一个新的连接,因此在服务器端获得的ConnectionId是一个新的值。

对HubConnectionBuilder设置完成后,我们调用build就可以构建完成一个客户端到集线器的连接。

我们通过build获得的到集线器的连接只是逻辑上的连接,还需要调用start方法来实际启动连接。

一旦连接建立完成,我们就可以通过连接对象的invoke函数来调用集线器中的方法,我们也可以通过on函数来注册监听服务器端使用SendAsync的代码。

Vue
<script setup>

import { reactive,onMounted } from 'vue';

import* as singnalR from '@microsoft/signalr'

let connection;

const state = reactive({userMessage:"",messages:[]});

//对用户在输入框内的按键进行监听
const txtMsgOnKeypress = async function name(e) 
{

  if(e.keyCode !=13) return;

  //把用户输入消息发送服务器端再转发给连接集线器的全部客户端
  await connection.invoke("SendPublicMessage",state.userMessage);

  state.userMessage = "";

}

onMounted(async (params) => {
  
  connection = new singnalR.HubConnectionBuilder()
  .withUrl('https://localhost:7115/Hubs/ChatRoomHub')
  .withAutomaticReconnect().build();

  await connection.start();

  connection.on('ReceivePublicMessage',msg=>{
     
      state.messages.push(msg);
     
  });

  connection.onclose(async () => {
    console.log('连接失败. Attempting to reconnect...');
    
});


  return {state,txtMsgOnKeypress};

})


</script>

<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>

<style scoped>

</style>

接下来启动,然后打开两个聊天室页面,并分别在两个页面中发送一些消息。

我们可以看到A页面发送的消息,在B页面中能立即看到,与之相对应的情况依然可以看到。

订阅评论
提醒
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
滚动至顶部