* LCLogger.cs:
* LCIMClient.cs: * LCAppRouter.cs: * LCRTMRouter.cs: * LCHeartBeat.cs: * LCConnection.cs: * LCWebSocketClient.cs: * LCIMGoAwayController.cs: * LCIMConversationController.cs: * LCHttpClient.cs: chore: 支持 goaway
parent
35c66d65cf
commit
88f2b64eba
|
@ -63,7 +63,7 @@ namespace LeanCloud.Common {
|
||||||
Dictionary<string, object> data = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString);
|
Dictionary<string, object> data = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString);
|
||||||
appServer = new LCAppServer(data);
|
appServer = new LCAppServer(data);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e.Message);
|
LCLogger.Error(e);
|
||||||
// 拉取服务地址失败后,使用国际节点的默认服务地址
|
// 拉取服务地址失败后,使用国际节点的默认服务地址
|
||||||
appServer = LCAppServer.GetInternalFallbackAppServer(appId);
|
appServer = LCAppServer.GetInternalFallbackAppServer(appId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace LeanCloud.Common {
|
namespace LeanCloud.Common {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -36,5 +37,15 @@ namespace LeanCloud.Common {
|
||||||
public static void Error(string format, params object[] args) {
|
public static void Error(string format, params object[] args) {
|
||||||
LogDelegate?.Invoke(LCLogLevel.Error, string.Format(format, args));
|
LogDelegate?.Invoke(LCLogLevel.Error, string.Format(format, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Error(Exception e) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.Append(e.GetType());
|
||||||
|
sb.Append("\n");
|
||||||
|
sb.Append(e.Message);
|
||||||
|
sb.Append("\n");
|
||||||
|
sb.Append(e.StackTrace);
|
||||||
|
Error(sb.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Text;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
|
using LeanCloud.Realtime.Internal.Router;
|
||||||
using LeanCloud.Realtime.Internal.WebSocket;
|
using LeanCloud.Realtime.Internal.WebSocket;
|
||||||
using LeanCloud.Realtime.Protocol;
|
using LeanCloud.Realtime.Protocol;
|
||||||
using LeanCloud.Common;
|
using LeanCloud.Common;
|
||||||
|
@ -15,7 +16,11 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
internal class LCConnection {
|
internal class LCConnection {
|
||||||
private const int SEND_TIMEOUT = 10000;
|
private const int SEND_TIMEOUT = 10000;
|
||||||
|
|
||||||
private const int MAX_RECONNECT_TIMES = 10;
|
private const int MAX_RECONNECT_TIMES = 3;
|
||||||
|
|
||||||
|
private const int RECONNECT_INTERVAL = 5000;
|
||||||
|
|
||||||
|
private const int HEART_BEAT_INTERVAL = 5000;
|
||||||
|
|
||||||
internal Action<GenericCommand> OnNotification;
|
internal Action<GenericCommand> OnNotification;
|
||||||
|
|
||||||
|
@ -25,24 +30,25 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
|
|
||||||
internal Action OnReconnected;
|
internal Action OnReconnected;
|
||||||
|
|
||||||
private LCHeartBeat heartBeat;
|
|
||||||
|
|
||||||
internal string id;
|
internal string id;
|
||||||
|
|
||||||
private readonly Dictionary<int, TaskCompletionSource<GenericCommand>> responses;
|
private readonly Dictionary<int, TaskCompletionSource<GenericCommand>> responses;
|
||||||
|
|
||||||
private int requestI = 1;
|
private int requestI = 1;
|
||||||
|
|
||||||
|
private LCRTMRouter router;
|
||||||
|
|
||||||
|
private LCHeartBeat heartBeat;
|
||||||
|
|
||||||
private LCWebSocketClient client;
|
private LCWebSocketClient client;
|
||||||
|
|
||||||
internal LCConnection(string id) {
|
internal LCConnection(string id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
responses = new Dictionary<int, TaskCompletionSource<GenericCommand>>();
|
responses = new Dictionary<int, TaskCompletionSource<GenericCommand>>();
|
||||||
heartBeat = new LCHeartBeat(this, 10000, 10000, () => {
|
heartBeat = new LCHeartBeat(this, HEART_BEAT_INTERVAL, HEART_BEAT_INTERVAL);
|
||||||
|
router = new LCRTMRouter();
|
||||||
});
|
client = new LCWebSocketClient(router, heartBeat) {
|
||||||
client = new LCWebSocketClient {
|
OnMessage = OnClientMessage,
|
||||||
OnMessage = OnMessage,
|
|
||||||
OnDisconnect = OnClientDisconnect
|
OnDisconnect = OnClientDisconnect
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -51,6 +57,18 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
await client.Connect();
|
await client.Connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal async Task Reset() {
|
||||||
|
router.Reset();
|
||||||
|
await client.Close();
|
||||||
|
heartBeat = new LCHeartBeat(this, HEART_BEAT_INTERVAL, HEART_BEAT_INTERVAL);
|
||||||
|
router = new LCRTMRouter();
|
||||||
|
client = new LCWebSocketClient(router, heartBeat) {
|
||||||
|
OnMessage = OnClientMessage,
|
||||||
|
OnDisconnect = OnClientDisconnect
|
||||||
|
};
|
||||||
|
await Reconnect();
|
||||||
|
}
|
||||||
|
|
||||||
internal async Task<GenericCommand> SendRequest(GenericCommand request) {
|
internal async Task<GenericCommand> SendRequest(GenericCommand request) {
|
||||||
TaskCompletionSource<GenericCommand> tcs = new TaskCompletionSource<GenericCommand>();
|
TaskCompletionSource<GenericCommand> tcs = new TaskCompletionSource<GenericCommand>();
|
||||||
request.I = requestI++;
|
request.I = requestI++;
|
||||||
|
@ -58,15 +76,15 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
LCLogger.Debug($"{id} => {FormatCommand(request)}");
|
LCLogger.Debug($"{id} => {FormatCommand(request)}");
|
||||||
byte[] bytes = request.ToByteArray();
|
byte[] bytes = request.ToByteArray();
|
||||||
Task sendTask = client.Send(bytes);
|
Task sendTask = client.Send(bytes);
|
||||||
Task timeoutTask = Task.Delay(SEND_TIMEOUT);
|
if (await Task.WhenAny(sendTask, Task.Delay(SEND_TIMEOUT)) == sendTask) {
|
||||||
try {
|
try {
|
||||||
Task doneTask = await Task.WhenAny(sendTask, timeoutTask);
|
await sendTask;
|
||||||
if (timeoutTask == doneTask) {
|
|
||||||
tcs.TrySetException(new TimeoutException("Send request"));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
tcs.TrySetException(e);
|
tcs.TrySetException(e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
tcs.TrySetException(new TimeoutException("Send request"));
|
||||||
|
}
|
||||||
return await tcs.Task;
|
return await tcs.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,8 +95,7 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
await client.Close();
|
await client.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMessage(byte[] bytes) {
|
private void OnClientMessage(byte[] bytes) {
|
||||||
_ = heartBeat.Update();
|
|
||||||
try {
|
try {
|
||||||
GenericCommand command = GenericCommand.Parser.ParseFrom(bytes);
|
GenericCommand command = GenericCommand.Parser.ParseFrom(bytes);
|
||||||
LCLogger.Debug($"{id} <= {FormatCommand(command)}");
|
LCLogger.Debug($"{id} <= {FormatCommand(command)}");
|
||||||
|
@ -106,14 +123,14 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
OnNotification?.Invoke(command);
|
OnNotification?.Invoke(command);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e.Message);
|
LCLogger.Error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClientDisconnect() {
|
private void OnClientDisconnect() {
|
||||||
OnDisconnect?.Invoke();
|
OnDisconnect?.Invoke();
|
||||||
OnReconnecting?.Invoke();
|
OnReconnecting?.Invoke();
|
||||||
// TODO 重连
|
// 重连
|
||||||
_ = Reconnect();
|
_ = Reconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,13 +142,14 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
try {
|
try {
|
||||||
LCLogger.Debug($"Reconnecting... {reconnectCount}");
|
LCLogger.Debug($"Reconnecting... {reconnectCount}");
|
||||||
await client.Connect();
|
await client.Connect();
|
||||||
|
client.OnMessage = OnClientMessage;
|
||||||
|
client.OnDisconnect = OnClientDisconnect;
|
||||||
break;
|
break;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
reconnectCount++;
|
reconnectCount++;
|
||||||
LCLogger.Error(e.Message);
|
LCLogger.Error(e);
|
||||||
int delay = 10;
|
LCLogger.Debug($"Reconnect after {RECONNECT_INTERVAL}ms");
|
||||||
LCLogger.Debug($"Reconnect after {delay}s");
|
await Task.Delay(RECONNECT_INTERVAL);
|
||||||
await Task.Delay(1000 * delay);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (reconnectCount < MAX_RECONNECT_TIMES) {
|
if (reconnectCount < MAX_RECONNECT_TIMES) {
|
||||||
|
@ -140,8 +158,8 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
OnReconnected?.Invoke();
|
OnReconnected?.Invoke();
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
// TODO 重置连接
|
// 重置 Router,继续尝试重连
|
||||||
client = new LCWebSocketClient();
|
router.Reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,26 +24,22 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly int pongInterval;
|
private readonly int pongInterval;
|
||||||
|
|
||||||
private Action onTimeout;
|
|
||||||
|
|
||||||
private CancellationTokenSource pingCTS;
|
private CancellationTokenSource pingCTS;
|
||||||
private CancellationTokenSource pongCTS;
|
private CancellationTokenSource pongCTS;
|
||||||
|
|
||||||
internal LCHeartBeat(LCConnection connection,
|
internal LCHeartBeat(LCConnection connection,
|
||||||
int pingInterval,
|
int pingInterval,
|
||||||
int pongInterval,
|
int pongInterval) {
|
||||||
Action onTimeout) {
|
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.pingInterval = pingInterval;
|
this.pingInterval = pingInterval;
|
||||||
this.pongInterval = pongInterval;
|
this.pongInterval = pongInterval;
|
||||||
this.onTimeout = onTimeout;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 更新心跳监听
|
/// 更新心跳监听
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal async Task Update() {
|
internal async Task Update(Action onTimeout) {
|
||||||
LCLogger.Debug("HeartBeat update");
|
LCLogger.Debug("HeartBeat update");
|
||||||
pingCTS?.Cancel();
|
pingCTS?.Cancel();
|
||||||
pongCTS?.Cancel();
|
pongCTS?.Cancel();
|
||||||
|
@ -78,7 +74,6 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
/// 停止心跳监听
|
/// 停止心跳监听
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void Stop() {
|
internal void Stop() {
|
||||||
onTimeout = null;
|
|
||||||
pingCTS?.Cancel();
|
pingCTS?.Cancel();
|
||||||
pongCTS?.Cancel();
|
pongCTS?.Cancel();
|
||||||
}
|
}
|
||||||
|
|
|
@ -451,7 +451,7 @@ namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
Data = where
|
Data = where
|
||||||
};
|
};
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e.Message);
|
LCLogger.Error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
command.ConvMessage = convMessage;
|
command.ConvMessage = convMessage;
|
||||||
|
|
|
@ -11,8 +11,7 @@ namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
|
|
||||||
internal override async Task OnNotification(GenericCommand notification) {
|
internal override async Task OnNotification(GenericCommand notification) {
|
||||||
// 清空缓存,断开连接,等待重新连接
|
// 清空缓存,断开连接,等待重新连接
|
||||||
//Connection.Router.Reset();
|
await Connection.Reset();
|
||||||
await Connection.Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
|
@ -6,12 +6,21 @@ using LeanCloud.Common;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal.Router {
|
namespace LeanCloud.Realtime.Internal.Router {
|
||||||
|
/// <summary>
|
||||||
|
/// RTM Router
|
||||||
|
/// </summary>
|
||||||
internal class LCRTMRouter {
|
internal class LCRTMRouter {
|
||||||
|
private const int REQUEST_TIMEOUT = 10000;
|
||||||
|
|
||||||
private LCRTMServer rtmServer;
|
private LCRTMServer rtmServer;
|
||||||
|
|
||||||
internal LCRTMRouter() {
|
internal LCRTMRouter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取服务器地址
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
internal async Task<LCRTMServer> GetServer() {
|
internal async Task<LCRTMServer> GetServer() {
|
||||||
if (rtmServer == null || !rtmServer.IsValid) {
|
if (rtmServer == null || !rtmServer.IsValid) {
|
||||||
await Fetch();
|
await Fetch();
|
||||||
|
@ -19,6 +28,9 @@ namespace LeanCloud.Realtime.Internal.Router {
|
||||||
return rtmServer;
|
return rtmServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重置服务器地址缓存
|
||||||
|
/// </summary>
|
||||||
internal void Reset() {
|
internal void Reset() {
|
||||||
rtmServer = null;
|
rtmServer = null;
|
||||||
}
|
}
|
||||||
|
@ -33,9 +45,14 @@ namespace LeanCloud.Realtime.Internal.Router {
|
||||||
};
|
};
|
||||||
HttpClient client = new HttpClient();
|
HttpClient client = new HttpClient();
|
||||||
LCHttpUtils.PrintRequest(client, request);
|
LCHttpUtils.PrintRequest(client, request);
|
||||||
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
|
||||||
request.Dispose();
|
|
||||||
|
|
||||||
|
Task<HttpResponseMessage> requestTask = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
if (await Task.WhenAny(requestTask, Task.Delay(REQUEST_TIMEOUT)) != requestTask) {
|
||||||
|
throw new TimeoutException("Request timeout.");
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponseMessage response = await requestTask;
|
||||||
|
request.Dispose();
|
||||||
string resultString = await response.Content.ReadAsStringAsync();
|
string resultString = await response.Content.ReadAsStringAsync();
|
||||||
response.Dispose();
|
response.Dispose();
|
||||||
LCHttpUtils.PrintResponse(response, resultString);
|
LCHttpUtils.PrintResponse(response, resultString);
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using LeanCloud.Common;
|
using LeanCloud.Common;
|
||||||
using LeanCloud.Realtime.Internal.Router;
|
using LeanCloud.Realtime.Internal.Router;
|
||||||
|
using LeanCloud.Realtime.Internal.Connection;
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal.WebSocket {
|
namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -14,6 +15,8 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
|
|
||||||
private const int CLOSE_TIMEOUT = 5000;
|
private const int CLOSE_TIMEOUT = 5000;
|
||||||
|
|
||||||
|
private const int CONNECT_TIMEOUT = 10000;
|
||||||
|
|
||||||
internal Action<byte[]> OnMessage;
|
internal Action<byte[]> OnMessage;
|
||||||
|
|
||||||
internal Action OnDisconnect;
|
internal Action OnDisconnect;
|
||||||
|
@ -24,20 +27,27 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
|
|
||||||
private readonly LCRTMRouter router;
|
private readonly LCRTMRouter router;
|
||||||
|
|
||||||
internal LCWebSocketClient() {
|
private readonly LCHeartBeat heartBeat;
|
||||||
router = new LCRTMRouter();
|
|
||||||
|
internal LCWebSocketClient(LCRTMRouter router, LCHeartBeat heartBeat) {
|
||||||
|
this.router = router;
|
||||||
|
this.heartBeat = heartBeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task Connect() {
|
internal async Task Connect() {
|
||||||
|
try {
|
||||||
LCRTMServer rtmServer = await router.GetServer();
|
LCRTMServer rtmServer = await router.GetServer();
|
||||||
try {
|
try {
|
||||||
LCLogger.Debug($"Primary Server");
|
LCLogger.Debug($"Primary Server");
|
||||||
await Connect(rtmServer.Primary);
|
await Connect(rtmServer.Primary);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e.Message);
|
LCLogger.Error(e);
|
||||||
LCLogger.Debug($"Secondary Server");
|
LCLogger.Debug($"Secondary Server");
|
||||||
await Connect(rtmServer.Secondary);
|
await Connect(rtmServer.Secondary);
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
// 接收
|
// 接收
|
||||||
_ = StartReceive();
|
_ = StartReceive();
|
||||||
|
@ -45,7 +55,7 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
|
|
||||||
private async Task Connect(string server) {
|
private async Task Connect(string server) {
|
||||||
LCLogger.Debug($"Connecting WebSocket: {server}");
|
LCLogger.Debug($"Connecting WebSocket: {server}");
|
||||||
Task timeoutTask = Task.Delay(5000);
|
Task timeoutTask = Task.Delay(CONNECT_TIMEOUT);
|
||||||
ws = new ClientWebSocket();
|
ws = new ClientWebSocket();
|
||||||
ws.Options.AddSubProtocol("lc.protobuf2.3");
|
ws.Options.AddSubProtocol("lc.protobuf2.3");
|
||||||
Task connectTask = ws.ConnectAsync(new Uri(server), default);
|
Task connectTask = ws.ConnectAsync(new Uri(server), default);
|
||||||
|
@ -61,14 +71,15 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
OnMessage = null;
|
OnMessage = null;
|
||||||
OnDisconnect = null;
|
OnDisconnect = null;
|
||||||
OnReconnect = null;
|
OnReconnect = null;
|
||||||
|
heartBeat.Stop();
|
||||||
try {
|
try {
|
||||||
// 发送关闭帧可能会很久,所以增加超时
|
// 发送关闭帧可能会很久,所以增加超时
|
||||||
// 主动挥手关闭,不会再收到 Close Frame
|
// 主动挥手关闭,不会再收到 Close Frame
|
||||||
Task closeTask = ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "", default);
|
Task closeTask = ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default);
|
||||||
Task delayTask = Task.Delay(CLOSE_TIMEOUT);
|
Task delayTask = Task.Delay(CLOSE_TIMEOUT);
|
||||||
await Task.WhenAny(closeTask, delayTask);
|
await Task.WhenAny(closeTask, delayTask);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e.Message);
|
LCLogger.Error(e);
|
||||||
} finally {
|
} finally {
|
||||||
ws.Abort();
|
ws.Abort();
|
||||||
ws.Dispose();
|
ws.Dispose();
|
||||||
|
@ -82,7 +93,7 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
try {
|
try {
|
||||||
await ws.SendAsync(bytes, WebSocketMessageType.Binary, true, default);
|
await ws.SendAsync(bytes, WebSocketMessageType.Binary, true, default);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e.Message);
|
LCLogger.Error(e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -99,16 +110,22 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
WebSocketReceiveResult result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), default);
|
WebSocketReceiveResult result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), default);
|
||||||
if (result.MessageType == WebSocketMessageType.Close) {
|
if (result.MessageType == WebSocketMessageType.Close) {
|
||||||
// 由服务端发起关闭
|
// 由服务端发起关闭
|
||||||
LCLogger.Debug($"Receive Closed: {result.CloseStatusDescription}");
|
LCLogger.Debug($"Receive Closed: {result.CloseStatus}");
|
||||||
|
LCLogger.Debug($"ws state: {ws.State}");
|
||||||
|
// 这里有可能是客户端主动关闭,也有可能是服务端主动关闭
|
||||||
|
if (ws.State == WebSocketState.CloseReceived) {
|
||||||
|
// 如果是服务端主动关闭,则挥手关闭,并认为是断线
|
||||||
try {
|
try {
|
||||||
// 挥手关闭
|
|
||||||
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default);
|
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default);
|
||||||
} catch (Exception ex) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(ex.Message);
|
LCLogger.Error(e);
|
||||||
} finally {
|
} finally {
|
||||||
HandleClose();
|
// TODO 正常关闭不知道能否完成???
|
||||||
|
OnDisconnect?.Invoke();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (result.MessageType == WebSocketMessageType.Binary) {
|
} else if (result.MessageType == WebSocketMessageType.Binary) {
|
||||||
|
_ = heartBeat.Update(HandleClose);
|
||||||
// 拼合 WebSocket Message
|
// 拼合 WebSocket Message
|
||||||
int length = result.Count;
|
int length = result.Count;
|
||||||
byte[] data = new byte[length];
|
byte[] data = new byte[length];
|
||||||
|
@ -120,20 +137,18 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 客户端网络异常
|
// 客户端网络异常
|
||||||
LCLogger.Error(e.Message);
|
LCLogger.Error(e);
|
||||||
LCLogger.Debug($"WebSocket State: {ws.State}");
|
OnDisconnect?.Invoke();
|
||||||
HandleClose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleClose() {
|
private void HandleClose() {
|
||||||
try {
|
try {
|
||||||
|
heartBeat.Stop();
|
||||||
ws.Abort();
|
ws.Abort();
|
||||||
ws.Dispose();
|
ws.Dispose();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e.Message);
|
LCLogger.Error(e);
|
||||||
} finally {
|
|
||||||
OnDisconnect?.Invoke();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -455,7 +455,7 @@ namespace LeanCloud.Realtime {
|
||||||
// 回调用户
|
// 回调用户
|
||||||
OnReconnected?.Invoke();
|
OnReconnected?.Invoke();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e.Message);
|
LCLogger.Error(e);
|
||||||
await Connection.Close();
|
await Connection.Close();
|
||||||
OnReconnectError?.Invoke();
|
OnReconnectError?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,7 +165,7 @@ namespace LeanCloud.Storage.Internal.Http {
|
||||||
code = (int)error["code"];
|
code = (int)error["code"];
|
||||||
message = error["error"].ToString();
|
message = error["error"].ToString();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e.Message);
|
LCLogger.Error(e);
|
||||||
}
|
}
|
||||||
return new LCException(code, message);
|
return new LCException(code, message);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue