You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

304 lines
9.1 KiB

using AuroraDesk.Presentation.ViewModels.Base;
using Microsoft.Extensions.Logging;
using ReactiveUI;
using System;
using System.Collections.ObjectModel;
using System.Net;
using System.Net.Sockets;
using System.Reactive;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Threading;
namespace AuroraDesk.Presentation.ViewModels.Pages;
/// <summary>
/// UDP 客户端页面 ViewModel
/// </summary>
public class UdpClientPageViewModel : RoutableViewModel
{
private readonly ILogger<UdpClientPageViewModel>? _logger;
private UdpClient? _udpClient;
private CancellationTokenSource? _receiveCancellationTokenSource;
private bool _isConnected;
private string _serverIp = "127.0.0.1";
private int _serverPort = 8080;
private int _localPort = 0; // 0 表示系统自动分配
private string _message = string.Empty;
private string _statusMessage = "未连接";
private readonly ObservableCollection<string> _receivedMessages = new();
private readonly ObservableCollection<string> _sentMessages = new();
public UdpClientPageViewModel(
IScreen hostScreen,
ILogger<UdpClientPageViewModel>? logger = null)
: base(hostScreen, "UdpClient")
{
_logger = logger;
// 创建命令
ConnectCommand = ReactiveCommand.CreateFromTask(ConnectAsync,
this.WhenAnyValue(x => x.IsConnected, isConnected => !isConnected));
DisconnectCommand = ReactiveCommand.Create(Disconnect,
this.WhenAnyValue(x => x.IsConnected));
SendMessageCommand = ReactiveCommand.CreateFromTask(SendMessageAsync,
this.WhenAnyValue(x => x.IsConnected, x => x.Message,
(isConnected, message) => isConnected && !string.IsNullOrWhiteSpace(message)));
ClearMessagesCommand = ReactiveCommand.Create(ClearMessages);
}
/// <summary>
/// 是否已连接
/// </summary>
public bool IsConnected
{
get => _isConnected;
set => this.RaiseAndSetIfChanged(ref _isConnected, value);
}
/// <summary>
/// 服务器 IP 地址
/// </summary>
public string ServerIp
{
get => _serverIp;
set => this.RaiseAndSetIfChanged(ref _serverIp, value);
}
/// <summary>
/// 服务器端口
/// </summary>
public int ServerPort
{
get => _serverPort;
set => this.RaiseAndSetIfChanged(ref _serverPort, value);
}
/// <summary>
/// 本地端口
/// </summary>
public int LocalPort
{
get => _localPort;
set => this.RaiseAndSetIfChanged(ref _localPort, value);
}
/// <summary>
/// 要发送的消息
/// </summary>
public string Message
{
get => _message;
set => this.RaiseAndSetIfChanged(ref _message, value);
}
/// <summary>
/// 状态消息
/// </summary>
public string StatusMessage
{
get => _statusMessage;
set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
}
/// <summary>
/// 接收到的消息列表
/// </summary>
public ObservableCollection<string> ReceivedMessages => _receivedMessages;
/// <summary>
/// 已发送的消息列表
/// </summary>
public ObservableCollection<string> SentMessages => _sentMessages;
/// <summary>
/// 连接命令
/// </summary>
public ReactiveCommand<Unit, Unit> ConnectCommand { get; }
/// <summary>
/// 断开连接命令
/// </summary>
public ReactiveCommand<Unit, Unit> DisconnectCommand { get; }
/// <summary>
/// 发送消息命令
/// </summary>
public ReactiveCommand<Unit, Unit> SendMessageCommand { get; }
/// <summary>
/// 清空消息命令
/// </summary>
public ReactiveCommand<Unit, Unit> ClearMessagesCommand { get; }
/// <summary>
/// 连接到服务器
/// </summary>
private async Task ConnectAsync()
{
try
{
if (!IPAddress.TryParse(ServerIp, out var ipAddress))
{
StatusMessage = $"无效的 IP 地址: {ServerIp}";
_logger?.LogWarning("无效的 IP 地址: {Ip}", ServerIp);
return;
}
if (ServerPort < 1 || ServerPort > 65535)
{
StatusMessage = $"无效的端口: {ServerPort}";
_logger?.LogWarning("无效的端口: {Port}", ServerPort);
return;
}
// 创建 UDP 客户端
_udpClient = new UdpClient(LocalPort);
_udpClient.EnableBroadcast = true;
var localEndPoint = (IPEndPoint)_udpClient.Client.LocalEndPoint!;
LocalPort = localEndPoint.Port;
IsConnected = true;
StatusMessage = $"已连接到 {ServerIp}:{ServerPort} (本地端口: {LocalPort})";
_logger?.LogInformation("UDP 客户端已连接到 {ServerIp}:{ServerPort}, 本地端口: {LocalPort}",
ServerIp, ServerPort, LocalPort);
// 启动接收任务
_receiveCancellationTokenSource = new CancellationTokenSource();
_ = Task.Run(() => ReceiveMessagesAsync(_receiveCancellationTokenSource.Token));
}
catch (Exception ex)
{
_logger?.LogError(ex, "连接失败");
StatusMessage = $"连接失败: {ex.Message}";
Disconnect();
}
}
/// <summary>
/// 断开连接
/// </summary>
private void Disconnect()
{
try
{
_receiveCancellationTokenSource?.Cancel();
_receiveCancellationTokenSource?.Dispose();
_receiveCancellationTokenSource = null;
_udpClient?.Close();
_udpClient?.Dispose();
_udpClient = null;
IsConnected = false;
StatusMessage = "已断开连接";
_logger?.LogInformation("UDP 客户端已断开连接");
}
catch (Exception ex)
{
_logger?.LogError(ex, "断开连接时出错");
}
}
/// <summary>
/// 发送消息
/// </summary>
private async Task SendMessageAsync()
{
if (_udpClient == null || string.IsNullOrWhiteSpace(Message))
return;
try
{
if (!IPAddress.TryParse(ServerIp, out var ipAddress))
{
StatusMessage = $"无效的 IP 地址: {ServerIp}";
return;
}
var remoteEndPoint = new IPEndPoint(ipAddress, ServerPort);
var data = Encoding.UTF8.GetBytes(Message);
await _udpClient.SendAsync(data, data.Length, remoteEndPoint);
var timestamp = DateTime.Now.ToString("HH:mm:ss");
var displayMessage = $"[{timestamp}] 发送: {Message}";
await Dispatcher.UIThread.InvokeAsync(() =>
{
SentMessages.Add(displayMessage);
});
_logger?.LogInformation("发送消息到 {ServerIp}:{ServerPort}: {Message}",
ServerIp, ServerPort, Message);
Message = string.Empty; // 清空输入框
}
catch (Exception ex)
{
_logger?.LogError(ex, "发送消息失败");
StatusMessage = $"发送失败: {ex.Message}";
}
}
/// <summary>
/// 接收消息(后台任务)
/// </summary>
private async Task ReceiveMessagesAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested && _udpClient != null)
{
try
{
var result = await _udpClient.ReceiveAsync();
var receivedData = Encoding.UTF8.GetString(result.Buffer);
var remoteEndPoint = result.RemoteEndPoint;
var timestamp = DateTime.Now.ToString("HH:mm:ss");
var displayMessage = $"[{timestamp}] 来自 {remoteEndPoint.Address}:{remoteEndPoint.Port}: {receivedData}";
await Dispatcher.UIThread.InvokeAsync(() =>
{
ReceivedMessages.Add(displayMessage);
});
_logger?.LogDebug("收到消息: {Message} 来自 {EndPoint}", receivedData, remoteEndPoint);
}
catch (ObjectDisposedException)
{
// UDP 客户端已关闭,正常退出
break;
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.Interrupted)
{
// 操作被取消,正常退出
break;
}
catch (Exception ex)
{
_logger?.LogError(ex, "接收消息时出错");
await Task.Delay(1000, cancellationToken); // 等待后重试
}
}
}
/// <summary>
/// 清空消息
/// </summary>
private void ClearMessages()
{
ReceivedMessages.Clear();
SentMessages.Clear();
_logger?.LogInformation("已清空消息列表");
}
/// <summary>
/// 清理资源
/// </summary>
public void Dispose()
{
Disconnect();
}
}