using System; using System.Threading; using System.Threading.Tasks; using LeanCloud.Common; using LeanCloud.Realtime.Protocol; namespace LeanCloud.Realtime.Internal.Connection { /// /// 心跳控制器,由于 .Net Standard 2.0 不支持发送 ping frame,所以需要发送逻辑心跳 /// 1. 每次接收到消息后开始监听,如果在 pingInterval 时间内没有再次接收到消息,则发送 ping 请求; /// 2. 发送后等待 pongInterval 时间,如果在此时间内接收到了任何消息,则取消并重新开始监听 1; /// 3. 如果没收到消息,则认为超时并回调,连接层接收回调后放弃当前连接,以断线逻辑处理 /// internal class LCHeartBeat { private readonly LCConnection connection; /// /// ping 间隔 /// private readonly int pingInterval; /// /// pong 间隔 /// private readonly int pongInterval; private CancellationTokenSource pingCTS; private CancellationTokenSource pongCTS; internal LCHeartBeat(LCConnection connection, int pingInterval, int pongInterval) { this.connection = connection; this.pingInterval = pingInterval; this.pongInterval = pongInterval; } /// /// 更新心跳监听 /// /// internal async Task Update(Action onTimeout) { LCLogger.Debug("HeartBeat update"); pingCTS?.Cancel(); pongCTS?.Cancel(); // 计时准备 ping pingCTS = new CancellationTokenSource(); Task delayTask = Task.Delay(pingInterval, pingCTS.Token); await delayTask; if (delayTask.IsCanceled) { return; } // 发送 ping 包 LCLogger.Debug("Ping ~~~"); GenericCommand command = new GenericCommand { Cmd = CommandType.Echo, AppId = LCApplication.AppId, PeerId = connection.id }; _ = connection.SendRequest(command); pongCTS = new CancellationTokenSource(); Task timeoutTask = Task.Delay(pongInterval, pongCTS.Token); await timeoutTask; if (timeoutTask.IsCanceled) { return; } // timeout LCLogger.Error("Ping timeout"); onTimeout.Invoke(); } /// /// 停止心跳监听 /// internal void Stop() { pingCTS?.Cancel(); pongCTS?.Cancel(); } } }