Unity BestHTTP 插件高级指南 - WebSocket 与实时通信
BestHTTP 不仅支持 HTTP 请求,还提供了完整的 WebSocket、Socket.IO、SignalR 和 Server-Sent Events 实现。本文档详细介绍这些实时通信协议的使用方法。
一、实时通信协议概述
1.1 协议对比
| 协议 |
特性 |
适用场景 |
连接方式 |
| WebSocket |
全双工通信,低延迟 |
实时游戏、聊天室 |
长连接 |
| Socket.IO |
WebSocket 封装,自动降级 |
Node.js 后端兼容 |
长连接 |
| SignalR |
微软开发,自动协商 |
.NET 后端兼容 |
长连接 |
| SSE |
服务器推送,单向 |
消息通知、股票行情 |
长连接 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ┌─────────────────────────────────────────────────────────────────────────┐ │ 实时通信协议架构对比 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ WebSocket │ │ Socket.IO │ │ SignalR │ │ │ │ (RFC 6455) │ │ (封装层) │ │ (抽象层) │ │ │ ├──────────────┤ ├──────────────┤ ├──────────────┤ │ │ │ 原生协议 │ │ 自动重连 │ │ 自动协商 │ │ │ │ 低延迟 │ │ 心跳检测 │ │ 多种传输 │ │ │ │ 二进制支持 │ │ 命名空间 │ │ Hub 模型 │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ │ │ └────────────────────┴────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────┐ │ │ │ TCP 连接 │ │ │ └──────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘
|
二、WebSocket 基础
2.1 引入命名空间
1
| using BestHTTP.WebSocket;
|
2.2 创建 WebSocket 连接
1 2 3 4 5 6 7 8 9 10 11 12
| var webSocket = new WebSocket(new Uri("wss://echo.websocket.org"));
webSocket.OnOpen += OnWebSocketOpen; webSocket.OnMessage += OnMessageReceived; webSocket.OnBinary += OnBinaryMessageReceived; webSocket.OnClosed += OnWebSocketClosed; webSocket.OnError += OnError;
webSocket.Open();
|
2.3 WebSocket 事件详解
| 事件 |
触发时机 |
回调签名 |
| OnOpen |
连接成功建立 |
OnOpen(WebSocket ws) |
| OnMessage |
收到文本消息 |
OnMessage(WebSocket ws, string message) |
| OnBinary |
收到二进制消息 |
OnBinary(WebSocket ws, byte[] message) |
| OnClosed |
连接关闭 |
OnClosed(WebSocket ws, UInt16 code, string message) |
| OnError |
发生错误 |
OnError(WebSocket ws, Exception ex) |
| OnErrorDesc |
详细错误信息 |
OnErrorDesc(WebSocket ws, string error) |
2.4 事件处理代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| void OnWebSocketOpen(WebSocket ws) { Debug.Log("WebSocket Open!"); }
void OnMessageReceived(WebSocket ws, string message) { Debug.Log($"Text Message: {message}"); }
void OnBinaryMessageReceived(WebSocket ws, byte[] message) { Debug.Log($"Binary Message, Length: {message.Length}"); }
void OnWebSocketClosed(WebSocket ws, UInt16 code, string message) { Debug.Log($"WebSocket Closed! Code: {code}, Message: {message}"); }
void OnError(WebSocket ws, Exception ex) { string errorMsg = string.Empty; if (ws.InternalRequest.Response != null) { errorMsg = $"Status Code: {ws.InternalRequest.Response.StatusCode}, " + $"Message: {ws.InternalRequest.Response.Message}"; } Debug.LogError($"Error: {(ex != null ? ex.Message : "Unknown: " + errorMsg)}"); }
void OnErrorDesc(WebSocket ws, string error) { Debug.LogError($"Error Description: {error}"); }
|
2.5 发送消息
1 2 3 4 5 6 7 8
|
webSocket.Send("Message to the Server");
byte[] buffer = new byte[1024];
webSocket.Send(buffer);
|
2.6 心跳机制
1 2 3 4 5
| webSocket.StartPingThread = true;
webSocket.PingFrequency = 1000;
|
💡 注意:Ping 消息会定期发送到服务器,服务器返回的 Pong 消息会自动处理。
2.7 关闭连接
三、Socket.IO 客户端
3.1 Socket.IO 概述
Socket.IO 是一个 WebSocket 封装库,提供:
- 自动重连
- 心跳检测
- 命名空间
- 房间管理
- 事件驱动通信
3.2 引入命名空间
1
| using BestHTTP.SocketIO;
|
3.3 创建 Socket.Manager
1 2
| var manager = new SocketManager(new Uri("http://chat.socket.io/socket.io/"));
|
⚠️ 注意:URL 中的 /socket.io/ 路径很重要,这是 Socket.IO 服务器的默认监听路径。
3.4 连接命名空间
1 2 3 4 5 6 7 8
| Socket root = manager.Socket;
Socket nsp = manager["/customNamespace"]; Socket nsp2 = manager.GetSocket("/customNamespace");
|
3.5 订阅事件
| 预定义事件 |
说明 |
connect |
命名空间打开时发送 |
connecting |
开始连接时发送 |
disconnect |
传输断开时发送 |
reconnect |
重连成功时发送 |
reconnecting |
尝试重连时发送 |
reconnect_attempt |
重连尝试时发送 |
reconnect_failed |
重连失败时发送 |
error |
服务器或内部错误时发送 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| manager.Socket.On("login", OnLogin); manager.Socket.On("new message", OnNewMessage);
void OnLogin(Socket socket, Packet packet, params object[] args) { Debug.Log($"Login: {args[0]}"); }
manager.Socket.On("connect", () => Debug.Log("Connected!")); manager.Socket.On("disconnect", () => Debug.Log("Disconnected!"));
|
3.6 发送事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| manager.Socket.Emit("message", "userName", "message");
manager.Socket.Emit("custom event", OnAckCallback, "param1", "param2");
void OnAckCallback(Socket socket, Packet originalPacket, params object[] args) { Debug.Log("Server acknowledged!"); }
manager["/customNamespace"].On("customEvent", (socket, packet, args) => { socket.EmitAck(packet, "Event", "Received", "Successfully"); });
|
3.7 二进制数据处理
发送二进制数据
1 2 3
| byte[] data = new byte[10]; manager.Socket.Emit("eventWithBinary", "textual param", data);
|
接收二进制数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| socket.On("frame", OnFrame); void OnFrame(Socket socket, Packet packet, params object[] args) { texture.LoadImage(packet.Attachments[0]); }
socket.On("frame", OnFrame, autoDecodePayload: false); void OnFrame(Socket socket, Packet packet, params object[] args) { texture.LoadImage(packet.Attachments[0]); }
socket.On("frame", OnFrame, autoDecodePayload: false); void OnFrame(Socket socket, Packet packet, params object[] args) { packet.ReconstructAttachmentAsIndex(); args = packet.Decode(socket.Manager.Encoder); byte[] data = packet.Attachments[Convert.ToInt32(args[0])]; texture.LoadImage(data); }
socket.On("frame", OnFrame, autoDecodePayload: false); void OnFrame(Socket socket, Packet packet, params object[] args) { packet.ReconstructAttachmentAsBase64(); args = packet.Decode(socket.Manager.Encoder); byte[] data = Convert.FromBase64String(args[0] as string); texture.LoadImage(data); }
|
3.8 事件管理
1 2 3 4 5 6 7
| socket.Once("connect", OnConnected);
socket.Off(); socket.Off("connect"); socket.Off("connect", OnConnected);
|
3.9 SocketOptions 配置
| 选项 |
默认值 |
说明 |
Reconnection |
true |
是否自动重连 |
ReconnectionAttempts |
int.MaxValue |
重连尝试次数 |
ReconnectionDelay |
1000ms |
初始重连延迟 |
ReconnectionDelayMax |
5000ms |
最大重连延迟 |
RandomizationFactor |
0.5 |
延迟随机因子 |
Timeout |
20000ms |
连接超时 |
AutoConnect |
true |
是否自动连接 |
ConnectWith |
- |
传输类型(Polling/WebSocket) |
四、SignalR 客户端
4.1 SignalR 概述
SignalR 是微软开发的实时通信库,特点:
- 自动协商传输方式
- Hub 代理模型
- 强类型支持
- 连接状态管理
4.2 引入命名空间
4.3 创建 Connection
1 2 3 4 5 6 7 8 9 10 11 12
| Uri uri = new Uri("http://besthttpsignalr.azurewebsites.net/raw-connection/");
Connection connection = new Connection(uri);
Connection connection = new Connection(uri, "hub1", "hub2", "hubN");
Hub hub1 = new Hub("hub1"); Hub hub2 = new Hub("hub2"); Connection connection = new Connection(uri, hub1, hub2);
|
4.4 Connection 事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| connection.OnConnected += (conn) => Debug.Log("Connected!");
connection.OnClosed += (conn) => Debug.Log("Connection Closed");
connection.OnError += (conn, err) => Debug.Log($"Error: {err}");
connection.OnReconnecting += (conn) => Debug.Log("Reconnecting...");
connection.OnReconnected += (conn) => Debug.Log("Reconnected!");
connection.OnStateChanged += (conn, oldState, newState) => Debug.Log($"{oldState} -> {newState}");
connection.OnNonHubMessage += (conn, data) => Debug.Log($"Message: {data}");
connection.RequestPreparator = (conn, req, type) => req.Timeout = TimeSpan.FromSeconds(30);
|
4.5 发送非 Hub 消息
1 2 3 4 5
| connection.Send(new { Type = "Broadcast", Value = "Hello!" });
connection.SendJson("{ Type: 'Broadcast', Value: 'Hello!' }");
|
4.6 Hub 操作
1 2 3 4 5 6 7 8 9 10
| Hub hub = connection[0]; Hub hub2 = connection["hubName"];
connection["hubName"].On("joined", Joined); void Joined(Hub hub, MethodCallMessage msg) { Debug.Log($"{msg.Arguments[0]} joined at {msg.Arguments[1]}"); }
|
4.7 调用服务器方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| connection["hubName"].Call("Ping"); connection["hubName"].Call("Message", "param1", "param2");
connection["hubName"].Call("GetValue", OnGetValueDone); void OnGetValueDone(Hub hub, ClientMessage originalMessage, ResultMessage result) { Debug.Log($"Return value: {result.ReturnValue}"); }
connection["hubName"].Call("GetValue", OnGetValueDone, OnGetValueFailed); void OnGetValueFailed(Hub hub, ClientMessage originalMessage, FailureMessage error) { Debug.LogError($"Error: {error.ErrorMessage}"); }
connection["hubName"].Call("GetValue", OnDone, OnFailed, OnProgress); void OnProgress(Hub hub, ClientMessage originalMessage, ProgressMessage progress) { Debug.Log($"Progress: {progress.Progress}%"); }
|
4.8 Hub 基类继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class SampleHub : Hub { public SampleHub() : base("SampleHub") { base.On("ClientFunction", ClientFunctionImplementation); }
private void ClientFunctionImplementation(Hub hub, MethodCallMessage msg) { }
public void ServerFunction(string argument) { base.Call("ServerFunction", argument); } }
SampleHub sampleHub = new SampleHub(); Connection connection = new Connection(uri, sampleHub);
|
4.9 身份验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class HeaderAuthenticator : IAuthenticationProvider { public string User { get; private set; } public string Roles { get; private set; }
public bool IsPreAuthRequired { get { return false; } }
public event OnAuthenticationSuccededDelegate OnAuthenticationSucceded; public event OnAuthenticationFailedDelegate OnAuthenticationFailed;
public HeaderAuthenticator(string user, string roles) { this.User = user; this.Roles = roles; }
public void StartAuthentication() { }
public void PrepareRequest(HTTPRequest request, RequestTypes type) { request.SetHeader("username", this.User); request.SetHeader("roles", this.Roles); } }
connection.AuthenticationProvider = new HeaderAuthenticator("user1", "admin");
|
五、Server-Sent Events (SSE)
5.1 SSE 概述
SSE 是一种基于字符串的单向协议:
- 数据从服务器流向客户端
- 无法向服务器发送数据
- 自动重连机制
5.2 引入命名空间
1
| using BestHTTP.ServerSentEvents;
|
5.3 创建 EventSource
1
| var eventSource = new EventSource(new Uri("http://server.com"));
|
5.4 EventSource 属性
| 属性 |
说明 |
Uri |
连接端点 |
State |
当前状态 |
ReconnectionTime |
重连延迟(默认 2 秒) |
LastEventId |
最后收到的事件 ID |
InternalRequest |
内部 HTTP 请求 |
5.5 EventSource 事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| eventSource.OnOpen += OnEventSourceOpened; void OnEventSourceOpened(EventSource source) { Debug.Log("EventSource Opened!"); }
eventSource.OnMessage += OnEventSourceMessage; void OnEventSourceMessage(EventSource source, Message msg) { Debug.Log($"Message: {msg.Data}"); }
eventSource.OnError += OnEventSourceError; void OnEventSourceError(EventSource source, string error) { Debug.LogError($"Error: {error}"); }
eventSource.OnRetry += OnEventSourceRetry; bool OnEventSourceRetry(EventSource source) { return true; }
eventSource.OnClosed += OnEventSourceClosed; void OnEventSourceClosed(EventSource source) { Debug.Log("EventSource Closed!"); }
eventSource.OnStateChanged += OnEventSourceStateChanged; void OnEventSourceStateChanged(EventSource source, States oldState, States newState) { Debug.Log($"{oldState} => {newState}"); }
|
5.6 订阅自定义事件
1 2 3 4 5 6 7 8 9
| eventSource.On("userLogon", OnUserLoggedIn); void OnUserLoggedIn(EventSource source, Message msg) { Debug.Log(msg.Data); }
eventSource.Off("userLogon");
|
5.7 Message 属性
| 属性 |
说明 |
Id |
事件 ID |
Event |
事件名称 |
Data |
消息负载 |
Retry |
重连延迟 |
六、简单示例
6.1 上传图片(表单方式)
1 2 3
| var request = new HTTPRequest(new Uri("http://server.com"), HTTPMethods.Post, onFinished); request.AddBinaryData("image", texture.EncodeToPNG(), "image.png"); request.Send();
|
6.2 上传图片(原始数据方式)
1 2 3 4
| var request = new HTTPRequest(new Uri("http://server.com"), HTTPMethods.Post, onFinished); request.SetHeader("Content-Type", "image/png"); request.RawData = texture.EncodeToPNG(); request.Send();
|
6.3 自定义请求头
1 2 3 4
| var request = new HTTPRequest(new Uri("http://server.com"), HTTPMethods.Post, onFinished); request.SetHeader("Content-Type", "application/json; charset=UTF-8"); request.RawData = UTF8Encoding.GetBytes(ToJson(data)); request.Send();
|
6.4 显示下载进度
1 2 3 4 5 6 7 8 9
| var request = new HTTPRequest(new Uri("http://server.com/largefile"), (req, resp) => { Debug.Log("Finished!"); }); request.OnProgress += (req, downloaded, length) => { Debug.Log($"Progress: {downloaded / (float)length:P2}"); }; request.Send();
|
6.5 中止请求
1 2 3 4 5 6 7
| var request = new HTTPRequest(new Uri(address), (req, resp) => { Debug.Log($"State: {req.State}"); }); request.Send();
request.Abort();
|
6.6 可恢复下载(分块下载)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| private const int ChunkSize = 1024 * 1024; private string saveTo = "downloaded.bin";
void StartDownload(string url) { var headRequest = new HTTPRequest(new Uri(url), HTTPMethods.Head, (request, response) => { if (response == null) Debug.LogError("Server unreachable!"); else if (response.HasHeaderWithValue("accept-ranges", "none")) Debug.LogError("Server doesn't support Range header!"); else DownloadCallback(request, response); });
int startPos = PlayerPrefs.GetInt("LastDownloadPosition", 0); headRequest.SetRangeHeader(startPos, startPos + ChunkSize); headRequest.DisableCache = true; headRequest.Send(); }
void DownloadCallback(HTTPRequest request, HTTPResponse response) { if (response == null) return;
var range = response.GetRange(); if (range == null || !range.IsValid) return;
if (request.MethodType != HTTPMethods.Head) { string path = Path.Combine(Application.temporaryCachePath, saveTo); using (FileStream fs = new FileStream(path, FileMode.Append)) fs.Write(response.Data, 0, response.Data.Length);
PlayerPrefs.SetInt("LastDownloadPosition", range.LastBytePos);
if (range.LastBytePos == range.ContentLength - 1) { Debug.Log("Download finished!"); return; } }
int nextPos = (request.MethodType != HTTPMethods.Head) ? range.LastBytePos + 1 : PlayerPrefs.GetInt("LastDownloadPosition", 0);
var downloadRequest = new HTTPRequest(request.Uri, HTTPMethods.Get, true, DownloadCallback); downloadRequest.SetRangeHeader(nextPos, nextPos + ChunkSize); downloadRequest.DisableCache = true; downloadRequest.Send(); }
|
七、高级配置
7.1 禁用特定功能
通过编译符号可以禁用特定功能以减小包大小:
| 定义 |
作用 |
BESTHTTP_DISABLE_COOKIES |
禁用 Cookie 功能 |
BESTHTTP_DISABLE_CACHING |
禁用缓存功能 |
BESTHTTP_DISABLE_SERVERSENT_EVENTS |
禁用 SSE |
BESTHTTP_DISABLE_WEBSOCKET |
禁用 WebSocket |
BESTHTTP_DISABLE_SIGNALR |
禁用 SignalR |
BESTHTTP_DISABLE_SOCKETIO |
禁用 Socket.IO |
7.2 支持的平台
| 平台 |
支持情况 |
| WebGL |
✅ |
| iOS |
✅ |
| Android |
✅ |
| Windows Phone 10 |
✅ |
| WinRT / Metro |
✅ |
| Windows / Linux / Mac Standalone |
✅ |
7.3 SSL 处理
1 2
| request.UseAlternateSSL = true;
|
⚠️ 注意:大多数情况下默认 SSL 处理即可,仅在遇到证书问题时使用备用方案。
八、总结
| 协议 |
核心特性 |
适用场景 |
复杂度 |
| WebSocket |
全双工、低延迟 |
实时游戏、高频交易 |
⭐⭐ |
| Socket.IO |
自动重连、房间 |
聊天应用、社交游戏 |
⭐⭐⭐ |
| SignalR |
Hub 模型、协商传输 |
.NET 后端应用 |
⭐⭐⭐⭐ |
| SSE |
单向推送、简单实现 |
消息通知、数据订阅 |
⭐ |
💡 最佳实践:
- 优先使用加密连接(WSS)
- 实现心跳和断线重连
- 添加消息确认机制
- 做好错误处理和日志记录
- 根据场景选择合适的协议
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1487842110@qq.com