using System;
using System.Text;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using LeanCloud.Realtime.Internal.Router;
using LeanCloud.Realtime.Internal.WebSocket;
using LeanCloud.Common;
using LeanCloud.Storage;
namespace LeanCloud.LiveQuery.Internal {
public class LCLiveQueryConnection {
///
/// 发送超时
///
private const int SEND_TIMEOUT = 10000;
///
/// 最大重连次数,超过后重置 Router 缓存后再次尝试重连
///
private const int MAX_RECONNECT_TIMES = 10;
///
/// 重连间隔
///
private const int RECONNECT_INTERVAL = 10000;
///
/// 子协议
///
private const string SUB_PROTOCOL = "lc.json.3";
///
/// 通知事件
///
internal Action> OnNotification;
///
/// 断线事件
///
internal Action OnDisconnect;
///
/// 重连成功事件
///
internal Action OnReconnected;
internal string id;
///
/// 请求回调缓存
///
private readonly Dictionary>> responses;
private int requestI = 1;
private LCRTMRouter router;
private LCLiveQueryHeartBeat heartBeat;
private LCWebSocketClient client;
public LCLiveQueryConnection(string id) {
this.id = id;
responses = new Dictionary>>();
heartBeat = new LCLiveQueryHeartBeat(this);
router = new LCRTMRouter();
client = new LCWebSocketClient {
OnMessage = OnClientMessage,
OnClose = OnClientDisconnect
};
}
public async Task Connect() {
try {
LCRTMServer rtmServer = await router.GetServer();
try {
LCLogger.Debug($"Primary Server");
await client.Connect(rtmServer.Primary, SUB_PROTOCOL);
} catch (Exception e) {
LCLogger.Error(e);
LCLogger.Debug($"Secondary Server");
await client.Connect(rtmServer.Secondary, SUB_PROTOCOL);
}
} catch (Exception e) {
throw e;
}
}
///
/// 重置连接
///
///
internal async Task Reset() {
// 关闭就连接
await client.Close();
// 重新创建连接组件
heartBeat = new LCLiveQueryHeartBeat(this);
router = new LCRTMRouter();
client = new LCWebSocketClient {
OnMessage = OnClientMessage,
OnClose = OnClientDisconnect
};
await Reconnect();
}
///
/// 发送请求,会在收到应答后返回
///
///
///
internal async Task> SendRequest(Dictionary request) {
TaskCompletionSource> tcs = new TaskCompletionSource>();
int requestIndex = requestI++;
request["i"] = requestIndex;
responses.Add(requestIndex, tcs);
try {
string json = JsonConvert.SerializeObject(request);
await SendText(json);
} catch (Exception e) {
tcs.TrySetException(e);
}
return await tcs.Task;
}
///
/// 发送文本消息
///
///
///
internal async Task SendText(string text) {
LCLogger.Debug($"{id} => {text}");
Task sendTask = client.Send(text);
if (await Task.WhenAny(sendTask, Task.Delay(SEND_TIMEOUT)) == sendTask) {
await sendTask;
} else {
throw new TimeoutException("Send request time out");
}
}
///
/// 关闭连接
///
///
internal async Task Close() {
OnNotification = null;
OnDisconnect = null;
OnReconnected = null;
heartBeat.Stop();
await client.Close();
}
private void OnClientMessage(byte[] bytes) {
_ = heartBeat.Refresh(OnPingTimeout);
try {
string json = Encoding.UTF8.GetString(bytes);
Dictionary msg = JsonConvert.DeserializeObject>(json,
LCJsonConverter.Default);
LCLogger.Debug($"{id} <= {json}");
if (msg.TryGetValue("i", out object i)) {
int requestIndex = Convert.ToInt32(i);
if (responses.TryGetValue(requestIndex, out TaskCompletionSource> tcs)) {
if (msg.TryGetValue("error", out object error)) {
// 错误
if (error is Dictionary dict) {
int code = Convert.ToInt32(dict["code"]);
string detail = dict["detail"] as string;
tcs.SetException(new LCException(code, detail));
} else {
tcs.SetException(new Exception(error as string));
}
} else {
tcs.SetResult(msg);
}
responses.Remove(requestIndex);
} else {
LCLogger.Error($"No request for {requestIndex}");
}
} else {
// 通知
OnNotification?.Invoke(msg);
}
} catch (Exception e) {
LCLogger.Error(e);
}
}
private void OnClientDisconnect() {
heartBeat.Stop();
OnDisconnect?.Invoke();
// 重连
_ = Reconnect();
}
private async void OnPingTimeout() {
await client.Close();
OnClientDisconnect();
}
private async Task Reconnect() {
while (true) {
int reconnectCount = 0;
// 重连策略
while (reconnectCount < MAX_RECONNECT_TIMES) {
try {
LCLogger.Debug($"Reconnecting... {reconnectCount}");
await Connect();
break;
} catch (Exception e) {
reconnectCount++;
LCLogger.Error(e);
LCLogger.Debug($"Reconnect after {RECONNECT_INTERVAL}ms");
await Task.Delay(RECONNECT_INTERVAL);
}
}
if (reconnectCount < MAX_RECONNECT_TIMES) {
// 重连成功
LCLogger.Debug("Reconnected");
client.OnMessage = OnClientMessage;
client.OnClose = OnClientDisconnect;
OnReconnected?.Invoke();
break;
} else {
// 重置 Router,继续尝试重连
router = new LCRTMRouter();
}
}
}
}
}