.NET使用SignalR實(shí)現(xiàn)實(shí)時(shí)通信的完全指南
1. SignalR 概述
SignalR 是一個(gè)為 ASP.NET 開(kāi)發(fā)者提供的開(kāi)源庫(kù),它極大地簡(jiǎn)化了向應(yīng)用程序添加實(shí)時(shí) Web 功能的過(guò)程。所謂"實(shí)時(shí) Web"功能,是指服務(wù)器代碼能夠即時(shí)將內(nèi)容推送到連接的客戶(hù)端,而不需要服務(wù)器等待客戶(hù)端請(qǐng)求新數(shù)據(jù)。
1.1 SignalR 的核心價(jià)值
SignalR 為開(kāi)發(fā)者提供了三大核心價(jià)值:
- 抽象化傳輸層:自動(dòng)選擇客戶(hù)端和服務(wù)器之間最佳可用傳輸方法(WebSocket、Server-Sent Events 或長(zhǎng)輪詢(xún)),無(wú)需開(kāi)發(fā)者關(guān)心底層實(shí)現(xiàn)。
- 連接管理:自動(dòng)處理連接、斷開(kāi)連接和重新連接的復(fù)雜邏輯,提供穩(wěn)定的通信通道。
- 簡(jiǎn)化的 API:通過(guò) Hub 模式提供簡(jiǎn)單易用的高級(jí) API,使開(kāi)發(fā)者可以像調(diào)用本地方法一樣進(jìn)行遠(yuǎn)程調(diào)用。
1.2 SignalR 的發(fā)展歷程
- 2013年:SignalR 1.0 發(fā)布,作為 ASP.NET 的擴(kuò)展
- 2018年:SignalR for ASP.NET Core 2.1 發(fā)布,完全重寫(xiě)
- 2020年:SignalR 成為 .NET 5 的核心組件
- 2023年:.NET 8 中的 SignalR 性能提升 40%
1.3 SignalR 的架構(gòu)組成
SignalR 由以下幾個(gè)關(guān)鍵組件構(gòu)成:
- Hubs:高級(jí)管道,允許客戶(hù)端和服務(wù)器直接相互調(diào)用方法
- Persistent Connections:低級(jí)管道,用于需要更精細(xì)控制的場(chǎng)景
- 傳輸層:自動(dòng)處理 WebSocket、Server-Sent Events 和長(zhǎng)輪詢(xún)
- 橫向擴(kuò)展支持:通過(guò) Redis、Azure SignalR 等服務(wù)支持大規(guī)模部署
2. SignalR 的核心功能
2.1 自動(dòng)傳輸選擇
SignalR 會(huì)自動(dòng)從以下幾種傳輸方式中選擇最佳方案:
- WebSocket:首選,提供全雙工通信
- Server-Sent Events (SSE):當(dāng) WebSocket 不可用時(shí)使用
- 長(zhǎng)輪詢(xún):作為最后的后備方案
// 示例:在 Startup.cs 中配置傳輸方式
services.AddSignalR(hubOptions => {
hubOptions.Transports = HttpTransportType.WebSockets |
HttpTransportType.ServerSentEvents;
hubOptions.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
});
2.2 Hub 模式
Hub 是 SignalR 的核心概念,它允許客戶(hù)端和服務(wù)器直接調(diào)用彼此的方法:
// 服務(wù)器端 Hub 示例
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
// 調(diào)用所有客戶(hù)端的 ReceiveMessage 方法
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
// 客戶(hù)端可以調(diào)用的方法
public Task JoinGroup(string groupName)
{
return Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
}
2.3 客戶(hù)端支持
SignalR 提供多種客戶(hù)端支持:
- JavaScript 客戶(hù)端:用于 Web 前端
- .NET 客戶(hù)端:用于 WPF、Xamarin 等應(yīng)用
- Java 客戶(hù)端:用于 Android 應(yīng)用
- C++ 客戶(hù)端:用于原生應(yīng)用
// JavaScript 客戶(hù)端示例
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.on("ReceiveMessage", (user, message) => {
console.log(`${user}: ${message}`);
});
async function start() {
try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
}3. SignalR 的詳細(xì)實(shí)現(xiàn)
3.1 服務(wù)器端配置
基本配置
// Startup.cs 中的 ConfigureServices 方法
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
// 配置 CORS(如果需要)
services.AddCors(options => {
options.AddPolicy("CorsPolicy", builder => builder
.WithOrigins("http://example.com")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials());
});
}
// Startup.cs 中的 Configure 方法
public void Configure(IApplication app)
{
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseEndpoints(endpoints => {
endpoints.MapHub<ChatHub>("/chatHub");
});
}
高級(jí)配置
services.AddSignalR(hubOptions => {
// 啟用詳細(xì)錯(cuò)誤消息(開(kāi)發(fā)環(huán)境)
hubOptions.EnableDetailedErrors = true;
// 配置保持活動(dòng)狀態(tài)
hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(15);
// 限制最大消息大小
hubOptions.MaximumReceiveMessageSize = 65536;
});
3.2 客戶(hù)端實(shí)現(xiàn)
JavaScript 客戶(hù)端
// 創(chuàng)建連接
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub", {
// 配置傳輸回退順序
transport: signalR.HttpTransportType.WebSockets |
signalR.HttpTransportType.ServerSentEvents,
// 訪問(wèn)令牌(如果需要認(rèn)證)
accessTokenFactory: () => {
return localStorage.getItem('authToken');
},
// 跳過(guò)協(xié)商(直接使用WebSocket)
skipNegotiation: true
})
.configureLogging(signalR.LogLevel.Information)
.withAutomaticReconnect({
// 自定義重試策略
nextRetryDelayInMilliseconds: retryContext => {
return Math.min(retryContext.elapsedMilliseconds * 2, 10000);
}
})
.build();
// 定義服務(wù)器可調(diào)用的方法
connection.on("ReceiveMessage", (user, message) => {
displayMessage(user, message);
});
// 啟動(dòng)連接
async function startConnection() {
try {
await connection.start();
console.log("Connected successfully");
} catch (err) {
console.log("Connection failed: ", err);
setTimeout(startConnection, 5000);
}
}
// 調(diào)用服務(wù)器方法
async function sendMessage(user, message) {
try {
await connection.invoke("SendMessage", user, message);
} catch (err) {
console.error(err);
}
}.NET 客戶(hù)端
// 創(chuàng)建連接
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chatHub", options => {
options.AccessTokenProvider = () => Task.FromResult(_authToken);
options.SkipNegotiation = true;
options.Transports = HttpTransportType.WebSockets;
})
.WithAutomaticReconnect(new[] {
TimeSpan.Zero, // 立即重試
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(30) // 之后每30秒重試一次
})
.ConfigureLogging(logging => {
logging.SetMinimumLevel(LogLevel.Debug);
logging.AddConsole();
})
.Build();
// 注冊(cè)處理方法
connection.On<string, string>("ReceiveMessage", (user, message) => {
Console.WriteLine($"{user}: {message}");
});
// 啟動(dòng)連接
try {
await connection.StartAsync();
Console.WriteLine("Connection started");
} catch (Exception ex) {
Console.WriteLine($"Error starting connection: {ex.Message}");
}
// 調(diào)用服務(wù)器方法
try {
await connection.InvokeAsync("SendMessage",
"ConsoleUser", "Hello from .NET client!");
} catch (Exception ex) {
Console.WriteLine($"Error sending message: {ex.Message}");
}
4. SignalR 高級(jí)特性
4.1 組管理
SignalR 提供了強(qiáng)大的組管理功能,允許將連接分組并向特定組廣播消息:
public class ChatHub : Hub
{
// 加入組
public async Task JoinGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("SystemMessage",
$"{Context.ConnectionId} 加入了 {groupName}");
}
// 離開(kāi)組
public async Task LeaveGroup(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("SystemMessage",
$"{Context.ConnectionId} 離開(kāi)了 {groupName}");
}
// 向組發(fā)送消息
public async Task SendToGroup(string groupName, string message)
{
await Clients.Group(groupName).SendAsync("ReceiveMessage",
Context.ConnectionId, message);
}
}
4.2 用戶(hù)標(biāo)識(shí)
SignalR 可以集成 ASP.NET Core 的身份認(rèn)證系統(tǒng):
[Authorize]
public class ChatHub : Hub
{
public override async Task OnConnectedAsync()
{
var user = Context.User;
var username = user.Identity.Name;
await Clients.All.SendAsync("SystemMessage",
$"{username} 加入了聊天");
await base.OnConnectedAsync();
}
public async Task SendMessage(string message)
{
var user = Context.User;
await Clients.All.SendAsync("ReceiveMessage",
user.Identity.Name, message);
}
}
4.3 流式傳輸
SignalR 支持從服務(wù)器到客戶(hù)端的流式數(shù)據(jù)傳輸:
public class DataStreamHub : Hub
{
// 服務(wù)器到客戶(hù)端流
public async IAsyncEnumerable<int> CounterStream(int count,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
for (var i = 0; i < count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
yield return i;
await Task.Delay(1000, cancellationToken);
}
}
// 客戶(hù)端到服務(wù)器流
public async Task UploadStream(IAsyncEnumerable<string> stream)
{
await foreach (var item in stream)
{
Console.WriteLine($"Received: {item}");
}
}
}
客戶(hù)端調(diào)用流方法:
// 消費(fèi)服務(wù)器流
connection.on("CounterStream", async (count) => {
const stream = connection.stream("CounterStream", 10);
for await (const item of stream) {
console.log(item);
}
});
// 發(fā)送客戶(hù)端流
async function * getDataStream() {
for (let i = 0; i < 10; i++) {
yield i.toString();
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
const streamResult = connection.send("UploadStream", getDataStream());
5. SignalR 性能優(yōu)化
5.1 橫向擴(kuò)展
當(dāng)部署多個(gè)服務(wù)器時(shí),SignalR 需要后端服務(wù)來(lái)同步消息:
// 使用 Redis 作為背板
services.AddSignalR().AddStackExchangeRedis("localhost", options => {
options.Configuration.ChannelPrefix = "MyApp";
});
// 或者使用 Azure SignalR 服務(wù)
services.AddSignalR().AddAzureSignalR("Endpoint=...;AccessKey=...");
5.2 消息壓縮
services.AddSignalR()
.AddMessagePackProtocol(options => {
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>() {
MessagePack.Resolvers.StandardResolver.Instance
};
});
5.3 連接過(guò)濾
public class CustomHubFilter : IHubFilter
{
public async ValueTask<object> InvokeMethodAsync(
HubInvocationContext invocationContext,
Func<HubInvocationContext, ValueTask<object>> next)
{
// 記錄方法調(diào)用
Console.WriteLine($"調(diào)用 {invocationContext.HubMethodName}");
// 檢查權(quán)限等
return await next(invocationContext);
}
}
// 注冊(cè)全局過(guò)濾器
services.AddSignalR(options => {
options.AddFilter<CustomHubFilter>();
});
6. SignalR 最佳實(shí)踐
連接管理:
- 始終處理斷開(kāi)連接和重連
- 在客戶(hù)端檢測(cè)網(wǎng)絡(luò)狀態(tài)變化
- 使用
withAutomaticReconnect配置合理的重試策略
安全性:
- 始終驗(yàn)證輸入
- 使用 HTTPS
- 實(shí)現(xiàn)適當(dāng)?shù)氖跈?quán)
- 限制消息大小
性能:
- 對(duì)于高頻率消息,考慮批處理
- 使用二進(jìn)制協(xié)議(MessagePack)減少負(fù)載
- 適當(dāng)配置保持活動(dòng)間隔
監(jiān)控:
- 記錄連接和斷開(kāi)事件
- 監(jiān)控消息速率
- 設(shè)置警報(bào)閾值
7. SignalR 與其他技術(shù)的對(duì)比
| 特性 | SignalR | 原生 WebSocket | SSE | 長(zhǎng)輪詢(xún) |
|---|---|---|---|---|
| 協(xié)議 | 自動(dòng)選擇最佳 | WebSocket | HTTP | HTTP |
| 雙向通信 | 是 | 是 | 否 | 否 |
| 自動(dòng)重連 | 是 | 需手動(dòng)實(shí)現(xiàn) | 是 | 需手動(dòng)實(shí)現(xiàn) |
| 傳輸效率 | 高 | 高 | 中 | 低 |
| 服務(wù)器負(fù)載 | 低 | 低 | 中 | 高 |
| 復(fù)雜度 | 低 | 高 | 中 | 低 |
| .NET集成 | 完美 | 需手動(dòng)處理 | 需手動(dòng)處理 | 需手動(dòng)處理 |
8. 實(shí)際應(yīng)用場(chǎng)景
8.1 實(shí)時(shí)聊天應(yīng)用
public class ChatHub : Hub
{
private static readonly Dictionary<string, string> _users = new();
public async Task RegisterUser(string username)
{
_users[Context.ConnectionId] = username;
await Clients.All.SendAsync("UserJoined", username);
}
public async Task SendMessage(string message)
{
if (_users.TryGetValue(Context.ConnectionId, out var username))
{
await Clients.All.SendAsync("ReceiveMessage", username, message);
}
}
public override async Task OnDisconnectedAsync(Exception exception)
{
if (_users.TryGetValue(Context.ConnectionId, out var username))
{
_users.Remove(Context.ConnectionId);
await Clients.All.SendAsync("UserLeft", username);
}
await base.OnDisconnectedAsync(exception);
}
}
8.2 實(shí)時(shí)數(shù)據(jù)儀表盤(pán)
public class DashboardHub : Hub
{
private readonly IDataService _dataService;
public DashboardHub(IDataService dataService)
{
_dataService = dataService;
}
public async Task SubscribeToUpdates()
{
var data = await _dataService.GetInitialData();
await Clients.Caller.SendAsync("InitialData", data);
// 開(kāi)始推送更新
var cancellationToken = Context.GetHttpContext().RequestAborted;
await foreach (var update in _dataService.GetDataUpdates(cancellationToken))
{
await Clients.Caller.SendAsync("DataUpdate", update);
}
}
}
8.3 多人協(xié)作編輯
public class CollaborationHub : Hub
{
private readonly ICollaborationService _collabService;
public CollaborationHub(ICollaborationService collabService)
{
_collabService = collabService;
}
public async Task JoinDocument(string docId)
{
await Groups.AddToGroupAsync(Context.ConnectionId, docId);
var document = await _collabService.GetDocument(docId);
var users = await _collabService.GetDocumentUsers(docId);
await Clients.Caller.SendAsync("DocumentLoaded", document);
await Clients.Group(docId).SendAsync("UsersUpdated", users);
}
public async Task EditDocument(string docId, DocumentEdit edit)
{
await _collabService.ApplyEdit(docId, edit);
await Clients.OthersInGroup(docId).SendAsync("DocumentEdited", edit);
}
}
9. 常見(jiàn)問(wèn)題解決方案
9.1 連接問(wèn)題排查
檢查傳輸協(xié)議:
connection.onclose(error => {
console.log("Connection closed due to error: ", error);
console.log("Last transport: ", connection.connection.transport.name);
});
啟用詳細(xì)日志:
services.AddSignalR()
.AddHubOptions<ChatHub>(options => {
options.EnableDetailedErrors = true;
});
檢查 CORS 配置:
services.AddCors(options => {
options.AddPolicy("SignalRCors", builder => {
builder.WithOrigins("https://yourdomain.com")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
9.2 性能問(wèn)題優(yōu)化
使用 MessagePack:
services.AddSignalR()
.AddMessagePackProtocol();
限制消息大小:
services.AddSignalR(options => {
options.MaximumReceiveMessageSize = 32768; // 32KB
});
批處理消息:
// 在客戶(hù)端
let batch = [];
setInterval(() => {
if (batch.length > 0) {
connection.send("SendBatch", batch);
batch = [];
}
}, 100);
9.3 橫向擴(kuò)展問(wèn)題
使用 Azure SignalR 服務(wù):
services.AddSignalR()
.AddAzureSignalR("Endpoint=...;AccessKey=...");
實(shí)現(xiàn)自定義背板:
public class CustomBackplane : IHubLifetimeManager
{
// 實(shí)現(xiàn)必要接口方法
}
services.AddSingleton<IHubLifetimeManager, CustomBackplane>();
10. 未來(lái)展望
SignalR 作為 .NET 實(shí)時(shí)通信的核心組件,未來(lái)可能會(huì):
- 集成更高效的二進(jìn)制協(xié)議
- 改進(jìn)移動(dòng)端支持
- 增強(qiáng)與 WebRTC 的集成
- 提供更好的離線消息處理
- 優(yōu)化大規(guī)模集群支持
結(jié)論
SignalR 是 .NET 生態(tài)中最強(qiáng)大、最成熟的實(shí)時(shí)通信解決方案,它抽象了底層傳輸細(xì)節(jié),提供了簡(jiǎn)單易用的 API,并自動(dòng)處理了連接管理、重連等復(fù)雜問(wèn)題。無(wú)論是構(gòu)建聊天應(yīng)用、實(shí)時(shí)儀表盤(pán)還是協(xié)作系統(tǒng),SignalR 都能提供穩(wěn)定高效的實(shí)時(shí)通信能力。
以上就是.NET使用SignalR實(shí)現(xiàn)實(shí)時(shí)通信的完全指南的詳細(xì)內(nèi)容,更多關(guān)于.NET SignalR實(shí)時(shí)通信的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
asp.net MVC 在Controller控制器中實(shí)現(xiàn)驗(yàn)證碼輸出功能
這篇文章主要介紹了asp.net MVC 在Controller控制器中實(shí)現(xiàn)驗(yàn)證碼輸出功能,本文給大家介紹非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12
在DataTable中執(zhí)行Select("條件")后,返回DataTable的方法
在DataTable中執(zhí)行Select("條件")后,返回DataTable的方法...2007-09-09
XmlReader 讀取器讀取內(nèi)存流 MemoryStream 的注意事項(xiàng)
XmlReader 讀取器讀取內(nèi)存流 MemoryStream 的注意事項(xiàng)...2007-04-04
ASP.NET?MVC5網(wǎng)站開(kāi)發(fā)顯示文章列表(九)
顯示文章列表分兩塊,管理員可以顯示全部文章列表,一般用戶(hù)只顯示自己的文章列表。文章列表的顯示采用easyui-datagrid,后臺(tái)需要與之對(duì)應(yīng)的action返回json類(lèi)型數(shù)據(jù),感興趣的小伙伴們可以參考一下2015-09-09
用WPF實(shí)現(xiàn)屏幕文字提示的實(shí)現(xiàn)方法
本文介紹WPF應(yīng)用程序?qū)崿F(xiàn)在屏幕上顯示一行或多行文字通知。它沒(méi)有標(biāo)題欄和最大化最小化等按鈕,可以有半透明背景以使文字的顯示更清晰,鼠標(biāo)點(diǎn)擊后提示消失。2013-07-07
ASP.NetCore使用Swagger實(shí)戰(zhàn)
這篇文章主要介紹了ASP.NetCore使用Swagger實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
asp.net StreamReader 創(chuàng)建文件的實(shí)例代碼
這篇文章介紹了asp.net StreamReader 創(chuàng)建文件的實(shí)例代碼,有需要的朋友可以參考一下2013-07-07

