C# SignalR使用

发布时间 2023-09-14 21:14:27作者: 说不出来

SignalR简介

SignalR是一个开源的库,跨平台;让Web应用与其他应用通讯变得很简单,Web服务端可以实时的将内容推送给对应的客户端,客户端发送的信息也可以实时到其他客户端。

SignalR提供了一种远程过程调用(RPC)的方式,使得客户端可以调用服务器的方法,同样在服务器端的方法中也能调用客户端的方法。

SignalR的通信方式

SignalR支持如下的方式实现实时通信:

  • WebSockets:是一种在单个TCP连接上进行全双工通信的协议,使得服务器和浏览器的通信更加简单,服务端可以主动发送信息。
  • Server-Sent Events:SSE 与 WebSocket 作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。WebSocket是双向的,而SSE是单向的。
  • Long Polling(长轮询):和传统的轮询原理一样,只是服务端不会每次都返回响应信息,只有有数据或超时了才会返回,从而减少了请求次数。

SignalR会依照下列顺序来判定使用那种传输方式,当然也可以手动指定:

  • 1.如果浏览器是 Internet Explorer8 或更早版本,则使用长轮询。
  • 2.如果配置了 JSONP(即连接启动时 jsonp 参数设置为 true),则使用长轮询。
  • 3.如果要建立跨域连接(即 SignalR 终结点和宿主页不在相同的域中),并且满足以下条件,则会使用 WebSocket:
    • 3.1客户端支持 CORS(跨域资源共享)
    • 3.2客户端支持 WebSocket
    • 3.3服务器支持 WebSocket
    • 如果这些条件中的任何一条不满足,将使用长轮询.
  • 4.如果未配置 JSONP 并且连接没有跨域,只要客户端和服务器都支持的话,将使用 WebSocket。
  • 5.如果客户端或服务器不支持 WebSocket,则尽量使用服务器发送事件。Forever Frame。
  • 7.如果 Forever Frame 失败,则使用长轮询。

案例演示

SignalR采用名为中心的处理方法去处理服务器和客户端之间的数据传输

Hub 是一种高级管道,允许客户端和服务器相互调用方法。 SignalR 自动处理跨计算机边界的调度,并允许客户端调用服务器上的方法,反之亦然。可以将强类型参数传递给方法,从而支持模型绑定。 SignalR 提供两种内置中心协议:基于 JSON 的文本协议和基于MessagePack的二进制协议。 与 JSON 相比,MessagePack 通常会创建更小的消息。 旧版浏览器必须支持XHR 级别 2才能提供 MessagePack 协议支持。

中心通过发送包含客户端方法的名称和参数的消息来调用客户端代码。 作为方法参数发送的对象使用配置的协议进行反序列化。 客户端尝试将名称与客户端代码中的方法匹配。 当客户端找到匹配项时,它会调用该方法并将反序列化的参数数据传递给它。

  1. 服务器触发所有连接的客户端上定义的相对应的接收数据的方法
  2. 客户端需要发送消息给其他客户端时,调用服务器中心上的方法,让服务器去触发所有的客户端执行对应的方法

创建一个ASP.NET Core Web项目

1.ASP.NET Core项目下已经默认安装了包Microsoft.AspNetCore.SignalR

2.配置 SignalR,配置Program.cs下的文件配置,新增SignalR的依赖注入和配置终结点

using Microsoft.AspNetCore.Cors.Infrastructure;
using SignalRChat.Hubs;

namespace SignalRChat
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddRazorPages();
            builder.Services.AddSignalR();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (!app.Environment.IsDevelopment())
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.MapHub<ChatHub>("/chathub");
            app.UseRouting();

            app.UseAuthorization();

            app.MapRazorPages();

            app.Run();
        }
    }
}

3.创建 SignalR 中心
中心是一个类,用作处理客户端 - 服务器通信的高级管道。
在 SignalRChat 项目文件夹中,创建Hubs文件夹。
Hubs文件夹中,使用以下代码创建ChatHub类:

using Microsoft.AspNetCore.SignalR;

namespace SignalRChat.Hubs
{
    public class ChatHub : Hub
    {
        public async Task SendMessage(string user, string message)
        {
            //触发所有客户端定义的"ReceiveMessage"方法
            await Clients.All.SendAsync("ReceiveMessage", user, message);
        }
    }
}

ChatHub类继承自 SignalR下的Hub
Hub类管理连接、组和消息。可通过已连接客户端调用SendMessage,以向所有客户端发送消息。 本教程后面部分将显示调用该方法的 JavaScript 客户端代码。 SignalR 代码是异步模式,可提供最大的可伸缩性。

注意下各个地方下的"/chathub"的命名,Programs.cs和chat.js和ChatHub.cs

创建JavaScript客户端

1.在ASP.NET Core项目下添加客户端库
在“解决方案资源管理器”>中,右键单击项目,然后选择“添加”“客户端库”。

在“添加客户端库”对话框中:

  • 为“提供程序”选择“unpkg”
  • 对于“库”,请输入 @microsoft/signalr@latest
  • 选择“选择特定文件”,展开“dist/browser”文件夹,然后选择 signalr.jssignalr.min.js
  • 将“目标位置”设置为 wwwroot/js/signalr/
  • 选择“安装” 。

![“添加客户端库”对话框 - 选择库](

LibMan 创建 wwwroot/js/signalr 文件夹并将所选文件复制到该文件夹。

2.添加SignalR客户端代码
使用以下代码替换Pages/Index.cshtml中的内容:

@page
<div class="container">
    <div class="row p-1">
        <div class="col-1">User</div>
        <div class="col-5"><input type="text" id="userInput" /></div>
    </div>
    <div class="row p-1">
        <div class="col-1">Message</div>
        <div class="col-5"><input type="text" class="w-100" id="messageInput" /></div>
    </div>
    <div class="row p-1">
        <div class="col-6 text-end">
            <input type="button" id="sendButton" value="Send Message" />
        </div>
    </div>
    <div class="row p-1">
        <div class="col-6">
            <hr />
        </div>
    </div>
    <div class="row p-1">
        <div class="col-6">
            <ul id="messagesList"></ul>
        </div>
    </div>
</div>
<script src="~/js/signalr/dist/browser/signalr.js"></script>
<script src="~/js/chat.js"></script>

以上的标记:

  • 创建文本框和提交按钮。
  • 使用id="messagesList"创建一个列表,用于显示从 SignalR 中心接收的消息。
  • 包含对 SignalR 的脚本引用,并在下一步中创建chat.js应用代码。

wwwroot/js文件夹中,使用以下代码创建chat.js文件:

"use strict";

var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();

//Disable the send button until connection is established.
document.getElementById("sendButton").disabled = true;

connection.on("ReceiveMessage", function (user, message) {
    var li = document.createElement("li");
    document.getElementById("messagesList").appendChild(li);
    // We can assign user-supplied strings to an element's textContent because it
    // is not interpreted as markup. If you're assigning in any other way, you 
    // should be aware of possible script injection concerns.
    li.textContent = `${user} says ${message}`;
});

connection.start().then(function () {
    document.getElementById("sendButton").disabled = false;
}).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;
    connection.invoke("SendMessage", user, message).catch(function (err) {
        return console.error(err.toString());
    });
    event.preventDefault();
});

以上的 JavaScript:

  • 创建并启动连接。
  • 向“提交”按钮添加一个用于向中心发送消息的处理程序。
  • 向连接对象添加一个用于从中心接收消息并将其添加到列表的处理程序。

添加WinForm客户端

1.添加WinForm客户端,并设置对应窗体界面,分别用于发送消息和接收消息

2.添加Nuget包,添加客户端包Microsoft.AspNetCore.SignalR.Client

3.添加客户端连接代码
若要建立连接,请创建HubConnectionBuilder并调用Build。 在建立连接期间,可以配置中心 URL、协议、传输类型、日志级别、标头和其他选项。 可通过将任何HubConnectionBuilder方法插入Build中来配置任何必需选项。 使用StartAsync启动连接。

InvokeAsync会对中心调用方法。 将中心方法中定义的中心方法名称和所有参数传递给InvokeAsync。 SignalR 是异步的,因此在进行调用时请使用asyncawait

InvokeAsync方法会返回一个在服务器方法返回时完成的Task。 返回值(如果有)作为Task的结果提供。 服务器上的方法所引发的任何异常都会产生出错的Task。 使用await语法等待服务器方法完成,并使用try...catch语法处理错误。

SendAsync方法会返回一个在消息已发送到服务器时完成的Task。 不会提供返回值,因为此Task不会等到服务器方法完成。 发送消息期间在客户端上引发的任何异常都会产生出错的Task。 使用awaittry...catch语法处理发送错误。

using Microsoft.AspNetCore.SignalR.Client;
using System.Net;

namespace SingalRWinForm
{
    public partial class Form1 : Form
    {
        private HubConnection _conn;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //建立SignalR连接
            string url = "https://localhost:7249/ChatHub";
            _conn = new HubConnectionBuilder()
                .WithUrl(url)
                .Build();

            _conn.StartAsync();
            //SignalR客户端定义ReceiveMessage方法,中心调用客户端方法
            _conn.On<string, string>("ReceiveMessage", RecvMsg);//接收错误日志

            //断开重连方法
            _conn.Closed += async (error) =>
            {
                await Task.Delay(new Random().Next(0, 5) * 1000);
                await _conn.StartAsync();
            };
        }

        private void RecvMsg(string identifiy, string errMessage)
        {
            this.BeginInvoke(new Action(() =>
            {
                rthMessage.Text += $"{identifiy}:{errMessage}" + Environment.NewLine;
            }));
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            //触发SignalR客户端的发送方法
,客户端调用中心方法,此处的方法名称"SendMessage"应和服务器上的方法名称保持一致
            _conn.InvokeAsync("SendMessage", this.txtMsg1.Text, this.txtMsg2.Text);
        }
    }
}

执行结果


参考如下:

SignalR源码(github.com)
ASP.NET Core SignalR 入门 | Microsoft Learn
ASP.NET Core SignalR .NET 客户端 | Microsoft Learn
SignalR 与 ASP.NET Core SignalR 的区别 | Microsoft Learn