SignalR 协议深度分析
1. 握手协议 (Negotiation)
1.1 客户端发起握手
POST /chathub/negotiate HTTP/1.1
Host: localhost:5000
Content-Type: application/json
Connection: keep-alive{"protocol": "json","version": 1
}
1.2 服务器响应
HTTP/1.1 200 OK
Content-Type: application/json{"connectionId": "abc123","availableTransports": [{"transport": "WebSockets","transferFormats": ["Text", "Binary"]},{"transport": "ServerSentEvents","transferFormats": ["Text"]},{"transport": "LongPolling","transferFormats": ["Text", "Binary"]}]
}
2. WebSocket 消息格式
2.1 消息类型枚举
public enum MessageType
{Invocation = 1, // 方法调用StreamItem = 2, // 流数据项Completion = 3, // 完成StreamInvocation = 4, // 流调用CancelInvocation = 5, // 取消调用Ping = 6, // 心跳Pong = 7, // 心跳响应Close = 8, // 关闭连接Ack = 9 // 确认
}
2.2 实际消息示例
客户端调用服务器方法
{"protocol": "json","version": 1,"type": 1, // Invocation"target": "SendMessage","arguments": ["张三", "你好世界"],"invocationId": "12345"
}
服务器调用客户端方法
{"protocol": "json","version": 1,"type": 1, // Invocation"target": "ReceiveMessage","arguments": ["张三", "你好世界"]
}
心跳消息
{"protocol": "json","version": 1,"type": 6 // Ping
}
3. 长轮询协议
3.1 轮询请求
POST /chathub/poll?connectionId=abc123 HTTP/1.1
Host: localhost:5000
Content-Type: application/json
Connection: keep-alive{"protocol": "json","version": 1
}
3.2 轮询响应
HTTP/1.1 200 OK
Content-Type: application/json
Connection: keep-alive{"messages": [{"protocol": "json","version": 1,"type": 1,"target": "ReceiveMessage","arguments": ["张三", "你好世界"]}]
}
4. 二进制协议 (MessagePack)
4.1 消息结构
[消息类型][消息长度][消息内容]
4.2 序列化示例
// C# 对象
var message = new { User = "张三", Content = "你好" };// MessagePack 序列化后的字节数组
[0x01, 0x00, 0x00, 0x00, 0x1A, 0x82, 0xA4, 0x55, 0x73, 0x65, 0x72, 0xA6, 0xE5, 0xBC, 0xA0, 0xE4, 0xB8, 0x89, 0xA7, 0x43, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0xA6, 0xE4, 0xBD, 0xA0, 0xE5, 0xA5, 0xBD]
5. 连接管理
5.1 连接状态
public enum ConnectionState
{Disconnected = 0,Connecting = 1,Connected = 2,Reconnecting = 3
}
5.2 连接生命周期
1. 创建连接对象
2. 发起握手请求
3. 选择传输协议
4. 建立连接通道
5. 开始消息交换
6. 处理断开/重连
6. 错误处理
6.1 错误消息格式
{"protocol": "json","version": 1,"type": 3, // Completion"invocationId": "12345","error": "方法 'SendMessage' 不存在","result": null
}
6.2 常见错误类型
- 连接错误: 网络中断、服务器不可用
- 协议错误: 消息格式不正确
- 方法错误: 调用的方法不存在
- 参数错误: 参数类型不匹配
- 授权错误: 没有权限访问方法
7. 性能优化
7.1 消息批处理
{"protocol": "json","version": 1,"type": 1,"target": "ReceiveMessage","arguments": [["用户1", "消息1"],["用户2", "消息2"],["用户3", "消息3"]]
}
7.2 压缩传输
// 启用 GZIP 压缩
services.AddSignalR().AddHubOptions<ChatHub>(options =>{options.EnableDetailedErrors = true;options.MaximumReceiveMessageSize = 32 * 1024;});
8. 安全机制
8.1 身份验证
[Authorize]
public class ChatHub : Hub
{public async Task SendMessage(string message){var user = Context.User.Identity.Name;await Clients.All.SendAsync("ReceiveMessage", user, message);}
}
8.2 授权策略
[Authorize(Policy = "ChatPolicy")]
public class ChatHub : Hub
{// 只有通过授权策略的用户才能访问
}
9. 调试技巧
9.1 启用详细日志
services.AddSignalR().AddHubOptions<ChatHub>(options =>{options.EnableDetailedErrors = true;});
9.2 客户端调试
// 启用详细日志
const connection = new signalR.HubConnectionBuilder().withUrl("/chathub").configureLogging(signalR.LogLevel.Debug).build();
10. 实际应用场景
10.1 聊天应用
- 实时消息传递
- 用户状态同步
- 群组管理
10.2 游戏服务器
- 实时游戏状态同步
- 玩家动作广播
- 游戏事件通知
10.3 IoT 设备
- 设备状态监控
- 远程控制指令
- 数据采集
总结
SignalR 的底层原理可以概括为:
- 协议层: WebSocket + 长轮询 + Server-Sent Events
- 消息层: JSON/MessagePack 序列化
- RPC层: 双向方法调用机制
- 传输层: HTTP/WebSocket 协议
- 安全层: 身份验证 + 授权策略
这就是为什么 JavaScript 能调用 C# 方法,C# 能调用 JavaScript 函数的根本原理!