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(); } } } } }