添加项目文件。
This commit is contained in:
561
MoviconHub.App/Services/WebSocketClient.cs
Normal file
561
MoviconHub.App/Services/WebSocketClient.cs
Normal file
@@ -0,0 +1,561 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user