269 lines
9.5 KiB
C#
269 lines
9.5 KiB
C#
|
|
using System;
|
|||
|
|
using System.Net.Sockets;
|
|||
|
|
using System.Threading;
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
using Serilog;
|
|||
|
|
|
|||
|
|
namespace Protocol376Simulator.Services
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 网络服务类,负责TCP连接和网络通信
|
|||
|
|
/// </summary>
|
|||
|
|
public class NetworkService : IDisposable
|
|||
|
|
{
|
|||
|
|
private readonly string _serverAddress;
|
|||
|
|
private readonly int _serverPort;
|
|||
|
|
private TcpClient _client;
|
|||
|
|
private NetworkStream _stream;
|
|||
|
|
private CancellationTokenSource _cancellationTokenSource;
|
|||
|
|
private readonly string _deviceIdentifier;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 当接收到消息时触发
|
|||
|
|
/// </summary>
|
|||
|
|
public event EventHandler<byte[]> MessageReceived;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 当连接状态改变时触发
|
|||
|
|
/// </summary>
|
|||
|
|
public event EventHandler<ConnectionStatus> ConnectionStatusChanged;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 当发生错误时触发
|
|||
|
|
/// </summary>
|
|||
|
|
public event EventHandler<Exception> ErrorOccurred;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 连接状态枚举
|
|||
|
|
/// </summary>
|
|||
|
|
public enum ConnectionStatus
|
|||
|
|
{
|
|||
|
|
Disconnected,
|
|||
|
|
Connecting,
|
|||
|
|
Connected,
|
|||
|
|
Failed,
|
|||
|
|
Reconnecting
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 是否连接到服务器
|
|||
|
|
/// </summary>
|
|||
|
|
public bool IsConnected => _client != null && _client.Connected;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 构造函数
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="serverAddress">服务器地址</param>
|
|||
|
|
/// <param name="serverPort">服务器端口</param>
|
|||
|
|
/// <param name="deviceIdentifier">设备标识(用于日志)</param>
|
|||
|
|
public NetworkService(string serverAddress, int serverPort, string deviceIdentifier)
|
|||
|
|
{
|
|||
|
|
_serverAddress = serverAddress;
|
|||
|
|
_serverPort = serverPort;
|
|||
|
|
_deviceIdentifier = deviceIdentifier;
|
|||
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 连接到服务器
|
|||
|
|
/// </summary>
|
|||
|
|
public async Task ConnectAsync()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 如果已有连接,先断开
|
|||
|
|
await DisconnectAsync();
|
|||
|
|
|
|||
|
|
// 重置取消标记
|
|||
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|||
|
|
|
|||
|
|
// 触发状态改变事件
|
|||
|
|
ConnectionStatusChanged?.Invoke(this, ConnectionStatus.Connecting);
|
|||
|
|
|
|||
|
|
// 连接服务器
|
|||
|
|
_client = new TcpClient();
|
|||
|
|
Log.Information("{DeviceId} 正在连接到服务器 {ServerAddress}:{ServerPort}...",
|
|||
|
|
_deviceIdentifier, _serverAddress, _serverPort);
|
|||
|
|
|
|||
|
|
await _client.ConnectAsync(_serverAddress, _serverPort);
|
|||
|
|
_stream = _client.GetStream();
|
|||
|
|
|
|||
|
|
Log.Information("{DeviceId} 已成功连接到服务器", _deviceIdentifier);
|
|||
|
|
|
|||
|
|
// 触发状态改变事件
|
|||
|
|
ConnectionStatusChanged?.Invoke(this, ConnectionStatus.Connected);
|
|||
|
|
|
|||
|
|
// 启动接收消息任务
|
|||
|
|
_ = StartReceiveMessagesAsync(_cancellationTokenSource.Token);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Log.Error(ex, "{DeviceId} 连接失败: {ErrorMessage}", _deviceIdentifier, ex.Message);
|
|||
|
|
|
|||
|
|
// 触发错误事件
|
|||
|
|
ErrorOccurred?.Invoke(this, ex);
|
|||
|
|
|
|||
|
|
// 触发状态改变事件
|
|||
|
|
ConnectionStatusChanged?.Invoke(this, ConnectionStatus.Failed);
|
|||
|
|
|
|||
|
|
// 确保清理资源
|
|||
|
|
_client?.Dispose();
|
|||
|
|
_client = null;
|
|||
|
|
_stream = null;
|
|||
|
|
|
|||
|
|
// 重新抛出异常,让调用者处理
|
|||
|
|
throw;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 断开连接
|
|||
|
|
/// </summary>
|
|||
|
|
public async Task DisconnectAsync()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 取消所有后台任务
|
|||
|
|
_cancellationTokenSource?.Cancel();
|
|||
|
|
|
|||
|
|
// 关闭网络流和客户端
|
|||
|
|
_stream?.Close();
|
|||
|
|
_client?.Close();
|
|||
|
|
|
|||
|
|
// 等待一段时间确保资源释放
|
|||
|
|
await Task.Delay(100);
|
|||
|
|
|
|||
|
|
// 触发状态改变事件
|
|||
|
|
ConnectionStatusChanged?.Invoke(this, ConnectionStatus.Disconnected);
|
|||
|
|
|
|||
|
|
Log.Information("{DeviceId} 已断开连接", _deviceIdentifier);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Log.Error(ex, "{DeviceId} 断开连接时发生错误: {ErrorMessage}", _deviceIdentifier, ex.Message);
|
|||
|
|
ErrorOccurred?.Invoke(this, ex);
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
_stream = null;
|
|||
|
|
_client = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 发送消息
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="message">要发送的消息字节数组</param>
|
|||
|
|
public async Task SendMessageAsync(byte[] message)
|
|||
|
|
{
|
|||
|
|
if (!IsConnected)
|
|||
|
|
{
|
|||
|
|
throw new InvalidOperationException("未连接到服务器,无法发送消息");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
await _stream.WriteAsync(message, 0, message.Length);
|
|||
|
|
await _stream.FlushAsync();
|
|||
|
|
|
|||
|
|
Log.Debug("{DeviceId} 发送消息: {HexMessage}",
|
|||
|
|
_deviceIdentifier, BitConverter.ToString(message).Replace("-", " "));
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Log.Error(ex, "{DeviceId} 发送消息失败: {ErrorMessage}", _deviceIdentifier, ex.Message);
|
|||
|
|
ErrorOccurred?.Invoke(this, ex);
|
|||
|
|
throw;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 开始接收消息的后台任务
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="cancellationToken">取消标记</param>
|
|||
|
|
private async Task StartReceiveMessagesAsync(CancellationToken cancellationToken)
|
|||
|
|
{
|
|||
|
|
byte[] buffer = new byte[1024];
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
while (!cancellationToken.IsCancellationRequested && IsConnected)
|
|||
|
|
{
|
|||
|
|
// 读取消息长度
|
|||
|
|
int bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
|
|||
|
|
|
|||
|
|
if (bytesRead > 0)
|
|||
|
|
{
|
|||
|
|
// 创建一个新数组,只包含实际读取的字节
|
|||
|
|
byte[] receivedMessage = new byte[bytesRead];
|
|||
|
|
Array.Copy(buffer, receivedMessage, bytesRead);
|
|||
|
|
|
|||
|
|
Log.Debug("{DeviceId} 收到消息: {HexMessage}",
|
|||
|
|
_deviceIdentifier, BitConverter.ToString(receivedMessage).Replace("-", " "));
|
|||
|
|
|
|||
|
|
// 触发消息接收事件
|
|||
|
|
MessageReceived?.Invoke(this, receivedMessage);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// 读取0字节表示连接已关闭
|
|||
|
|
Log.Warning("{DeviceId} 连接已关闭(服务器端断开)", _deviceIdentifier);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (OperationCanceledException)
|
|||
|
|
{
|
|||
|
|
// 任务被取消,正常退出
|
|||
|
|
Log.Information("{DeviceId} 接收消息任务已取消", _deviceIdentifier);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
if (!cancellationToken.IsCancellationRequested)
|
|||
|
|
{
|
|||
|
|
Log.Error(ex, "{DeviceId} 接收消息时发生错误: {ErrorMessage}", _deviceIdentifier, ex.Message);
|
|||
|
|
ErrorOccurred?.Invoke(this, ex);
|
|||
|
|
|
|||
|
|
// 触发连接状态变更
|
|||
|
|
ConnectionStatusChanged?.Invoke(this, ConnectionStatus.Failed);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果不是因为取消而退出循环,表示连接已断开
|
|||
|
|
if (!cancellationToken.IsCancellationRequested && _client != null)
|
|||
|
|
{
|
|||
|
|
// 额外检查并确认连接状态
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (_client.Client != null && !_client.Client.Poll(0, SelectMode.SelectRead) || _client.Available != 0)
|
|||
|
|
{
|
|||
|
|
// 客户端仍然连接
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch
|
|||
|
|
{
|
|||
|
|
// 忽略额外的检查异常
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 连接已断开
|
|||
|
|
ConnectionStatusChanged?.Invoke(this, ConnectionStatus.Disconnected);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 释放资源
|
|||
|
|
/// </summary>
|
|||
|
|
public void Dispose()
|
|||
|
|
{
|
|||
|
|
_cancellationTokenSource?.Cancel();
|
|||
|
|
_cancellationTokenSource?.Dispose();
|
|||
|
|
_stream?.Dispose();
|
|||
|
|
_client?.Dispose();
|
|||
|
|
|
|||
|
|
_cancellationTokenSource = null;
|
|||
|
|
_stream = null;
|
|||
|
|
_client = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|