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.
 
 
 
 

308 lines
9.3 KiB

using AuroraDesk.Presentation.ViewModels.Base;
using Microsoft.Extensions.Logging;
using ReactiveUI;
using System;
using System.Collections.ObjectModel;
using System.Linq;
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 UdpServerPageViewModel : RoutableViewModel
{
private readonly ILogger<UdpServerPageViewModel>? _logger;
private UdpClient? _udpServer;
private CancellationTokenSource? _listenCancellationTokenSource;
private bool _isListening;
private int _listenPort = 8080;
private string _statusMessage = "未启动";
private readonly ObservableCollection<string> _receivedMessages = new();
private readonly ObservableCollection<ClientInfo> _clients = new();
public UdpServerPageViewModel(
IScreen hostScreen,
ILogger<UdpServerPageViewModel>? logger = null)
: base(hostScreen, "UdpServer")
{
_logger = logger;
// 创建命令
StartListeningCommand = ReactiveCommand.CreateFromTask(StartListeningAsync,
this.WhenAnyValue(x => x.IsListening, isListening => !isListening));
StopListeningCommand = ReactiveCommand.Create(StopListening,
this.WhenAnyValue(x => x.IsListening));
ClearMessagesCommand = ReactiveCommand.Create(ClearMessages);
ClearClientsCommand = ReactiveCommand.Create(ClearClients);
}
/// <summary>
/// 是否正在监听
/// </summary>
public bool IsListening
{
get => _isListening;
set => this.RaiseAndSetIfChanged(ref _isListening, value);
}
/// <summary>
/// 监听端口
/// </summary>
public int ListenPort
{
get => _listenPort;
set => this.RaiseAndSetIfChanged(ref _listenPort, 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<ClientInfo> Clients => _clients;
/// <summary>
/// 开始监听命令
/// </summary>
public ReactiveCommand<Unit, Unit> StartListeningCommand { get; }
/// <summary>
/// 停止监听命令
/// </summary>
public ReactiveCommand<Unit, Unit> StopListeningCommand { get; }
/// <summary>
/// 清空消息命令
/// </summary>
public ReactiveCommand<Unit, Unit> ClearMessagesCommand { get; }
/// <summary>
/// 清空客户端命令
/// </summary>
public ReactiveCommand<Unit, Unit> ClearClientsCommand { get; }
/// <summary>
/// 开始监听
/// </summary>
private async Task StartListeningAsync()
{
try
{
if (ListenPort < 1 || ListenPort > 65535)
{
StatusMessage = $"无效的端口: {ListenPort}";
_logger?.LogWarning("无效的端口: {Port}", ListenPort);
return;
}
// 创建 UDP 服务器
var localEndPoint = new IPEndPoint(IPAddress.Any, ListenPort);
_udpServer = new UdpClient(localEndPoint);
_udpServer.EnableBroadcast = true;
IsListening = true;
StatusMessage = $"正在监听端口 {ListenPort}...";
_logger?.LogInformation("UDP 服务器开始监听端口 {Port}", ListenPort);
// 启动接收任务
_listenCancellationTokenSource = new CancellationTokenSource();
_ = Task.Run(() => ListenForMessagesAsync(_listenCancellationTokenSource.Token));
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
{
StatusMessage = $"端口 {ListenPort} 已被占用";
_logger?.LogWarning("端口 {Port} 已被占用", ListenPort);
IsListening = false;
}
catch (Exception ex)
{
_logger?.LogError(ex, "启动监听失败");
StatusMessage = $"启动失败: {ex.Message}";
IsListening = false;
}
}
/// <summary>
/// 停止监听
/// </summary>
private void StopListening()
{
try
{
_listenCancellationTokenSource?.Cancel();
_listenCancellationTokenSource?.Dispose();
_listenCancellationTokenSource = null;
_udpServer?.Close();
_udpServer?.Dispose();
_udpServer = null;
IsListening = false;
StatusMessage = "已停止监听";
_logger?.LogInformation("UDP 服务器已停止监听");
}
catch (Exception ex)
{
_logger?.LogError(ex, "停止监听时出错");
}
}
/// <summary>
/// 监听消息(后台任务)
/// </summary>
private async Task ListenForMessagesAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested && _udpServer != null)
{
try
{
var result = await _udpServer.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);
// 更新客户端列表
var clientAddress = remoteEndPoint.Address.ToString();
var clientPort = remoteEndPoint.Port;
var clientKey = $"{clientAddress}:{clientPort}";
var existingClient = Clients.FirstOrDefault(c => c.Address == clientAddress && c.Port == clientPort);
if (existingClient == null)
{
Clients.Add(new ClientInfo
{
Address = clientAddress,
Port = clientPort,
FirstSeen = DateTime.Now,
LastSeen = DateTime.Now,
MessageCount = 1
});
}
else
{
existingClient.LastSeen = DateTime.Now;
existingClient.MessageCount++;
}
});
_logger?.LogDebug("收到消息: {Message} 来自 {EndPoint}", receivedData, remoteEndPoint);
// 自动回复(可选)
var responseMessage = $"收到: {receivedData}";
var responseData = Encoding.UTF8.GetBytes(responseMessage);
await _udpServer.SendAsync(responseData, responseData.Length, 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();
_logger?.LogInformation("已清空消息列表");
}
/// <summary>
/// 清空客户端列表
/// </summary>
private void ClearClients()
{
Clients.Clear();
_logger?.LogInformation("已清空客户端列表");
}
/// <summary>
/// 清理资源
/// </summary>
public void Dispose()
{
StopListening();
}
}
/// <summary>
/// 客户端信息
/// </summary>
public class ClientInfo : ReactiveObject
{
private string _address = string.Empty;
private int _port;
private DateTime _firstSeen;
private DateTime _lastSeen;
private int _messageCount;
public string Address
{
get => _address;
set => this.RaiseAndSetIfChanged(ref _address, value);
}
public int Port
{
get => _port;
set => this.RaiseAndSetIfChanged(ref _port, value);
}
public DateTime FirstSeen
{
get => _firstSeen;
set => this.RaiseAndSetIfChanged(ref _firstSeen, value);
}
public DateTime LastSeen
{
get => _lastSeen;
set => this.RaiseAndSetIfChanged(ref _lastSeen, value);
}
public int MessageCount
{
get => _messageCount;
set => this.RaiseAndSetIfChanged(ref _messageCount, value);
}
public string DisplayName => $"{Address}:{Port}";
}