263 lines
9.9 KiB
C#
263 lines
9.9 KiB
C#
|
|
using System;
|
||
|
|
using System.Threading;
|
||
|
|
using System.Threading.Tasks;
|
||
|
|
using Serilog;
|
||
|
|
|
||
|
|
namespace Protocol376Simulator.Services
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// 重连服务类,负责处理自动重连逻辑
|
||
|
|
/// </summary>
|
||
|
|
public class ReconnectionService
|
||
|
|
{
|
||
|
|
private readonly NetworkService _networkService;
|
||
|
|
private readonly string _deviceIdentifier;
|
||
|
|
private CancellationTokenSource _reconnectCancellationTokenSource;
|
||
|
|
private Task _reconnectTask;
|
||
|
|
private readonly object _reconnectLock = new object();
|
||
|
|
|
||
|
|
// 重连配置
|
||
|
|
private bool _autoReconnect = true;
|
||
|
|
private int _reconnectAttempts = 0;
|
||
|
|
private int _maxReconnectAttempts = 5;
|
||
|
|
private TimeSpan _reconnectDelay = TimeSpan.FromSeconds(5);
|
||
|
|
private TimeSpan _reconnectIncreaseDelay = TimeSpan.FromSeconds(5);
|
||
|
|
private DateTime _lastReconnectTime = DateTime.MinValue;
|
||
|
|
private TimeSpan _minReconnectInterval = TimeSpan.FromSeconds(30);
|
||
|
|
private bool _isReconnecting = false;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 当重连尝试开始时触发
|
||
|
|
/// </summary>
|
||
|
|
public event EventHandler<int> ReconnectAttemptStarted;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 当重连尝试结束时触发
|
||
|
|
/// </summary>
|
||
|
|
public event EventHandler<bool> ReconnectAttemptCompleted;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 是否启用自动重连
|
||
|
|
/// </summary>
|
||
|
|
public bool AutoReconnect
|
||
|
|
{
|
||
|
|
get => _autoReconnect;
|
||
|
|
set => _autoReconnect = value;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 重连尝试次数
|
||
|
|
/// </summary>
|
||
|
|
public int ReconnectAttempts => _reconnectAttempts;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 是否正在重连
|
||
|
|
/// </summary>
|
||
|
|
public bool IsReconnecting => _isReconnecting;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 构造函数
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="networkService">网络服务</param>
|
||
|
|
/// <param name="deviceIdentifier">设备标识(用于日志)</param>
|
||
|
|
public ReconnectionService(NetworkService networkService, string deviceIdentifier)
|
||
|
|
{
|
||
|
|
_networkService = networkService ?? throw new ArgumentNullException(nameof(networkService));
|
||
|
|
_deviceIdentifier = deviceIdentifier;
|
||
|
|
|
||
|
|
// 订阅网络服务的连接状态变更事件
|
||
|
|
_networkService.ConnectionStatusChanged += OnConnectionStatusChanged;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 设置重连参数
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="autoReconnect">是否启用自动重连</param>
|
||
|
|
/// <param name="maxAttempts">最大重连尝试次数</param>
|
||
|
|
/// <param name="delaySeconds">重连延迟(秒)</param>
|
||
|
|
public void SetReconnectParameters(bool autoReconnect, int maxAttempts, int delaySeconds)
|
||
|
|
{
|
||
|
|
_autoReconnect = autoReconnect;
|
||
|
|
_maxReconnectAttempts = maxAttempts;
|
||
|
|
_reconnectDelay = TimeSpan.FromSeconds(delaySeconds);
|
||
|
|
_reconnectIncreaseDelay = TimeSpan.FromSeconds(delaySeconds);
|
||
|
|
|
||
|
|
Log.Information("{DeviceId} 已设置重连参数: 自动重连={AutoReconnect}, 最大尝试次数={MaxAttempts}, 延迟={Delay}秒",
|
||
|
|
_deviceIdentifier, _autoReconnect, _maxReconnectAttempts, delaySeconds);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 连接状态变更处理
|
||
|
|
/// </summary>
|
||
|
|
private void OnConnectionStatusChanged(object sender, NetworkService.ConnectionStatus status)
|
||
|
|
{
|
||
|
|
if (status == NetworkService.ConnectionStatus.Failed ||
|
||
|
|
status == NetworkService.ConnectionStatus.Disconnected)
|
||
|
|
{
|
||
|
|
// 如果启用了自动重连,开始重连任务
|
||
|
|
if (_autoReconnect)
|
||
|
|
{
|
||
|
|
StartReconnectAsync();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (status == NetworkService.ConnectionStatus.Connected)
|
||
|
|
{
|
||
|
|
// 连接成功,重置重连尝试次数
|
||
|
|
_reconnectAttempts = 0;
|
||
|
|
StopReconnect();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 开始重连任务
|
||
|
|
/// </summary>
|
||
|
|
private void StartReconnectAsync()
|
||
|
|
{
|
||
|
|
lock (_reconnectLock)
|
||
|
|
{
|
||
|
|
// 如果已经在重连中,不要重复启动
|
||
|
|
if (_isReconnecting)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 如果距离上次重连时间太短,不要立即重连
|
||
|
|
TimeSpan timeSinceLastReconnect = DateTime.Now - _lastReconnectTime;
|
||
|
|
if (timeSinceLastReconnect < _minReconnectInterval)
|
||
|
|
{
|
||
|
|
Log.Information("{DeviceId} 距离上次重连尝试时间太短,将等待 {WaitTime} 秒后再尝试",
|
||
|
|
_deviceIdentifier, (_minReconnectInterval - timeSinceLastReconnect).TotalSeconds);
|
||
|
|
|
||
|
|
// 等待一段时间后再次检查是否需要重连
|
||
|
|
Task.Delay(_minReconnectInterval - timeSinceLastReconnect)
|
||
|
|
.ContinueWith(_ => StartReconnectAsync());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
_isReconnecting = true;
|
||
|
|
_reconnectCancellationTokenSource?.Cancel();
|
||
|
|
_reconnectCancellationTokenSource = new CancellationTokenSource();
|
||
|
|
|
||
|
|
_reconnectTask = ReconnectAsync(_reconnectCancellationTokenSource.Token);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 停止重连任务
|
||
|
|
/// </summary>
|
||
|
|
private void StopReconnect()
|
||
|
|
{
|
||
|
|
lock (_reconnectLock)
|
||
|
|
{
|
||
|
|
_reconnectCancellationTokenSource?.Cancel();
|
||
|
|
_isReconnecting = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 重连任务实现
|
||
|
|
/// </summary>
|
||
|
|
private async Task ReconnectAsync(CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
// 记录开始重连的时间
|
||
|
|
_lastReconnectTime = DateTime.Now;
|
||
|
|
|
||
|
|
while (!cancellationToken.IsCancellationRequested)
|
||
|
|
{
|
||
|
|
_reconnectAttempts++;
|
||
|
|
|
||
|
|
// 检查是否超过最大尝试次数
|
||
|
|
if (_maxReconnectAttempts > 0 && _reconnectAttempts > _maxReconnectAttempts)
|
||
|
|
{
|
||
|
|
Log.Warning("{DeviceId} 已达到最大重连尝试次数 {MaxAttempts},停止重连",
|
||
|
|
_deviceIdentifier, _maxReconnectAttempts);
|
||
|
|
|
||
|
|
lock (_reconnectLock)
|
||
|
|
{
|
||
|
|
_isReconnecting = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
ReconnectAttemptCompleted?.Invoke(this, false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Log.Information("{DeviceId} 尝试重连 (第 {Attempt}/{MaxAttempts} 次)...",
|
||
|
|
_deviceIdentifier, _reconnectAttempts, _maxReconnectAttempts > 0 ? _maxReconnectAttempts.ToString() : "∞");
|
||
|
|
|
||
|
|
// 触发重连开始事件
|
||
|
|
ReconnectAttemptStarted?.Invoke(this, _reconnectAttempts);
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
// 尝试重新连接
|
||
|
|
await _networkService.ConnectAsync();
|
||
|
|
|
||
|
|
// 如果连接成功,退出重连循环
|
||
|
|
Log.Information("{DeviceId} 重连成功", _deviceIdentifier);
|
||
|
|
|
||
|
|
lock (_reconnectLock)
|
||
|
|
{
|
||
|
|
_isReconnecting = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 触发重连完成事件
|
||
|
|
ReconnectAttemptCompleted?.Invoke(this, true);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
Log.Warning("{DeviceId} 重连失败: {ErrorMessage}", _deviceIdentifier, ex.Message);
|
||
|
|
|
||
|
|
// 触发重连完成事件(失败)
|
||
|
|
ReconnectAttemptCompleted?.Invoke(this, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 计算下一次重连的延迟时间(逐渐增加)
|
||
|
|
TimeSpan currentDelay = TimeSpan.FromMilliseconds(
|
||
|
|
_reconnectDelay.TotalMilliseconds +
|
||
|
|
(_reconnectAttempts - 1) * _reconnectIncreaseDelay.TotalMilliseconds);
|
||
|
|
|
||
|
|
Log.Information("{DeviceId} 将在 {Delay} 秒后重试...",
|
||
|
|
_deviceIdentifier, currentDelay.TotalSeconds);
|
||
|
|
|
||
|
|
// 等待一段时间后重试
|
||
|
|
try
|
||
|
|
{
|
||
|
|
await Task.Delay(currentDelay, cancellationToken);
|
||
|
|
}
|
||
|
|
catch (OperationCanceledException)
|
||
|
|
{
|
||
|
|
// 任务被取消,退出循环
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 如果是因为取消而退出循环
|
||
|
|
if (cancellationToken.IsCancellationRequested)
|
||
|
|
{
|
||
|
|
lock (_reconnectLock)
|
||
|
|
{
|
||
|
|
_isReconnecting = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
Log.Information("{DeviceId} 重连任务已取消", _deviceIdentifier);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 强制立即尝试重连
|
||
|
|
/// </summary>
|
||
|
|
public async Task ForceReconnectAsync()
|
||
|
|
{
|
||
|
|
// 停止当前的重连任务
|
||
|
|
StopReconnect();
|
||
|
|
|
||
|
|
// 等待一段时间确保任务停止
|
||
|
|
await Task.Delay(100);
|
||
|
|
|
||
|
|
// 重置计数并开始新的重连
|
||
|
|
_reconnectAttempts = 0;
|
||
|
|
StartReconnectAsync();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|