2020-04-10 16:32:33 +08:00
|
|
|
|
using System;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Google.Protobuf;
|
2020-04-13 17:29:55 +08:00
|
|
|
|
using LeanCloud.Realtime.Internal.Router;
|
2020-04-10 16:32:33 +08:00
|
|
|
|
using LeanCloud.Realtime.Internal.WebSocket;
|
|
|
|
|
using LeanCloud.Realtime.Protocol;
|
|
|
|
|
using LeanCloud.Common;
|
|
|
|
|
using LeanCloud.Storage;
|
|
|
|
|
|
|
|
|
|
namespace LeanCloud.Realtime.Internal.Connection {
|
2020-04-13 10:47:14 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 连接层,只与数据协议相关
|
|
|
|
|
/// </summary>
|
2020-04-10 16:32:33 +08:00
|
|
|
|
internal class LCConnection {
|
2020-04-14 14:51:14 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 发送超时
|
|
|
|
|
/// </summary>
|
2020-04-10 16:32:33 +08:00
|
|
|
|
private const int SEND_TIMEOUT = 10000;
|
|
|
|
|
|
2020-04-14 14:51:14 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 最大重连次数,超过后重置 Router 缓存后再次尝试重连
|
|
|
|
|
/// </summary>
|
2020-04-13 17:29:55 +08:00
|
|
|
|
private const int MAX_RECONNECT_TIMES = 3;
|
|
|
|
|
|
2020-04-14 14:51:14 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 重连间隔
|
|
|
|
|
/// </summary>
|
2020-04-14 15:28:01 +08:00
|
|
|
|
private const int RECONNECT_INTERVAL = 10000;
|
2020-04-13 17:29:55 +08:00
|
|
|
|
|
2020-04-14 14:51:14 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 心跳间隔
|
|
|
|
|
/// </summary>
|
2020-04-13 17:29:55 +08:00
|
|
|
|
private const int HEART_BEAT_INTERVAL = 5000;
|
2020-04-10 16:32:33 +08:00
|
|
|
|
|
|
|
|
|
internal Action<GenericCommand> OnNotification;
|
|
|
|
|
|
|
|
|
|
internal Action OnDisconnect;
|
|
|
|
|
|
|
|
|
|
internal Action OnReconnecting;
|
|
|
|
|
|
|
|
|
|
internal Action OnReconnected;
|
|
|
|
|
|
|
|
|
|
internal string id;
|
|
|
|
|
|
|
|
|
|
private readonly Dictionary<int, TaskCompletionSource<GenericCommand>> responses;
|
|
|
|
|
|
|
|
|
|
private int requestI = 1;
|
|
|
|
|
|
2020-04-13 17:29:55 +08:00
|
|
|
|
private LCRTMRouter router;
|
|
|
|
|
|
|
|
|
|
private LCHeartBeat heartBeat;
|
|
|
|
|
|
2020-04-10 16:32:33 +08:00
|
|
|
|
private LCWebSocketClient client;
|
|
|
|
|
|
|
|
|
|
internal LCConnection(string id) {
|
|
|
|
|
this.id = id;
|
|
|
|
|
responses = new Dictionary<int, TaskCompletionSource<GenericCommand>>();
|
2020-04-13 17:29:55 +08:00
|
|
|
|
heartBeat = new LCHeartBeat(this, HEART_BEAT_INTERVAL, HEART_BEAT_INTERVAL);
|
|
|
|
|
router = new LCRTMRouter();
|
|
|
|
|
client = new LCWebSocketClient(router, heartBeat) {
|
|
|
|
|
OnMessage = OnClientMessage,
|
2020-04-10 16:32:33 +08:00
|
|
|
|
OnDisconnect = OnClientDisconnect
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal async Task Connect() {
|
|
|
|
|
await client.Connect();
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-13 17:29:55 +08:00
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-10 16:32:33 +08:00
|
|
|
|
internal async Task<GenericCommand> SendRequest(GenericCommand request) {
|
|
|
|
|
TaskCompletionSource<GenericCommand> tcs = new TaskCompletionSource<GenericCommand>();
|
|
|
|
|
request.I = requestI++;
|
|
|
|
|
responses.Add(request.I, tcs);
|
|
|
|
|
LCLogger.Debug($"{id} => {FormatCommand(request)}");
|
|
|
|
|
byte[] bytes = request.ToByteArray();
|
|
|
|
|
Task sendTask = client.Send(bytes);
|
2020-04-13 17:29:55 +08:00
|
|
|
|
if (await Task.WhenAny(sendTask, Task.Delay(SEND_TIMEOUT)) == sendTask) {
|
|
|
|
|
try {
|
|
|
|
|
await sendTask;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
tcs.TrySetException(e);
|
2020-04-10 16:32:33 +08:00
|
|
|
|
}
|
2020-04-13 17:29:55 +08:00
|
|
|
|
} else {
|
|
|
|
|
tcs.TrySetException(new TimeoutException("Send request"));
|
2020-04-10 16:32:33 +08:00
|
|
|
|
}
|
|
|
|
|
return await tcs.Task;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal async Task Close() {
|
|
|
|
|
OnNotification = null;
|
|
|
|
|
OnDisconnect = null;
|
|
|
|
|
heartBeat.Stop();
|
|
|
|
|
await client.Close();
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-13 17:29:55 +08:00
|
|
|
|
private void OnClientMessage(byte[] bytes) {
|
2020-04-10 16:32:33 +08:00
|
|
|
|
try {
|
|
|
|
|
GenericCommand command = GenericCommand.Parser.ParseFrom(bytes);
|
|
|
|
|
LCLogger.Debug($"{id} <= {FormatCommand(command)}");
|
|
|
|
|
if (command.HasI) {
|
|
|
|
|
// 应答
|
|
|
|
|
int requestIndex = command.I;
|
|
|
|
|
if (responses.TryGetValue(requestIndex, out TaskCompletionSource<GenericCommand> tcs)) {
|
|
|
|
|
if (command.HasErrorMessage) {
|
|
|
|
|
// 错误
|
|
|
|
|
ErrorCommand error = command.ErrorMessage;
|
|
|
|
|
int code = error.Code;
|
|
|
|
|
string detail = error.Detail;
|
|
|
|
|
// 包装成异常抛出
|
|
|
|
|
LCException exception = new LCException(code, detail);
|
|
|
|
|
tcs.TrySetException(exception);
|
|
|
|
|
} else {
|
|
|
|
|
tcs.TrySetResult(command);
|
|
|
|
|
}
|
|
|
|
|
responses.Remove(requestIndex);
|
|
|
|
|
} else {
|
|
|
|
|
LCLogger.Error($"No request for {requestIndex}");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 通知
|
|
|
|
|
OnNotification?.Invoke(command);
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
2020-04-13 17:29:55 +08:00
|
|
|
|
LCLogger.Error(e);
|
2020-04-10 16:32:33 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnClientDisconnect() {
|
|
|
|
|
OnDisconnect?.Invoke();
|
|
|
|
|
OnReconnecting?.Invoke();
|
2020-04-13 17:29:55 +08:00
|
|
|
|
// 重连
|
2020-04-10 16:32:33 +08:00
|
|
|
|
_ = Reconnect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task Reconnect() {
|
|
|
|
|
while (true) {
|
|
|
|
|
int reconnectCount = 0;
|
|
|
|
|
// 重连策略
|
|
|
|
|
while (reconnectCount < MAX_RECONNECT_TIMES) {
|
|
|
|
|
try {
|
|
|
|
|
LCLogger.Debug($"Reconnecting... {reconnectCount}");
|
|
|
|
|
await client.Connect();
|
2020-04-13 17:29:55 +08:00
|
|
|
|
client.OnMessage = OnClientMessage;
|
|
|
|
|
client.OnDisconnect = OnClientDisconnect;
|
2020-04-10 16:32:33 +08:00
|
|
|
|
break;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
reconnectCount++;
|
2020-04-13 17:29:55 +08:00
|
|
|
|
LCLogger.Error(e);
|
|
|
|
|
LCLogger.Debug($"Reconnect after {RECONNECT_INTERVAL}ms");
|
|
|
|
|
await Task.Delay(RECONNECT_INTERVAL);
|
2020-04-10 16:32:33 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (reconnectCount < MAX_RECONNECT_TIMES) {
|
|
|
|
|
// 重连成功
|
|
|
|
|
LCLogger.Debug("Reconnected");
|
|
|
|
|
OnReconnected?.Invoke();
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
2020-04-13 17:29:55 +08:00
|
|
|
|
// 重置 Router,继续尝试重连
|
|
|
|
|
router.Reset();
|
2020-04-10 16:32:33 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string FormatCommand(GenericCommand command) {
|
|
|
|
|
StringBuilder sb = new StringBuilder($"{command.Cmd}");
|
|
|
|
|
if (command.HasOp) {
|
|
|
|
|
sb.Append($"/{command.Op}");
|
|
|
|
|
}
|
|
|
|
|
sb.Append($"\n{command}");
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|