Protocol376Simulator/Services/NetworkService.cs
2025-05-08 17:26:10 +08:00

269 lines
9.5 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 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;
}
}
}