2020-04-10 16:32:33 +08:00
|
|
|
|
using System;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using System.Net.WebSockets;
|
|
|
|
|
using LeanCloud.Common;
|
|
|
|
|
using LeanCloud.Realtime.Internal.Router;
|
2020-04-13 17:29:55 +08:00
|
|
|
|
using LeanCloud.Realtime.Internal.Connection;
|
2020-04-10 16:32:33 +08:00
|
|
|
|
|
|
|
|
|
namespace LeanCloud.Realtime.Internal.WebSocket {
|
2020-04-13 10:47:14 +08:00
|
|
|
|
/// <summary>
|
2020-04-16 11:38:22 +08:00
|
|
|
|
/// WebSocket 客户端,负责底层连接和事件,只与通信协议相关
|
2020-04-13 10:47:14 +08:00
|
|
|
|
/// </summary>
|
2020-04-10 16:32:33 +08:00
|
|
|
|
internal class LCWebSocketClient {
|
|
|
|
|
// .net standard 2.0 好像在拼合 Frame 时有 bug,所以将这个值调整大一些
|
|
|
|
|
private const int RECV_BUFFER_SIZE = 1024 * 5;
|
|
|
|
|
|
2020-04-16 11:38:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 关闭超时
|
|
|
|
|
/// </summary>
|
2020-04-10 16:32:33 +08:00
|
|
|
|
private const int CLOSE_TIMEOUT = 5000;
|
|
|
|
|
|
2020-04-16 11:38:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 连接超时
|
|
|
|
|
/// </summary>
|
2020-04-13 17:29:55 +08:00
|
|
|
|
private const int CONNECT_TIMEOUT = 10000;
|
|
|
|
|
|
2020-04-16 11:38:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 消息事件
|
|
|
|
|
/// </summary>
|
2020-04-10 16:32:33 +08:00
|
|
|
|
internal Action<byte[]> OnMessage;
|
|
|
|
|
|
2020-04-16 11:38:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 连接关闭
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal Action OnClose;
|
2020-04-10 16:32:33 +08:00
|
|
|
|
|
|
|
|
|
private ClientWebSocket ws;
|
|
|
|
|
|
|
|
|
|
private readonly LCRTMRouter router;
|
|
|
|
|
|
2020-04-13 17:29:55 +08:00
|
|
|
|
private readonly LCHeartBeat heartBeat;
|
|
|
|
|
|
|
|
|
|
internal LCWebSocketClient(LCRTMRouter router, LCHeartBeat heartBeat) {
|
|
|
|
|
this.router = router;
|
|
|
|
|
this.heartBeat = heartBeat;
|
2020-04-10 16:32:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 11:38:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 连接
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
2020-04-10 16:32:33 +08:00
|
|
|
|
internal async Task Connect() {
|
|
|
|
|
try {
|
2020-04-13 17:29:55 +08:00
|
|
|
|
LCRTMServer rtmServer = await router.GetServer();
|
|
|
|
|
try {
|
|
|
|
|
LCLogger.Debug($"Primary Server");
|
|
|
|
|
await Connect(rtmServer.Primary);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
LCLogger.Error(e);
|
|
|
|
|
LCLogger.Debug($"Secondary Server");
|
|
|
|
|
await Connect(rtmServer.Secondary);
|
|
|
|
|
}
|
2020-04-10 16:32:33 +08:00
|
|
|
|
} catch (Exception e) {
|
2020-04-13 17:29:55 +08:00
|
|
|
|
throw e;
|
2020-04-10 16:32:33 +08:00
|
|
|
|
}
|
2020-04-13 17:29:55 +08:00
|
|
|
|
|
2020-04-10 16:32:33 +08:00
|
|
|
|
// 接收
|
|
|
|
|
_ = StartReceive();
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 11:38:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 连接指定 ws 服务器
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="server"></param>
|
|
|
|
|
/// <returns></returns>
|
2020-04-10 16:32:33 +08:00
|
|
|
|
private async Task Connect(string server) {
|
2020-04-13 10:47:14 +08:00
|
|
|
|
LCLogger.Debug($"Connecting WebSocket: {server}");
|
2020-04-13 17:29:55 +08:00
|
|
|
|
Task timeoutTask = Task.Delay(CONNECT_TIMEOUT);
|
2020-04-10 16:32:33 +08:00
|
|
|
|
ws = new ClientWebSocket();
|
|
|
|
|
ws.Options.AddSubProtocol("lc.protobuf2.3");
|
|
|
|
|
Task connectTask = ws.ConnectAsync(new Uri(server), default);
|
|
|
|
|
if (await Task.WhenAny(connectTask, timeoutTask) == connectTask) {
|
|
|
|
|
LCLogger.Debug($"Connected WebSocket: {server}");
|
|
|
|
|
} else {
|
|
|
|
|
throw new TimeoutException("Connect timeout");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 11:38:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 主动关闭连接
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
2020-04-10 16:32:33 +08:00
|
|
|
|
internal async Task Close() {
|
2020-04-13 10:47:14 +08:00
|
|
|
|
LCLogger.Debug("Closing WebSocket");
|
2020-04-10 16:32:33 +08:00
|
|
|
|
OnMessage = null;
|
2020-04-16 11:38:22 +08:00
|
|
|
|
OnClose = null;
|
2020-04-13 17:29:55 +08:00
|
|
|
|
heartBeat.Stop();
|
2020-04-10 16:32:33 +08:00
|
|
|
|
try {
|
|
|
|
|
// 发送关闭帧可能会很久,所以增加超时
|
|
|
|
|
// 主动挥手关闭,不会再收到 Close Frame
|
2020-04-13 17:29:55 +08:00
|
|
|
|
Task closeTask = ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default);
|
2020-04-10 16:32:33 +08:00
|
|
|
|
Task delayTask = Task.Delay(CLOSE_TIMEOUT);
|
|
|
|
|
await Task.WhenAny(closeTask, delayTask);
|
|
|
|
|
} catch (Exception e) {
|
2020-04-13 17:29:55 +08:00
|
|
|
|
LCLogger.Error(e);
|
2020-04-10 16:32:33 +08:00
|
|
|
|
} finally {
|
|
|
|
|
ws.Abort();
|
|
|
|
|
ws.Dispose();
|
2020-04-13 10:47:14 +08:00
|
|
|
|
LCLogger.Debug("Closed WebSocket");
|
2020-04-10 16:32:33 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 11:38:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 发送数据
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="data"></param>
|
|
|
|
|
/// <returns></returns>
|
2020-04-10 16:32:33 +08:00
|
|
|
|
internal async Task Send(byte[] data) {
|
|
|
|
|
ArraySegment<byte> bytes = new ArraySegment<byte>(data);
|
|
|
|
|
if (ws.State == WebSocketState.Open) {
|
|
|
|
|
try {
|
|
|
|
|
await ws.SendAsync(bytes, WebSocketMessageType.Binary, true, default);
|
|
|
|
|
} catch (Exception e) {
|
2020-04-13 17:29:55 +08:00
|
|
|
|
LCLogger.Error(e);
|
2020-04-10 16:32:33 +08:00
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2020-04-13 10:47:14 +08:00
|
|
|
|
string message = $"Error Websocket state: {ws.State}";
|
|
|
|
|
LCLogger.Error(message);
|
|
|
|
|
throw new Exception(message);
|
2020-04-10 16:32:33 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 11:38:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 接收数据
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
2020-04-10 16:32:33 +08:00
|
|
|
|
private async Task StartReceive() {
|
|
|
|
|
byte[] buffer = new byte[RECV_BUFFER_SIZE];
|
|
|
|
|
try {
|
|
|
|
|
while (ws.State == WebSocketState.Open) {
|
|
|
|
|
WebSocketReceiveResult result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), default);
|
|
|
|
|
if (result.MessageType == WebSocketMessageType.Close) {
|
2020-04-13 17:29:55 +08:00
|
|
|
|
LCLogger.Debug($"Receive Closed: {result.CloseStatus}");
|
|
|
|
|
if (ws.State == WebSocketState.CloseReceived) {
|
|
|
|
|
// 如果是服务端主动关闭,则挥手关闭,并认为是断线
|
|
|
|
|
try {
|
2020-04-16 11:38:22 +08:00
|
|
|
|
Task closeTask = ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default);
|
|
|
|
|
await Task.WhenAny(closeTask, Task.Delay(CLOSE_TIMEOUT));
|
2020-04-13 17:29:55 +08:00
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
LCLogger.Error(e);
|
|
|
|
|
} finally {
|
2020-04-16 11:38:22 +08:00
|
|
|
|
HandleExceptionClose();
|
2020-04-13 17:29:55 +08:00
|
|
|
|
}
|
2020-04-10 16:32:33 +08:00
|
|
|
|
}
|
|
|
|
|
} else if (result.MessageType == WebSocketMessageType.Binary) {
|
2020-04-16 11:38:22 +08:00
|
|
|
|
_ = heartBeat.Update(HandleExceptionClose);
|
2020-04-10 16:32:33 +08:00
|
|
|
|
// 拼合 WebSocket Message
|
|
|
|
|
int length = result.Count;
|
|
|
|
|
byte[] data = new byte[length];
|
|
|
|
|
Array.Copy(buffer, data, length);
|
|
|
|
|
OnMessage?.Invoke(data);
|
|
|
|
|
} else {
|
|
|
|
|
LCLogger.Error($"Error message type: {result.MessageType}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
// 客户端网络异常
|
2020-04-13 17:29:55 +08:00
|
|
|
|
LCLogger.Error(e);
|
2020-04-16 11:38:22 +08:00
|
|
|
|
OnClose?.Invoke();
|
2020-04-10 16:32:33 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 11:38:22 +08:00
|
|
|
|
private void HandleExceptionClose() {
|
2020-04-10 16:32:33 +08:00
|
|
|
|
try {
|
2020-04-13 17:29:55 +08:00
|
|
|
|
heartBeat.Stop();
|
2020-04-10 16:32:33 +08:00
|
|
|
|
ws.Abort();
|
|
|
|
|
ws.Dispose();
|
|
|
|
|
} catch (Exception e) {
|
2020-04-13 17:29:55 +08:00
|
|
|
|
LCLogger.Error(e);
|
2020-04-16 11:38:22 +08:00
|
|
|
|
} finally {
|
|
|
|
|
OnClose?.Invoke();
|
2020-04-10 16:32:33 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|