562 lines
18 KiB
C#
562 lines
18 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|