internal async Task <GenericCommand> SendRequest(GenericCommand request) { if (IsIdempotentCommand(request)) { GenericCommand sendingReq = requestToResponses.Keys.FirstOrDefault(item => { // TRICK 除了 I 其他字段相等 request.I = item.I; return(Equals(request, item)); }); if (sendingReq != null) { LCLogger.Warn("duplicated request"); if (requestToResponses.TryGetValue(sendingReq, out TaskCompletionSource <GenericCommand> waitingTcs)) { return(await waitingTcs.Task); } LCLogger.Error($"error request: {request}"); } } TaskCompletionSource <GenericCommand> tcs = new TaskCompletionSource <GenericCommand>(); request.I = requestI++; requestToResponses.Add(request, tcs); try { await SendCommand(request); } catch (Exception e) { tcs.TrySetException(e); } return(await tcs.Task); }
private void OnMessage(byte[] bytes, int length) { try { GenericCommand command = GenericCommand.Parser.ParseFrom(bytes, 0, length); LCLogger.Debug($"{id} <= {FormatCommand(command)}"); if (command.HasI) { // 应答 int requestIndex = command.I; if (requestToResponses.TryGetValue(command, out TaskCompletionSource <GenericCommand> tcs)) { requestToResponses.Remove(command); 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); } } else { LCLogger.Error($"No request for {requestIndex}"); } } else { if (command.Cmd == CommandType.Echo) { // 心跳应答 heartBeat.Pong(); } else if (command.Cmd == CommandType.Goaway) { // 针对连接的消息 Reset(); } else { // 通知 string peerId = command.HasPeerId ? command.PeerId : defaultClientId; if (idToClients.TryGetValue(peerId, out LCIMClient client)) { // 通知具体客户端 client.HandleNotification(command); } } } } catch (Exception e) { LCLogger.Error(e); } }
private static async Task SaveToLocal() { try { string json = currentUser.ToString(); await LCCore.PersistenceController.WriteText(USER_DATA, json); } catch (Exception e) { LCLogger.Error(e.Message); } }
/// <summary> /// Signing in /// </summary> /// <param name="force">If this is ture (default value), and single device sign-on is enabled, users already logged in on another device with the same tag will be logged out.</param> /// <returns></returns> public async Task Open(bool force = true) { try { // 打开 Session await SessionController.Open(force); } catch (Exception e) { LCLogger.Error(e); // 如果 session 阶段异常,则关闭连接 throw e; } }
private void HandleExceptionClose() { try { ws.Abort(); ws.Dispose(); } catch (Exception e) { LCLogger.Error(e); } finally { OnClose?.Invoke(); } }
private async Task StartReceive() { byte[] recvBuffer = new byte[RECV_BUFFER_SIZE]; byte[] msgBuffer = new byte[MSG_BUFFER_SIZE]; int offset = 0; try { while (ws.State == WebSocketState.Open) { WebSocketReceiveResult result = await ws.ReceiveAsync(new ArraySegment <byte>(recvBuffer), default); if (result.MessageType == WebSocketMessageType.Close) { LCLogger.Debug($"Receive Closed: {result.CloseStatus}"); if (ws.State == WebSocketState.CloseReceived) { // 如果是服务端主动关闭,则挥手关闭,并认为是断线 try { Task closeTask = ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default); await Task.WhenAny(closeTask, Task.Delay(CLOSE_TIMEOUT)); } catch (Exception e) { LCLogger.Error(e); } finally { HandleExceptionClose(); } } } else { // 拼合 WebSocket Message int length = result.Count; if (offset + length > msgBuffer.Length) { // 反序列化数组大小不够,则以 2x 扩充 byte[] newBuffer = new byte[msgBuffer.Length * 2]; Array.Copy(msgBuffer, newBuffer, msgBuffer.Length); msgBuffer = newBuffer; } Array.Copy(recvBuffer, 0, msgBuffer, offset, length); offset += length; if (result.EndOfMessage) { OnMessage?.Invoke(msgBuffer, offset); offset = 0; } } } } catch (Exception e) { // 客户端网络异常 LCLogger.Error(e); HandleExceptionClose(); } }
private void OnClientMessage(byte[] bytes, int length) { try { string json = Encoding.UTF8.GetString(bytes, 0, length); Dictionary <string, object> msg = JsonConvert.DeserializeObject <Dictionary <string, object> >(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 <Dictionary <string, object> > tcs)) { if (msg.TryGetValue("error", out object error)) { // 错误 if (error is Dictionary <string, object> 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 { if (json == "{}") { heartBeat.Pong(); } else { // 通知 OnNotification?.Invoke(msg); } } } catch (Exception e) { LCLogger.Error(e); } }
protected virtual void SendPing() { // 发送 ping 包 GenericCommand command = new GenericCommand { Cmd = CommandType.Echo, AppId = LCCore.AppId, PeerId = connection.id }; try { _ = connection.SendCommand(command); } catch (Exception e) { LCLogger.Error(e.Message); } }
LCException HandleErrorResponse(HttpStatusCode statusCode, string responseContent) { int code = (int)statusCode; string message = responseContent; try { // 尝试获取 LeanCloud 返回错误信息 Dictionary <string, object> error = JsonConvert.DeserializeObject <Dictionary <string, object> >(responseContent, LCJsonConverter.Default); code = (int)error["code"]; message = error["error"].ToString(); } catch (Exception e) { LCLogger.Error(e); } return(new LCException(code, message)); }
public async Task Close() { LCLogger.Debug("Closing WebSocket"); OnMessage = null; OnClose = null; try { // 发送关闭帧可能会很久,所以增加超时 // 主动挥手关闭,不会再收到 Close Frame Task closeTask = ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default); Task delayTask = Task.Delay(CLOSE_TIMEOUT); await Task.WhenAny(closeTask, delayTask); } catch (Exception e) { LCLogger.Error(e); } finally { ws.Abort(); ws.Dispose(); LCLogger.Debug("Closed WebSocket"); } }
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); } // 启动心跳 heartBeat.Start(); } catch (Exception e) { throw e; } }
/// <summary> /// Gets the currently logged in LCUser with a valid session, from /// memory or disk if necessary. /// </summary> /// <returns></returns> public static async Task <LCUser> GetCurrent() { if (currentUser != null) { return(currentUser); } string data = await LCCore.PersistenceController.ReadText(USER_DATA); if (!string.IsNullOrEmpty(data)) { try { currentUser = ParseObject(data) as LCUser; } catch (Exception e) { LCLogger.Error(e); await LCCore.PersistenceController.Delete(USER_DATA); } } return(currentUser); }
public async Task Send(byte[] data, WebSocketMessageType messageType = WebSocketMessageType.Binary) { ArraySegment <byte> bytes = new ArraySegment <byte>(data); if (ws.State == WebSocketState.Open) { try { await ws.SendAsync(bytes, messageType, true, default); } catch (Exception e) { LCLogger.Error(e); throw e; } } else { string message = $"Error Websocket state: {ws.State}"; LCLogger.Error(message); throw new Exception(message); } }
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"); ws.OnMessage = OnMessage; ws.OnClose = OnDisconnect; foreach (LCIMClient client in idToClients.Values) { client.HandleReconnected(); } break; } else { // 重置 Router,继续尝试重连 router = new LCRTMRouter(); } } }
internal async Task ConnectInternal() { state = State.Connecting; try { LCRTMServer rtmServer = await router.GetServer(); try { LCLogger.Debug($"Primary Server"); await ws.Connect(rtmServer.Primary, SUB_PROTOCOL); } catch (Exception e) { LCLogger.Error(e); LCLogger.Debug($"Secondary Server"); await ws.Connect(rtmServer.Secondary, SUB_PROTOCOL); } // 启动心跳 heartBeat.Start(); state = State.Open; } catch (Exception e) { state = State.Closed; throw e; } }
async Task <LCAppServer> FetchAppServer() { // 判断节点地区 if (!IsInternalApp(appId)) { // 国内节点必须配置自定义域名 throw new Exception("Please init with your server url."); } // 向 App Router 请求地址 if (appServer == null || !appServer.IsValid) { try { HttpRequestMessage request = new HttpRequestMessage { RequestUri = new Uri($"https://app-router.com/2/route?appId={appId}"), Method = HttpMethod.Get }; HttpClient client = new HttpClient(); LCHttpUtils.PrintRequest(client, request); HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); request.Dispose(); string resultString = await response.Content.ReadAsStringAsync(); response.Dispose(); LCHttpUtils.PrintResponse(response, resultString); Dictionary <string, object> data = JsonConvert.DeserializeObject <Dictionary <string, object> >(resultString); appServer = new LCAppServer(data); } catch (Exception e) { LCLogger.Error(e); // 拉取服务地址失败后,使用国际节点的默认服务地址 appServer = LCAppServer.GetInternalFallbackAppServer(appId); } } return(appServer); }
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(); } } }
internal async Task <ReadOnlyCollection <LCIMConversation> > Find(LCIMConversationQuery query) { GenericCommand command = new GenericCommand { Cmd = CommandType.Conv, Op = OpType.Query, AppId = LCCore.AppId, PeerId = Client.Id, }; ConvCommand convMessage = new ConvCommand(); string where = query.Condition.BuildWhere(); if (!string.IsNullOrEmpty(where)) { try { convMessage.Where = new JsonObjectMessage { Data = where }; } catch (Exception e) { LCLogger.Error(e); } } int flag = 0; if (query.Compact) { flag += LCIMConversationQuery.CompactFlag; } if (query.WithLastMessageRefreshed) { flag += LCIMConversationQuery.WithLastMessageFlag; } if (flag > 0) { convMessage.Flag = flag; } convMessage.Skip = query.Condition.Skip; convMessage.Limit = query.Condition.Limit; string orders = query.Condition.BuildOrders(); if (!string.IsNullOrEmpty(orders)) { convMessage.Sort = orders; } command.ConvMessage = convMessage; GenericCommand response = await Connection.SendRequest(command); JsonObjectMessage results = response.ConvMessage.Results; List <object> convs = JsonConvert.DeserializeObject <List <object> >(results.Data, LCJsonConverter.Default); return(convs.Select(item => { Dictionary <string, object> conv = item as Dictionary <string, object>; string convId = conv["objectId"] as string; if (!Client.ConversationDict.TryGetValue(convId, out LCIMConversation conversation)) { // 解析是哪种类型的对话 if (conv.TryGetValue("tr", out object transient) && (bool)transient == true) { conversation = new LCIMChatRoom(Client); } else if (conv.ContainsKey("tempConv") && conv.ContainsKey("tempConvTTL")) { conversation = new LCIMTemporaryConversation(Client); } else if (conv.TryGetValue("sys", out object sys) && (bool)sys == true) { conversation = new LCIMServiceConversation(Client); } else { conversation = new LCIMConversation(Client); } Client.ConversationDict[convId] = conversation; } conversation.MergeFrom(conv); return conversation; }).ToList().AsReadOnly()); }