282 lines
9.1 KiB
C#
282 lines
9.1 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace CtrEditor.Services
|
|
{
|
|
/// <summary>
|
|
/// Servidor TCP que captura y retransmite mensajes de debug console
|
|
/// Permite al MCP Proxy escuchar los logs de debug de CtrEditor
|
|
/// </summary>
|
|
public class DebugConsoleServer : IDisposable
|
|
{
|
|
private readonly int _port;
|
|
private TcpListener _tcpListener;
|
|
private bool _isRunning;
|
|
private CancellationTokenSource _cancellationTokenSource;
|
|
private readonly object _lockObject = new object();
|
|
private readonly ConcurrentQueue<string> _messageQueue = new ConcurrentQueue<string>();
|
|
private readonly ConcurrentBag<TcpClient> _connectedClients = new ConcurrentBag<TcpClient>();
|
|
|
|
// Custom TraceListener para capturar Debug.WriteLine
|
|
private DebugTraceListener _traceListener;
|
|
|
|
public DebugConsoleServer(int port = 5007)
|
|
{
|
|
_port = port;
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inicia el servidor de debug console
|
|
/// </summary>
|
|
public void Start()
|
|
{
|
|
if (_isRunning) return;
|
|
|
|
try
|
|
{
|
|
_tcpListener = new TcpListener(IPAddress.Loopback, _port);
|
|
_tcpListener.Start();
|
|
_isRunning = true;
|
|
|
|
// Instalar el trace listener personalizado solo para Trace
|
|
_traceListener = new DebugTraceListener(this);
|
|
Trace.Listeners.Add(_traceListener);
|
|
|
|
Trace.WriteLine($"[Debug Console Server] Servidor de debug iniciado en puerto {_port}");
|
|
|
|
// Procesar conexiones en background
|
|
_ = Task.Run(async () => await AcceptConnectionsAsync(_cancellationTokenSource.Token));
|
|
|
|
// Procesar cola de mensajes
|
|
_ = Task.Run(async () => await ProcessMessageQueueAsync(_cancellationTokenSource.Token));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"[Debug Console Server] Error al iniciar servidor: {ex.Message}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detiene el servidor de debug console
|
|
/// </summary>
|
|
public void Stop()
|
|
{
|
|
if (!_isRunning) return;
|
|
|
|
lock (_lockObject)
|
|
{
|
|
if (!_isRunning) return;
|
|
|
|
_isRunning = false;
|
|
_cancellationTokenSource?.Cancel();
|
|
|
|
// Remover el trace listener
|
|
if (_traceListener != null)
|
|
{
|
|
Trace.Listeners.Remove(_traceListener);
|
|
_traceListener = null;
|
|
}
|
|
|
|
_tcpListener?.Stop();
|
|
|
|
// Cerrar todas las conexiones de clientes
|
|
foreach (var client in _connectedClients)
|
|
{
|
|
try
|
|
{
|
|
client?.Close();
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
Debug.WriteLine("[Debug Console Server] Servidor de debug detenido");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Acepta conexiones de clientes
|
|
/// </summary>
|
|
private async Task AcceptConnectionsAsync(CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
while (_isRunning && !cancellationToken.IsCancellationRequested)
|
|
{
|
|
Debug.WriteLine("[Debug Console Server] Esperando conexión de cliente...");
|
|
var tcpClient = await _tcpListener.AcceptTcpClientAsync();
|
|
_connectedClients.Add(tcpClient);
|
|
|
|
Debug.WriteLine("[Debug Console Server] Cliente debug conectado");
|
|
|
|
// Manejar cliente en background
|
|
_ = Task.Run(async () => await HandleClientAsync(tcpClient, cancellationToken));
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (_isRunning)
|
|
{
|
|
Debug.WriteLine($"[Debug Console Server] Error aceptando conexión: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Maneja un cliente conectado
|
|
/// </summary>
|
|
private async Task HandleClientAsync(TcpClient client, CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
var stream = client.GetStream();
|
|
|
|
// Enviar mensaje de bienvenida
|
|
var welcomeMessage = $"[Debug Console Server] Conectado al servidor de debug de CtrEditor en puerto {_port}\n";
|
|
await SendMessageToClientAsync(stream, welcomeMessage);
|
|
|
|
// Mantener conexión viva hasta cancelación
|
|
while (_isRunning && client.Connected && !cancellationToken.IsCancellationRequested)
|
|
{
|
|
await Task.Delay(1000, cancellationToken);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"[Debug Console Server] Error en cliente debug: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
try
|
|
{
|
|
client?.Close();
|
|
}
|
|
catch { }
|
|
Debug.WriteLine("[Debug Console Server] Cliente debug desconectado");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Procesa la cola de mensajes y los envía a todos los clientes conectados
|
|
/// </summary>
|
|
private async Task ProcessMessageQueueAsync(CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
while (_isRunning && !cancellationToken.IsCancellationRequested)
|
|
{
|
|
if (_messageQueue.TryDequeue(out string message))
|
|
{
|
|
await BroadcastMessageAsync(message);
|
|
}
|
|
else
|
|
{
|
|
await Task.Delay(50, cancellationToken); // Pequeña pausa si no hay mensajes
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (_isRunning)
|
|
{
|
|
Debug.WriteLine($"[Debug Console Server] Error procesando cola de mensajes: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Envía un mensaje a todos los clientes conectados
|
|
/// </summary>
|
|
private async Task BroadcastMessageAsync(string message)
|
|
{
|
|
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
|
var formattedMessage = $"[{timestamp}] {message}\n";
|
|
|
|
var clientsToRemove = new List<TcpClient>();
|
|
|
|
foreach (var client in _connectedClients)
|
|
{
|
|
try
|
|
{
|
|
if (client.Connected)
|
|
{
|
|
var stream = client.GetStream();
|
|
await SendMessageToClientAsync(stream, formattedMessage);
|
|
}
|
|
else
|
|
{
|
|
clientsToRemove.Add(client);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
clientsToRemove.Add(client);
|
|
}
|
|
}
|
|
|
|
// Remover clientes desconectados
|
|
// Nota: ConcurrentBag no permite eliminación directa, pero no es crítico
|
|
// Los clientes desconectados se manejan en HandleClientAsync
|
|
}
|
|
|
|
/// <summary>
|
|
/// Envía un mensaje a un cliente específico
|
|
/// </summary>
|
|
private async Task SendMessageToClientAsync(NetworkStream stream, string message)
|
|
{
|
|
var data = Encoding.UTF8.GetBytes(message);
|
|
await stream.WriteAsync(data, 0, data.Length);
|
|
await stream.FlushAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Agrega un mensaje a la cola para transmisión
|
|
/// </summary>
|
|
public void QueueMessage(string message)
|
|
{
|
|
if (_isRunning)
|
|
{
|
|
_messageQueue.Enqueue(message);
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Stop();
|
|
_cancellationTokenSource?.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// TraceListener personalizado que captura mensajes de Debug.WriteLine
|
|
/// y los envía al servidor de debug console
|
|
/// </summary>
|
|
internal class DebugTraceListener : TraceListener
|
|
{
|
|
private readonly DebugConsoleServer _server;
|
|
|
|
public DebugTraceListener(DebugConsoleServer server)
|
|
{
|
|
_server = server;
|
|
}
|
|
|
|
public override void Write(string message)
|
|
{
|
|
_server?.QueueMessage(message);
|
|
}
|
|
|
|
public override void WriteLine(string message)
|
|
{
|
|
_server?.QueueMessage(message);
|
|
}
|
|
}
|
|
}
|