Files
MoviconHub/MoviconHub.App/Services/WebSocketClient.cs
2025-09-15 17:59:48 +08:00

562 lines
18 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using HslCommunication;
using MoviconHub.App.Com;
using MoviconHub.App.Models;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MoviconHub.App.Services
{
/// <summary>
/// WebSocket客户端类基于HslCommunication库实现
/// </summary>
public class WebSocketClient : IDisposable
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly WebSocketConfig _config;
private HslCommunication.WebSocket.WebSocketClient _client;
private Timer _heartbeatTimer;
private Timer _reconnectTimer;
private bool _isConnected = false;
private bool _reconnecting = false;
private bool _disposed = false;
private int _reconnectAttempts = 0;
private readonly int _maxReconnectInterval = 300000; // 最大重连间隔默认5分钟
private readonly object _lockObject = new object();
// 事件定义
public event EventHandler<MoviconHub.App.Models.WebSocketMessageEventArgs> MessageReceived;
public event EventHandler<WebSocketConnectEventArgs> Connected;
public event EventHandler<WebSocketConnectEventArgs> Disconnected;
public event EventHandler<WebSocketErrorEventArgs> Error;
/// <summary>
/// 初始化WebSocket客户端
/// </summary>
public WebSocketClient()
{
_config = WebSocketConfig.LoadConfig();
InitializeClient();
}
/// <summary>
/// 初始化WebSocket客户端
/// </summary>
private void InitializeClient()
{
try
{
// 初始化客户端并设置服务器地址和端口
_client = new HslCommunication.WebSocket.WebSocketClient(_config.ServerAddress, _config.ServerPort, _config.Url);
// 设置日志记录器
_client.LogNet = new HslCommunication.LogNet.LogNetSingle("websocket_logs.txt");
// 注册消息接收事件处理
_client.OnClientApplicationMessageReceive += (message) =>
{
try
{
Logger.Debug($"收到消息: {message.ToString()}");
// 尝试解析消息
MoviconHub.App.Models.WebSocketMessage wsMessage =
JsonConvert.DeserializeObject<MoviconHub.App.Models.WebSocketMessage>(message.ToString());
if (wsMessage != null)
{
// 触发消息接收事件
MessageReceived?.Invoke(this, new MoviconHub.App.Models.WebSocketMessageEventArgs
{
Message = wsMessage,
RawMessage = message.ToString()
});
}
}
catch (Exception ex)
{
Logger.Error(ex, "处理接收到的WebSocket消息时发生错误");
}
};
// 注册连接成功事件
_client.OnClientConnected += () =>
{
Logger.Info($"已成功连接到WebSocket服务器 {_config.ServerAddress}:{_config.ServerPort}");
_isConnected = true;
// 启动心跳定时器
//StartHeartbeatTimer();
// 触发连接事件
Connected?.Invoke(this, new WebSocketConnectEventArgs
{
ServerAddress = _config.ServerAddress,
ServerPort = _config.ServerPort,
IsReconnection = _reconnectAttempts > 0
});
};
// 注册网络错误事件 - 根据文档示例调整参数
_client.OnNetworkError += (sender, e) =>
{
string errorMessage = "WebSocket网络连接错误";
Logger.Error(errorMessage);
HandleDisconnection();
Error?.Invoke(this, new WebSocketErrorEventArgs { ErrorMessage = errorMessage });
};
}
catch (Exception ex)
{
Logger.Error(ex, "初始化WebSocket客户端时发生错误");
}
}
/// <summary>
/// 连接到WebSocket服务器
/// </summary>
/// <returns>连接结果</returns>
public async Task<bool> ConnectAsync()
{
if (_disposed)
throw new ObjectDisposedException(nameof(WebSocketClient));
if (_isConnected)
return true;
try
{
lock (_lockObject)
{
if (_isConnected)
return true;
_reconnecting = false;
_reconnectAttempts = 0;
}
// 确保设置了服务器地址和端口
_client.IpAddress = _config.ServerAddress;
_client.Port = _config.ServerPort;
Logger.Info($"正在连接到WebSocket服务器: ws://{_config.ServerAddress}:{_config.ServerPort}");
// 创建一个任务来包装同步连接方法
var tcs = new TaskCompletionSource<bool>();
var connectTask = Task.Run(() =>
{
try
{
var result = _client.ConnectServer();
if (result.IsSuccess)
{
tcs.SetResult(true);
}
else
{
Logger.Warn($"WebSocket连接失败: {result.Message}");
tcs.SetResult(false);
}
}
catch (Exception ex)
{
Logger.Error(ex, "WebSocket连接过程中发生异常");
tcs.SetResult(false);
}
});
// 添加超时控制
var timeoutTask = Task.Delay(_config.ConnectionTimeout);
var completedTask = await Task.WhenAny(tcs.Task, timeoutTask);
if (completedTask == timeoutTask)
{
Logger.Warn($"连接到WebSocket服务器 {_config.ServerAddress}:{_config.ServerPort} 超时");
Error?.Invoke(this, new WebSocketErrorEventArgs
{
Exception = new TimeoutException("WebSocket连接超时"),
ErrorMessage = "连接超时"
});
if (_config.AutoReconnect)
StartReconnectTimer();
return false;
}
bool connectionSuccess = await tcs.Task;
if (!connectionSuccess && _config.AutoReconnect)
{
StartReconnectTimer();
}
return connectionSuccess;
}
catch (Exception ex)
{
Logger.Error(ex, $"连接到WebSocket服务器 {_config.ServerAddress}:{_config.ServerPort} 时发生错误");
Error?.Invoke(this, new WebSocketErrorEventArgs { Exception = ex, ErrorMessage = ex.Message });
if (_config.AutoReconnect)
StartReconnectTimer();
return false;
}
}
/// <summary>
/// 断开与WebSocket服务器的连接
/// </summary>
public void Disconnect()
{
if (_disposed)
return;
try
{
// 停止定时器
StopHeartbeatTimer();
StopReconnectTimer();
if (_isConnected)
{
_client.ConnectClose();
_isConnected = false;
Logger.Info("已断开与WebSocket服务器的连接");
Disconnected?.Invoke(this, new WebSocketConnectEventArgs
{
ServerAddress = _config.ServerAddress,
ServerPort = _config.ServerPort,
IsReconnection = false
});
}
}
catch (Exception ex)
{
Logger.Error(ex, "断开WebSocket连接时发生错误");
}
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="message">要发送的消息</param>
/// <returns>是否发送成功</returns>
public bool SendMessage(MoviconHub.App.Models.WebSocketMessage message)
{
if (_disposed)
throw new ObjectDisposedException(nameof(WebSocketClient));
if (!_isConnected)
{
Logger.Warn("未连接到WebSocket服务器无法发送消息");
return false;
}
try
{
string json = JsonConvert.SerializeObject(message);
var sendResult = _client.SendServer(json);
if (sendResult.IsSuccess)
{
Logger.Debug($"消息已发送: {json}");
return true;
}
else
{
Logger.Warn($"发送消息失败: {sendResult.Message}");
HandleDisconnection();
return false;
}
}
catch (Exception ex)
{
Logger.Error(ex, "发送WebSocket消息时发生错误");
HandleDisconnection();
return false;
}
}
/// <summary>
/// 发送设备数据
/// 全量数据
/// </summary>
/// <param name="statusData">状态数据</param>
/// <returns>是否发送成功</returns>
public bool SendDeviceData(WebSocketData webSocketData)
{
MoviconHub.App.Models.WebSocketMessage message =
MoviconHub.App.Models.WebSocketMessage.CreateDeviceData( webSocketData);
return SendMessage(message);
}
/// <summary>
/// 发送设备状态数据
/// </summary>
/// <param name="statusData">状态数据</param>
/// <returns>是否发送成功</returns>
public bool SendDeviceStatus(DeviceStatusData statusData)
{
MoviconHub.App.Models.WebSocketMessage message =
MoviconHub.App.Models.WebSocketMessage.CreateDeviceStatusMessage(_config.DeviceCode, statusData);
return SendMessage(message);
}
/// <summary>
/// 发送设备故障信息
/// </summary>
/// <param name="faultData">故障数据</param>
/// <returns>是否发送成功</returns>
public bool SendDeviceFault(FaultDetails faultData)
{
MoviconHub.App.Models.WebSocketMessage message =
MoviconHub.App.Models.WebSocketMessage.CreateFaultMessage(_config.DeviceCode, faultData);
return SendMessage(message);
}
/// <summary>
/// 发送测试数据
/// </summary>
/// <param name="testData">测试数据</param>
/// <returns>是否发送成功</returns>
public bool SendTestData(TestData testData)
{
MoviconHub.App.Models.WebSocketMessage message =
MoviconHub.App.Models.WebSocketMessage.CreateTestDataMessage(_config.DeviceCode, testData);
return SendMessage(message);
}
/// <summary>
/// 更新配置
/// </summary>
/// <param name="serverAddress">服务器地址</param>
/// <param name="port">端口</param>
/// <param name="deviceCode">设备编码</param>
/// <param name="reconnect">是否重连</param>
public async Task UpdateConfig(string serverAddress, int port, string deviceCode, bool reconnect = true)
{
bool wasConnected = _isConnected;
if (wasConnected)
Disconnect();
_config.ServerAddress = serverAddress;
_config.ServerPort = port;
_config.DeviceCode = deviceCode;
_config.SaveConfig();
// 重新创建客户端
_client.ConnectClose();
InitializeClient();
if (wasConnected && reconnect)
await ConnectAsync();
}
/// <summary>
/// 处理断开连接情况
/// </summary>
private void HandleDisconnection()
{
if (!_isConnected)
return;
lock (_lockObject)
{
if (!_isConnected)
return;
_isConnected = false;
StopHeartbeatTimer();
Logger.Warn("WebSocket连接已断开");
Disconnected?.Invoke(this, new WebSocketConnectEventArgs
{
ServerAddress = _config.ServerAddress,
ServerPort = _config.ServerPort,
IsReconnection = false
});
if (_config.AutoReconnect && !_reconnecting)
{
StartReconnectTimer();
}
}
}
/// <summary>
/// 启动心跳定时器
/// </summary>
private void StartHeartbeatTimer()
{
StopHeartbeatTimer();
_heartbeatTimer = new Timer(SendHeartbeat, null,
_config.HeartbeatInterval,
_config.HeartbeatInterval);
Logger.Debug($"心跳定时器已启动,间隔: {_config.HeartbeatInterval}毫秒");
}
/// <summary>
/// 停止心跳定时器
/// </summary>
private void StopHeartbeatTimer()
{
_heartbeatTimer?.Dispose();
_heartbeatTimer = null;
}
/// <summary>
/// 发送心跳消息
/// </summary>
private void SendHeartbeat(object state)
{
if (!_isConnected)
return;
try
{
MoviconHub.App.Models.WebSocketMessage heartbeat =
MoviconHub.App.Models.WebSocketMessage.CreateHeartbeat(_config.DeviceCode);
SendMessage(heartbeat);
Logger.Debug("已发送心跳消息");
}
catch (Exception ex)
{
Logger.Error(ex, "发送心跳消息时发生错误");
}
}
/// <summary>
/// 启动重连定时器
/// </summary>
private void StartReconnectTimer()
{
lock (_lockObject)
{
if (_reconnecting)
return;
_reconnecting = true;
StopReconnectTimer();
_reconnectTimer = new Timer(ReconnectCallback, null,
_config.ReconnectInterval,
Timeout.Infinite); // 只执行一次,在重连方法中再次设置定时器
Logger.Info($"重连定时器已启动,间隔: {_config.ReconnectInterval}毫秒");
}
}
/// <summary>
/// 重连回调方法
/// </summary>
private void ReconnectCallback(object stateObj)
{
if (_isConnected || _disposed)
return;
// 使用Task执行异步重连
Task.Run(async () =>
{
await ReconnectAsync();
});
}
/// <summary>
/// 停止重连定时器
/// </summary>
private void StopReconnectTimer()
{
_reconnectTimer?.Dispose();
_reconnectTimer = null;
}
/// <summary>
/// 执行重连
/// </summary>
private async Task ReconnectAsync()
{
if (_isConnected || _disposed)
return;
_reconnectAttempts++;
Logger.Info($"正在尝试重连,第 {_reconnectAttempts} 次");
bool success = await ConnectAsync();
if (!success && _config.AutoReconnect && !_disposed)
{
lock (_lockObject)
{
if (!_isConnected && !_disposed)
{
// 计算指数退避重连间隔
int nextInterval = Math.Min(
_config.ReconnectInterval * (int)Math.Pow(2, Math.Min(9, _reconnectAttempts - 1)), // 最多512倍避免间隔过长
_maxReconnectInterval); // 最大重试间隔
Logger.Info($"重连失败,{nextInterval}毫秒后将再次重试");
_reconnectTimer = new Timer(ReconnectCallback, null, nextInterval, Timeout.Infinite);
}
}
}
else if (success)
{
_reconnecting = false;
}
}
/// <summary>
/// 是否已连接
/// </summary>
public bool IsConnected => _isConnected;
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// 释放资源
/// </summary>
/// <param name="disposing">是否由Dispose调用</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
// 释放托管资源
Disconnect();
_heartbeatTimer?.Dispose();
_reconnectTimer?.Dispose();
_client?.Dispose();
}
_disposed = true;
}
}
}