using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; using LeanCloud; using System.Reflection; using LeanCloud.Realtime.Internal; using LeanCloud.Storage.Internal; using System.Threading; #if UNITY using UnityEngine; #endif namespace LeanCloud.Realtime { /// /// 实时消息的框架类 /// 包含了 WebSocket 连接以及事件通知的管理 /// public class AVRealtime { internal static IDictionary clients = null; private static readonly object mutex = new object(); private string _wss; private string _secondaryWss; private string _sesstionToken; private long _sesstionTokenExpire; private string _clientId; private string _deviceId; private bool _secure; private string _tag; private string subprotocolPrefix = "lc.json."; static readonly int RECONNECT_DELAY = 5 * 1000; static readonly int RECONNECT_USE_SECONDARY_TIMES = 6; static readonly int RECONNECT_FROM_APP_ROUTER = 12; int reconnectTimes; public bool IsSesstionTokenExpired { get { return DateTime.Now.ToUnixTimeStamp() > _sesstionTokenExpire; } } private IAVIMCommandRunner avIMCommandRunner; /// /// /// public IAVIMCommandRunner AVIMCommandRunner { get { lock (mutex) { avIMCommandRunner = avIMCommandRunner ?? new AVIMCommandRunner(this.AVWebSocketClient); return avIMCommandRunner; } } } private IWebSocketClient webSocketController; internal IWebSocketClient AVWebSocketClient { get { lock (mutex) { webSocketController = webSocketController ?? new DefaultWebSocketClient(); return webSocketController; } } set { lock (mutex) { webSocketController = value; } } } internal static IAVRouterController RouterController { get { return AVIMCorePlugins.Instance.RouterController; } } internal static IFreeStyleMessageClassingController FreeStyleMessageClassingController { get { return AVIMCorePlugins.Instance.FreeStyleClassingController; } } /// /// /// public event EventHandler OnOfflineMessageReceived; /// /// 与云端通讯的状态 /// public enum Status { /// /// 未初始化 /// None = -1, /// /// 正在连接 /// Connecting = 0, /// /// 已连接 /// Online = 1, /// /// 连接已断开 /// Offline = 2, /// /// 正在重连 /// Reconnecting = 3, /// /// websocket 连接已被打开 /// Opened = 98, /// /// 已主动关闭 /// Closed = 99, } private AVRealtime.Status state = Status.None; public AVRealtime.Status State { get { return state; } private set { state = value; } } private struct NetworkStateOptions { public bool Available { get; set; } } private NetworkStateOptions NetworkState { get; set; } private struct WebSocketStateOptions { public int ClosedCode { get; set; } } private WebSocketStateOptions WebSocketState { get; set; } /// /// /// public struct AVIMReconnectOptions { /// /// 重连的时间间隔,单位是秒 /// public long Interval { get; set; } /// /// 重连的次数 /// public int Retry { get; set; } } internal string Subprotocol { get { return subprotocolPrefix + (int)CurrentConfiguration.OfflineMessageStrategy; } } /// /// 重连选项 /// public AVIMReconnectOptions ReconnectOptions { get; set; } private ISignatureFactory _signatureFactory; /// /// 签名接口 /// public ISignatureFactory SignatureFactory { get { _signatureFactory = _signatureFactory ?? new DefaulSiganatureFactory(); return _signatureFactory; } set { _signatureFactory = value; } } private bool useLeanEngineSignaturFactory; /// /// 启用 LeanEngine 云函数签名 /// public void UseLeanEngineSignatureFactory() { useLeanEngineSignaturFactory = true; this.SignatureFactory = new LeanEngineSignatureFactory(); } private EventHandler m_OnDisconnected; /// /// 连接断开触发的事件 /// 如果其他客户端使用了相同的 Tag 登录,就会导致当前用户被服务端断开 /// public event EventHandler OnDisconnected { add { m_OnDisconnected += value; } remove { m_OnDisconnected -= value; } } private EventHandler m_OnReconnecting; /// /// 正在重连时触发的事件 /// public event EventHandler OnReconnecting { add { m_OnReconnecting += value; } remove { m_OnReconnecting -= value; } } private EventHandler m_OnReconnected; /// /// 重连之后触发的事件 /// public event EventHandler OnReconnected { add { m_OnReconnected += value; } remove { m_OnReconnected -= value; } } private EventHandler m_OnReconnectFailed; /// /// 重连失败之后触发的事件 /// public event EventHandler OnReconnectFailed { add { m_OnReconnectFailed += value; } remove { m_OnReconnectFailed -= value; } } /// /// Invokes the state of the network. /// /// If set to true broken. internal void InvokeNetworkState(bool available = false) { if (this.NetworkState.Available == available) return; SetNetworkState(available); PrintLog(string.Format("network connectivity is {0} now", available)); // 如果断线产生的原因是客户端掉线而不是服务端踢下线,则应该开始自动重连 var reasonShouldReconnect = new int[] { 0, 1006, 4107 }; if (this.NetworkState.Available && reasonShouldReconnect.Contains(this.WebSocketState.ClosedCode)) { StartAutoReconnect(); } } internal void SetNetworkState(bool available = true) { this.NetworkState = new NetworkStateOptions() { Available = available }; } private EventHandler m_NoticeReceived; public event EventHandler NoticeReceived { add { m_NoticeReceived += value; } remove { m_NoticeReceived -= value; } } private void WebSocketClient_OnMessage(string obj) { try { var estimatedData = Json.Parse(obj) as IDictionary; var validator = AVIMNotice.IsValidLeanCloudProtocol(estimatedData); if (validator) { var notice = new AVIMNotice(estimatedData); m_NoticeReceived?.Invoke(this, notice); } } catch (Exception ex) { if (ex.InnerException != null) { PrintLog(ex.InnerException.Source); } if (ex.Source != null) { PrintLog(ex.Source); } PrintLog(ex.Message); } } /// /// 设定监听者 /// /// /// public void SubscribeNoticeReceived(IAVIMListener listener, Func runtimeHook = null) { this.NoticeReceived += new EventHandler((sender, notice) => { var approved = runtimeHook == null ? listener.ProtocolHook(notice) : runtimeHook(notice) && listener.ProtocolHook(notice); if (approved) { listener.OnNoticeReceived(notice); } }); } /// /// 初始化配置项 /// public struct Configuration { /// /// 签名工厂 /// public ISignatureFactory SignatureFactory { get; set; } /// /// 自定义 WebSocket 客户端 /// public IWebSocketClient WebSocketClient { get; set; } /// /// LeanCloud App Id /// public string ApplicationId { get; set; } /// /// LeanCloud App Key /// public string ApplicationKey { get; set; } /// /// 登录的时候告知服务器,本次登录所使用的离线消息策略 /// public OfflineMessageStrategy OfflineMessageStrategy { get; set; } } /// ///登录时的离线消息下发策略 /// public enum OfflineMessageStrategy { /// /// 服务器将所有离线消息一次性在登录之后马上下发下来 /// Default = 1, /// /// 不再下发未读消息,而是下发对话的未读通知,告知客户端有哪些对话处于未读状态 /// [Obsolete("该策略已被废弃,请使用 UnreadAck")] UnreadNotice = 2, /// /// ack 和 read 分离, ack 不会清理未读消息 /// UnreadAck = 3 } /// /// 当前配置 /// public Configuration CurrentConfiguration { get; internal set; } /// /// 初始化实时消息客户端 /// /// public AVRealtime(Configuration config) { lock (mutex) { if ((int)config.OfflineMessageStrategy == 0) { config.OfflineMessageStrategy = OfflineMessageStrategy.UnreadAck; } CurrentConfiguration = config; if (CurrentConfiguration.WebSocketClient != null) { webSocketController = CurrentConfiguration.WebSocketClient; } if (CurrentConfiguration.SignatureFactory != null) { this.SignatureFactory = CurrentConfiguration.SignatureFactory; } ReconnectOptions = new AVIMReconnectOptions() { Interval = 5, Retry = 120 }; RegisterMessageType(); RegisterMessageType(); RegisterMessageType(); RegisterMessageType(); RegisterMessageType(); RegisterMessageType(); RegisterMessageType(); RegisterMessageType(); RegisterMessageType(); // 注册服务端 goaway 指令 var goAwayListener = new GoAwayListener(); goAwayListener.OnGoAway += () => { RouterController.ClearCache().ContinueWith(_ => { reborn = true; // 关闭 WebSocket AVWebSocketClient.Disconnect(); }); }; SubscribeNoticeReceived(goAwayListener); reconnectTimes = 0; } } /// /// 初始化实时消息客户端 /// /// /// public AVRealtime(string applicationId, string applicationKey) : this(new Configuration() { ApplicationId = applicationId, ApplicationKey = applicationKey, OfflineMessageStrategy = OfflineMessageStrategy.UnreadAck }) { } #region websocket log internal static Action LogTracker { get; private set; } /// /// 打开 WebSocket 日志 /// /// public static void WebSocketLog(Action trace) { LogTracker = trace; } public static void PrintLog(string log) { if (AVRealtime.LogTracker != null) { AVRealtime.LogTracker(log); } } #endregion /// /// 创建 Client /// /// /// /// 设备唯一的 Id。如果是 iOS 设备,需要将 iOS 推送使用的 DeviceToken 作为 deviceId 传入 /// 是否强制加密 wss 链接 /// /// public Task CreateClientAsync(string clientId, string tag = null, string deviceId = null, bool secure = true, CancellationToken cancellationToken = default(CancellationToken)) { lock (mutex) { var client = PreLogIn(clientId, tag, deviceId); AVRealtime.PrintLog("begin OpenAsync."); return OpenAsync(secure, Subprotocol, true, cancellationToken).OnSuccess(t => { if (!t.Result) { return Task.FromResult(null); } AVRealtime.PrintLog("websocket server connected, begin to open sesstion."); SetNetworkState(); var cmd = new SessionCommand() .UA(VersionString) .Tag(tag) .DeviceId(deviceId) .Option("open") .PeerId(clientId); ToggleNotification(true); return AttachSignature(cmd, this.SignatureFactory.CreateConnectSignature(clientId)); }).Unwrap().OnSuccess(x => { var cmd = x.Result; if (cmd == null) { return Task.FromResult>>(null); } return this.RunCommandAsync(cmd); }).Unwrap().OnSuccess(s => { if (s.Result == null) { return null; } AVRealtime.PrintLog("sesstion opened."); state = Status.Online; ToggleHeartBeating(true); var response = s.Result.Item2; if (response.ContainsKey("st")) { _sesstionToken = response["st"] as string; } if (response.ContainsKey("stTtl")) { var stTtl = long.Parse(response["stTtl"].ToString()); _sesstionTokenExpire = DateTime.Now.ToUnixTimeStamp() + stTtl * 1000; } AfterLogIn(client); return client; }); } } /// /// Creates the client async. /// /// The client async. /// User. /// Tag. /// Device identifier. /// If set to true secure. public Task CreateClientAsync(AVUser user = null, string tag = null, string deviceId = null, bool secure = true, CancellationToken cancellationToken = default(CancellationToken)) { AVIMClient client = null; AVRealtime.PrintLog("begin OpenAsync."); return OpenAsync(secure, Subprotocol, true, cancellationToken).OnSuccess(openTask => { AVRealtime.PrintLog("OpenAsync OnSuccess. begin send open sesstion cmd."); var userTask = Task.FromResult(user); if (user == null) userTask = AVUser.GetCurrentUserAsync(); return userTask; }).Unwrap().OnSuccess(u => { var theUser = u.Result; return AVCloud.RequestRealtimeSignatureAsync(theUser); }).Unwrap().OnSuccess(signTask => { var signResult = signTask.Result; var clientId = signResult.ClientId; var nonce = signResult.Nonce; var singnature = signResult.Signature; var ts = signResult.Timestamp; client = PreLogIn(clientId, tag, deviceId); ToggleNotification(true); return this.OpenSessionAsync(clientId, tag, deviceId, nonce, ts, singnature, secure); }).Unwrap().OnSuccess(s => { ToggleHeartBeating(true); AfterLogIn(client); return client; }); } #region pre-login internal AVIMClient PreLogIn(string clientId, string tag = null, string deviceId = null) { var client = new AVIMClient(clientId, tag, this); if (this.OnOfflineMessageReceived != null) { client.OnOfflineMessageReceived += this.OnOfflineMessageReceived; } _clientId = clientId; _tag = tag; _deviceId = deviceId; if (_tag != null) { if (deviceId == null) throw new ArgumentNullException(deviceId, "当 tag 不为空时,必须传入当前设备不变的唯一 id(deviceId)"); } if (string.IsNullOrEmpty(clientId)) throw new Exception("当前 ClientId 为空,无法登录服务器。"); return client; } internal void AfterLogIn(AVIMClient client) { if (clients == null) clients = new Dictionary(); client.OnSessionClosed += (sender, e) => { string clientId = (sender as AVIMClient).ClientId; clients.Remove(clientId); if (clients.Count == 0) { LogOut(); } }; clients[client.ClientId] = client; } #endregion #region after-login #endregion /// /// 创建 Client /// /// /// /// 设备唯一的 Id。如果是 iOS 设备,需要将 iOS 推送使用的 DeviceToken 作为 deviceId 传入 /// 是否强制加密 wss 链接 /// /// [Obsolete("CreateClient is deprecated, please use CreateClientAsync instead.")] public Task CreateClient( string clientId, string tag = null, string deviceId = null, bool secure = true, CancellationToken cancellationToken = default(CancellationToken)) { return this.CreateClientAsync(clientId, tag, deviceId, secure, cancellationToken); } private bool _listening = false; /// /// websocket 事件的监听的开关 /// /// 是否打开 public void ToggleNotification(bool toggle) { AVRealtime.PrintLog("ToggleNotification| toggle:" + toggle + "|listening: " + _listening); if (toggle && !_listening) { AVWebSocketClient.OnClosed += WebsocketClient_OnClosed; AVWebSocketClient.OnMessage += WebSocketClient_OnMessage; _listening = true; } else if (!toggle && _listening) { AVWebSocketClient.OnClosed -= WebsocketClient_OnClosed; AVWebSocketClient.OnMessage -= WebSocketClient_OnMessage; _listening = false; } } //public void ToggleOfflineNotification(bool toggle) //{ // if (toggle) // { // PCLWebsocketClient.OnMessage += WebSocketClient_OnMessage_On_Session_Opening; // } // else // { // PCLWebsocketClient.OnMessage -= WebSocketClient_OnMessage_On_Session_Opening; // } //} //private void WebSocketClient_OnMessage_On_Session_Opening(string obj) //{ // AVRealtime.PrintLog("offline<=" + obj); //} string _beatPacket = "{}"; bool _heartBeatingToggle = true; IAVTimer _heartBeatingTimer; /// /// 主动发送心跳包 /// /// 是否开启 /// 时间间隔 /// 心跳包的内容,默认是个空的 {} public void ToggleHeartBeating(bool toggle = true, double interval = 60000, string beatPacket = "{}") { this._heartBeatingToggle = toggle; if (!string.Equals(_beatPacket, beatPacket)) _beatPacket = beatPacket; if (_heartBeatingTimer == null && this._heartBeatingToggle) { _heartBeatingTimer = new AVTimer(); _heartBeatingTimer.Elapsed += SendHeartBeatingPacket; _heartBeatingTimer.Interval = interval; _heartBeatingTimer.Start(); PrintLog("auto heart beating started."); } if (!this._heartBeatingToggle && _heartBeatingTimer != null) { _heartBeatingTimer.Stop(); _heartBeatingTimer = null; } } void SendHeartBeatingPacket(object sender, TimerEventArgs e) { PrintLog("auto heart beating ticked by timer."); #if MONO || UNITY Dispatcher.Instance.Post(() => { KeepAlive(); }); #else KeepAlive(); #endif } /// /// Keeps the alive. /// public void KeepAlive() { try { var cmd = new AVIMCommand(); RunCommandAsync(cmd).ContinueWith(t => { if (t.IsCanceled || t.IsFaulted || t.Exception != null) { InvokeNetworkState(); } }); } catch (Exception) { InvokeNetworkState(); } } internal bool sessionConflict = false; internal bool loggedOut = false; internal bool CanReconnect { get { return !sessionConflict && !loggedOut && state == Status.Offline; } } /// /// 开始自动重连 /// public void StartAutoReconnect() { } internal bool useSecondary = false; internal bool reborn = false; internal Task LogInAsync(string clientId, string tag = null, string deviceId = null, bool secure = true, CancellationToken cancellationToken = default(CancellationToken)) { lock (mutex) { var cmd = new SessionCommand() .UA(VersionString) .Tag(tag) .DeviceId(deviceId) .Option("open") .PeerId(clientId); var result = AttachSignature(cmd, this.SignatureFactory.CreateConnectSignature(clientId)).OnSuccess(_ => { return RunCommandAsync(cmd); }).Unwrap().OnSuccess(t => { AVRealtime.PrintLog("sesstion opened."); if (t.Exception != null) { var imException = t.Exception.InnerException as AVIMException; throw imException; } state = Status.Online; var response = t.Result.Item2; if (response.ContainsKey("st")) { _sesstionToken = response["st"] as string; } if (response.ContainsKey("stTtl")) { var stTtl = long.Parse(response["stTtl"].ToString()); _sesstionTokenExpire = DateTime.Now.ToUnixTimeStamp() + stTtl * 1000; } return t.Result; }); return result; } } internal Task OpenSessionAsync(string clientId, string tag = null, string deviceId = null, string nonce = null, long timestamp = 0, string signature = null, bool secure = true) { var cmd = new SessionCommand() .UA(VersionString) .Tag(tag) .DeviceId(deviceId) .Option("open") .PeerId(clientId) .Argument("n", nonce) .Argument("t", timestamp) .Argument("s", signature); return RunCommandAsync(cmd).OnSuccess(t => { AVRealtime.PrintLog("sesstion opened."); if (t.Exception != null) { var imException = t.Exception.InnerException as AVIMException; throw imException; } state = Status.Online; var response = t.Result.Item2; if (response.ContainsKey("st")) { _sesstionToken = response["st"] as string; } if (response.ContainsKey("stTtl")) { var stTtl = long.Parse(response["stTtl"].ToString()); _sesstionTokenExpire = DateTime.Now.ToUnixTimeStamp() + stTtl * 1000; } return t.Result; }); } /// /// 自动重连 /// /// Task AutoReconnect() { AVRealtime.PrintLog("AutoReconnect started."); var reconnectingArgs = new AVIMReconnectingEventArgs() { ClientId = _clientId, IsAuto = true, SessionToken = _sesstionToken }; m_OnReconnecting?.Invoke(this, reconnectingArgs); var tcs = new TaskCompletionSource(); Task task; if (reborn) { AVRealtime.PrintLog("both preferred and secondary websockets are expired, so try to request RTM router to get a new pair"); task = OpenAsync(this._secure, Subprotocol, true); } else { var websocketServer = _wss; if (useSecondary) { AVRealtime.PrintLog(string.Format("preferred websocket server ({0}) network broken, take secondary server({1}) :", _wss, _secondaryWss)); websocketServer = _secondaryWss; } task = OpenAsync(websocketServer, Subprotocol, true); } task.ContinueWith(t => { if (t.IsFaulted || t.IsCanceled) { state = Status.Reconnecting; var reconnectFailedArgs = new AVIMReconnectFailedArgs() { ClientId = _clientId, IsAuto = true, SessionToken = _sesstionToken, FailedCode = 0// network broken. }; m_OnReconnectFailed?.Invoke(this, reconnectFailedArgs); state = Status.Offline; tcs.SetException(t.Exception); throw t.Exception; } else { state = Status.Opened; SetNetworkState(); void onClose(int code, string reason, string detail) { AVRealtime.PrintLog("disconnect when open session"); var ex = new Exception("connection is closed"); tcs.SetException(ex); AVWebSocketClient.OnClosed -= onClose; throw ex; }; AVWebSocketClient.OnClosed += onClose; if (this.IsSesstionTokenExpired) { AVRealtime.PrintLog("session is expired, auto relogin with clientId :" + _clientId); return this.LogInAsync(_clientId, this._tag, this._deviceId, this._secure).ContinueWith(o => { AVWebSocketClient.OnClosed -= onClose; return !o.IsFaulted; }); } else { var sessionCMD = new SessionCommand().UA(VersionString).R(1); if (string.IsNullOrEmpty(_tag)) { sessionCMD = sessionCMD.Tag(_tag).SessionToken(this._sesstionToken); } var cmd = sessionCMD.Option("open") .PeerId(_clientId); AVRealtime.PrintLog("reopen session with session token :" + _sesstionToken); return RunCommandAsync(cmd).ContinueWith(o => { AVWebSocketClient.OnClosed -= onClose; return !o.IsFaulted; }); } } }).Unwrap().ContinueWith(s => { if (s.IsFaulted || s.Exception != null) { var reconnectFailedArgs = new AVIMReconnectFailedArgs() { ClientId = _clientId, IsAuto = true, SessionToken = _sesstionToken, FailedCode = 1 }; m_OnReconnectFailed?.Invoke(this, reconnectFailedArgs); state = Status.Offline; tcs.SetException(s.Exception); } else { var reconnectedArgs = new AVIMReconnectedEventArgs() { ClientId = _clientId, IsAuto = true, SessionToken = _sesstionToken, }; state = Status.Online; m_OnReconnected?.Invoke(this, reconnectedArgs); ToggleNotification(true); ToggleHeartBeating(true); tcs.SetResult(true); } }); return tcs.Task; } #region register IAVIMMessage /// /// Registers the subtype of the message. /// /// The 1st type parameter. public void RegisterMessageType() where T : IAVIMMessage { AVIMCorePlugins.Instance.FreeStyleClassingController.RegisterSubclass(typeof(T)); } #endregion /// /// open websocket with default configurations. /// /// public Task OpenAsync(bool secure = true) { return this.OpenAsync(secure, null); } /// /// Open websocket connection. /// /// The async. /// If set to true secure. /// Subprotocol. /// If set to true enforce. /// Cancellation token. public Task OpenAsync(bool secure, string subprotocol = null, bool enforce = false, CancellationToken cancellationToken = default(CancellationToken)) { _secure = secure; if (state == Status.Online && !enforce) { AVRealtime.PrintLog("state is Status.Online."); return Task.FromResult(true); } if (AVClient.CurrentConfiguration.RealtimeServer != null) { _wss = AVClient.CurrentConfiguration.RealtimeServer; AVRealtime.PrintLog("use configuration realtime server with url: " + _wss); return OpenAsync(_wss, subprotocol, enforce); } var routerUrl = AVClient.CurrentConfiguration.RTMServer; return RouterController.GetAsync(routerUrl, secure, cancellationToken).OnSuccess(r => { var routerState = r.Result; if (routerState == null) { return Task.FromResult(false); } _wss = routerState.server; _secondaryWss = routerState.secondary; state = Status.Connecting; AVRealtime.PrintLog("push router give a url :" + _wss); return OpenAsync(routerState.server, subprotocol, enforce); }).Unwrap(); } /// /// open webcoket connection with cloud. /// /// wss address /// subprotocol for websocket /// /// public Task OpenAsync(string url, string subprotocol = null, bool enforce = false, CancellationToken cancellationToken = default(CancellationToken)) { if (AVWebSocketClient.IsOpen && !enforce) { AVRealtime.PrintLog(url + "is already connectd."); return Task.FromResult(true); } AVRealtime.PrintLog("websocket try to connect url :" + url + " with subprotocol: " + subprotocol); AVRealtime.PrintLog(url + " \tconnecting..."); return AVWebSocketClient.Connect(url, subprotocol); } /// /// send websocket command to Realtime server. /// /// /// public Task>> RunCommandAsync(AVIMCommand command) { command.AppId(AVClient.CurrentConfiguration.ApplicationId); return this.AVIMCommandRunner.RunCommandAsync(command); } /// /// /// /// public void RunCommand(AVIMCommand command) { command.AppId(AVClient.CurrentConfiguration.ApplicationId); this.AVIMCommandRunner.RunCommand(command); } internal Task AttachSignature(AVIMCommand command, Task SignatureTask) { AVRealtime.PrintLog("begin to attach singature."); var tcs = new TaskCompletionSource(); if (SignatureTask == null) { tcs.SetResult(command); return tcs.Task; } return SignatureTask.OnSuccess(_ => { if (_.Result != null) { var signature = _.Result; command.Argument("t", signature.Timestamp); command.Argument("n", signature.Nonce); command.Argument("s", signature.SignatureContent); AVRealtime.PrintLog("AttachSignature ended.t:" + signature.Timestamp + ";n:" + signature.Nonce + ";s:" + signature.SignatureContent); } return command; }); } #region log out and clean event subscribtion private void WebsocketClient_OnClosed(int errorCode, string reason, string detail) { PrintLog(string.Format("websocket closed with code is {0},reason is {1} and detail is {2}", errorCode, reason, detail)); state = Status.Offline; ToggleNotification(false); ToggleHeartBeating(false); var disconnectEventArgs = new AVIMDisconnectEventArgs(errorCode, reason, detail); m_OnDisconnected?.Invoke(this, disconnectEventArgs); this.WebSocketState = new WebSocketStateOptions() { ClosedCode = errorCode }; PrepareReconnect(); } private void WebsocketClient_OnError(string obj) { PrintLog("error:" + obj); // 如果遇到 WebSocket 错误之后,先关闭,再按断线处理 AVWebSocketClient.Close(); WebsocketClient_OnClosed(0, obj, string.Empty); } void PrepareReconnect() { AVRealtime.PrintLog("Prepare Reconnect"); Task.Delay(RECONNECT_DELAY).ContinueWith(_ => { // 开启重连 AutoReconnect().ContinueWith(t => { if (t.IsFaulted) { // 重连失败,延迟再次重连 reconnectTimes++; AVRealtime.PrintLog(String.Format("reconnect {0} times", reconnectTimes)); if (reconnectTimes >= RECONNECT_FROM_APP_ROUTER) { // 如果大于当前服务地址的最大重连次数,则清空 Router 后重新重连 RouterController.ClearCache().ContinueWith(__ => { reborn = true; PrepareReconnect(); }); } else if (reconnectTimes >= RECONNECT_USE_SECONDARY_TIMES) { // 如果大于单台 IM 服务器的重连次数,则启用备用服务器 useSecondary = true; PrepareReconnect(); } else { PrepareReconnect(); } } else { // 重连成功 reconnectTimes = 0; reborn = false; useSecondary = false; } }); }); } internal void LogOut() { State = Status.Closed; loggedOut = true; Dispose(); AVWebSocketClient.Close(); } internal void Dispose() { var toggle = false; ToggleNotification(toggle); ToggleHeartBeating(toggle); if (m_NoticeReceived != null) { foreach (Delegate d in m_NoticeReceived.GetInvocationList()) { m_NoticeReceived -= (EventHandler)d; } } if (m_OnDisconnected != null) { foreach (Delegate d in m_OnDisconnected.GetInvocationList()) { m_OnDisconnected -= (EventHandler)d; } } } #endregion static AVRealtime() { #if MONO || UNITY versionString = "net-unity/" + Version; #else versionString = "net-universal/" + Version; #endif } private static readonly string versionString; internal static string VersionString { get { return versionString; } } internal static System.Version Version { get { AssemblyName assemblyName = new AssemblyName(typeof(AVRealtime).GetTypeInfo().Assembly.FullName); return assemblyName.Version; } } } }