文章目录
- [1. SignalR简介](#1. SignalR简介)
-
- [1.1 SignalR的三种通信方式](#1.1 SignalR的三种通信方式)
-
- WebSockets
- [Server-Sent Events(sse)](#Server-Sent Events(sse))
- [**Long Polling(长轮询)**](#Long Polling(长轮询))
- [1.2 SignalR的应用场景](#1.2 SignalR的应用场景)
- [1.3 SignalR支持的功能](#1.3 SignalR支持的功能)
- [1.4 SignalR服务器与客户端标准](#1.4 SignalR服务器与客户端标准)
- [1.5 SignalR 的核心价值](#1.5 SignalR 的核心价值)
- [1.6 SignalR 的发展历程](#1.6 SignalR 的发展历程)
- [1.7 SignalR 的架构组成](#1.7 SignalR 的架构组成)
-
- [1.7.1 RPC](#1.7.1 RPC)
- [1.7.2 Hub](#1.7.2 Hub)
前言
大多数传统的web应用是这样的:客户端发起http请求到服务端,服务端返回对应的结果。像这样:

也就是说,传统的web应用都是客户端主动 发起请求到服务端。
那么实时web应用呢?它不需要主动发起请求,服务端可以主动推送信息到客户端。

如今的应用程序常常需要与用户进行实时通信,无论是发送即时通知、提供实时聊天功能,还是动态更新仪表盘都是如此。SignalR是一个.NET库 ,它通过支持服务器 与客户端之间的双向通信,简化了实时应用程序的开发。
对于B/S模式的项目,基础的场景都是客户端发起请求,服务端返回响应结果就结束了一次连接;但在很多实际应用场景中,这种简单的请求和响应模式就显得很吃力,比如消息通知、监控看板信息自动刷新等实时通信场景 ,轮询 或WebSocket的方式来搞定,可是单纯用轮询的方式有点耗资源,只用WebSocket的方式又有些浏览器或其他客户端不支持,这个时候SignalR就该站出来了,封装的很给力,直接使用就行。

1. SignalR简介
SignalR是一个开源的库,跨平台;让Web应用与其他应用通讯变得很简单,Web服务端可以实时的将内容推送给对应的客户端,客户端发送的信息也可以实时到其他客户端。
SignalR提供了一种远程过程调用(RPC)的方式,使得客户端可以调用服务器的方法,同样在服务器端的方法中也能调用客户端的方法。
1.1 SignalR的三种通信方式
WebSockets
先后选择顺序
是一种在单个TCP连接上进行全双工通信 的协议,使得服务器 和浏览器的通信更加简单,服务端可以主动发送信息。
web socket允许客户端和服务端同时向对方发送消息(也就是双工通信),而且不限制信息类型。虽然浏览器同样有连接数量限制(可能是50个),但比sse强得多。理论上最优先使用
Server-Sent Events(sse)
自动选择服务器
SSE 与 WebSocket 作用相似,都是建立浏览器 与服务器之间的通信渠道,然后服务器向浏览器推送信息。WebSocket是双向的,而SSE是单向的。
服务器就拥有了向客户端推送的能力,这些信息和流信息差不多,期间会保持连接。
这种方式优点还是简单,也支持自动重连,综合来讲比long polling好用。缺点也很明显,不支持旧的浏览器不说,还只能发送本文信息,而且浏览器对sse还有连接数量的限制(6个)。
Long Polling(长轮询)
客户端能力范围内的最佳传输方法
和传统的轮询原理一样,只是服务端不会每次都返回响应信息,只有有数据或超时了才会返回,从而减少了请求次数。长轮询是客户端发起请求到服务端,服务器有数据就会直接返回。如果没有数据就保持连接并且等待,一直到有新的数据返回。如果请求保持到一段时间仍然没有返回,这时候就会超时,然后客户端再次发起请求。
SignalR会自动选择服务器和客户端能力范围内的最佳通信方式,当然也可以手动指定。
1.2 SignalR的应用场景
对于Web模式下的实时通信
服务端主动推送信息;比如发送公告场景;监控或看板数据实时显示;比如监控系统实时展示分布到各个客户端上的数据;服务端和客户端交互;比如客服系统的聊天场景。
- 需要从服务器进行高频率更新的应用。 示例包括游戏、社交网络、投票、拍卖、地图和 GPS 应用。
- 仪表板和监视应用。 示例包括公司仪表板、即时销售更新或旅行警报。
- 协作应用。 协作应用的示例包括白板应用和团队会议软件。
- 需要通知的应用。 社交网络、电子邮件、聊天、游戏、旅行警报和很多其他应用都需使用通知。
1.3 SignalR支持的功能
- 自动处理连接管理。
- 同时向所有连接的客户端发送消息。 例如聊天室。
- 向特定客户端或客户端组发送消息。
- 对其进行缩放,以处理不断增加的流量。
- SignalR中心协议(SignalR 使用 Hubs(中心协议) 在客户端和服务器之间进行通信;内置中心协议:基于JSON的文本协议 和 基于MessagePack 的二进制协议(JSON的代替方案,消息更小), 旧版浏览器必须支持XHR级别2 才能提供 MessagePack 协议支持。)
1.4 SignalR服务器与客户端标准
- 服务器标准
支持 ASP.NET Core 支持的任何服务器平台;如果服务器运行 IIS中,则 WebSockets 传输需要 Windows Server 2012 或更高版本上的 IIS 8.0 或更高版本。
- Web客户端标准
SignalR 面向 ES6。 对于不支持 ES6 的浏览器,请将库转译为 ES5。 有关详细信息,请参阅使用ES6入门 -- 使用 Traceur和Babel 将 ES6转为ES5。
- .Net客户端标准
.NET 客户端可在 ASP.NET Core 支持的任何平台上运行(Xamarin.Android版本>8.4.0.1;Xamarin.iOS版本>11.14.0.4)。
1.5 SignalR 的核心价值
SignalR 为开发者提供了三大核心价值:
- 抽象化传输层:自动选择客户端和服务器之间最佳可用传输方法(WebSocket、Server-Sent Events 或长轮询),无需开发者关心底层实现。
- 连接管理:自动处理连接、断开连接和重新连接的复杂逻辑,提供稳定的通信通道。
- 简化的 API:通过 Hub 模式提供简单易用的高级 API,使开发者可以像调用本地方法一样进行远程调用。
1.6 SignalR 的发展历程
2013年:SignalR 1.0 发布,作为 ASP.NET 的扩展
2018年:SignalR for ASP.NET Core 2.1 发布,完全重写
2020年:SignalR 成为 .NET 5 的核心组件
2023年:.NET 8 中的 SignalR 性能提升 40%
1.7 SignalR 的架构组成
SignalR 由以下几个关键组件构成:
- Hubs:高级管道,允许客户端和服务器直接相互调用方法
- Persistent Connections:低级管道,用于需要更精细控制的场景
- 传输层:自动处理 WebSocket、Server-Sent Events 和长轮询
- 横向扩展支持:通过 Redis、Azure SignalR 等服务支持大规模部署
1.7.1 RPC
全程Remote Procedure Call,字面意思远程服务调用,可以像调用本地方法一样调用远程服务。前端可以调用后端方法,后端也可以调用前端方法。
1.7.2 Hub
基于RPC,接受从客户端发过来的消息,也同时负责把服务端的消息发送给客户端。客户端可以调用Hub里面的方法,服务端可以通过Hub调用客户端里面的方法。
Hub 是一种高级管道,允许客户端和服务器相互调用方法。 SignalR 自动处理跨计算机边界的调度,并允许客户端调用服务器上的方法,反之亦然。 可以将强类型参数传递给方法,从而支持模型绑定。 SignalR 提供两种内置中心协议:基于 JSON 的文本协议和基于 MessagePack 的二进制协议。 与 JSON 相比,MessagePack 通常会创建更小的消息。 旧版浏览器必须支持 XHR 级别 2 才能提供 MessagePack 协议支持。
中心通过发送包含客户端方法的名称和参数的消息来调用客户端代码。 作为方法参数发送的对象使用配置的协议进行反序列化。 客户端尝试将名称与客户端代码中的方法匹配。 当客户端找到匹配项时,它会调用该方法并将反序列化的参数数据传递给它。
注意
- 客户端只能调用集线器类中定义的public的方法。
- 每次客户端访问服务器的集线器类的时候都是在新的对象实例上运行的,所以不能将状态存储在集线器类中。
- SignalR的代码是异步模式的,所以集线器中的方法也都是异步的。
1.7.2.1.Hub对象
1.Context
Hub类具有一个上下文Context,包含连接中的以下属性以及方法。
| 属性 | 描述 |
|---|---|
| ConnectionId | 获取由 SignalR 分配的连接的唯一 ID。每个连接都有一个连接 ID |
| UserIdentifier | 获取用户标识符。 默认情况下,SignalR 使用ClaimTypes.NameIdentifier与连接ClaimsPrincipal关联的作为用户标识符。 |
| User | 获取与ClaimsPrincipal当前用户关联的。 |
| Items | 获取可用于在此连接的范围内共享数据的键/值集合。数据可以存储在此集合中,它将在不同的集线器方法调用中持久保存。 |
| Features | 获取连接上的可用功能的集合。目前,在大多数情况下不需要此集合,因此不会对其进行详细介绍。 |
| ConnectionAborted | 获取一个CancellationToken,它将在连接中止时通知。 |
| 方法 | 描述 |
| GetHttpContext | 返回连接HttpContext的null,如果连接不与 HTTP 请求关联,则为。对于 HTTP 连接,可以使用此方法来获取 HTTP 标头和查询字符串等信息。 |
| Abort | 中止连接。 |
2.Clients
Hub类的Clients属性包含服务器和客户端之间通信的方法和属性。
| 属性 | 描述 |
|---|---|
| All | 在所有连接的客户端上调用方法 |
| Caller | 在调用集线器方法的客户端上调用方法 |
| Others | 在所有连接的客户端上调用方法,但调用方法的客户端除外 |
| 方法 | 描述 |
| AllExcept | 在所有连接的客户端(指定的连接除外)上调用方法 |
| Client | 在特定连接的客户端上调用方法 |
| Clients | 在多个特定连接的客户端上调用方法 |
| Group | 对指定组中的所有连接调用方法 |
| GroupExcept | 对指定组中的所有连接调用方法,指定的连接除外 |
| Groups | 在多组连接上调用方法 |
| OthersInGroup | 对一组连接调用方法,而不包括调用该集线器方法的客户端 |
| User | 对与特定用户关联的所有连接调用方法 |
| Users | 对与指定用户相关联的所有连接调用方法 |
表中的每个属性或方法都返回一个SendAsync包含方法的对象。 SendAsync方法允许你提供要调用的客户端方法的名称和参数。
1.7.2.2.强类型的hub
使用SendAsync的时候需要传入字符串来指定调用客户端的方法,这就会导致拼写错误等问题引发程序运行错误。
通过使用强类型Hub将客户端的方法约定为接口,这样Hub的Clients的将会禁用Sendasync方法,而只能调用我们约定的接口方法。
csharp
public interface IHub
{
Task ReceiveMessage(string user, string message);
Task ReceiveMessage(string message);
}
public class BaseHub : Hub<IHub>
{
public async Task SendMessage(string user, string message)
{
await Clients.All.ReceiveMessage(user, message);
}
}
1.7.2.3.处理连接事件
SignalR的hub提供OnConnectedAsync和OnDisconnectedAsync虚拟方法来管理和跟踪连接。 重写OnConnectedAsync虚拟方法,以便在客户端连接到集线器时执行操作,如将其添加到组。
csharp
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}
重写OnDisconnectedAsync虚拟方法,以便在客户端断开连接时执行操作。 如果客户端故意断开连接(例如connection.stop(),通过调用),则exception参数将为null。 但是,如果客户端由于错误(例如网络故障)而断开连接,则exception参数将包含描述失败的异常。
csharp
public override async Task OnDisconnectedAsync(Exception exception)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnDisconnectedAsync(exception);
}
服务器主动推和客户端主动推两种情况,基本上可以满足大多数实时需求。通信流程图如下:
- 服务端推送消息

- Js客户端点击发布按钮调用API接口;
- 接口内部将信息交给SignalR处理;
- 获取所有客户端,并通过远程调用客户端方法的方式将信息传递给客户端,最后信息就可以实时展示了。
- 客户端上报数据

- 在窗体客户端中点击按钮,内部调用服务端的UpdateDataServer方法;
- 服务端被调用之后,内部获取所有客户端,并调用客户端中updatedata方法;最后在客户端将信息展示。