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
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}";
|
|
}
|
|
|
|
|