138 Zeilen
4.2 KiB
C#
138 Zeilen
4.2 KiB
C#
using System.Net.Sockets;
|
|
using System.Text;
|
|
|
|
namespace PrinterMonitor.Tcp;
|
|
|
|
/// <summary>
|
|
/// Verbindet sich als TCP-Client auf localhost:12164 und sendet
|
|
/// den aggregierten Druckerzustand im VW-Protokoll (STX/ETX-Framing).
|
|
///
|
|
/// Reconnect-Logik: Bei Verbindungsverlust wird automatisch alle 5 Sekunden
|
|
/// ein erneuter Verbindungsversuch unternommen.
|
|
/// </summary>
|
|
public class TcpPushClient : IAsyncDisposable
|
|
{
|
|
private const byte Stx = 0x02;
|
|
private const byte Etx = 0x03;
|
|
private const int ReconnectDelayMs = 5000;
|
|
|
|
private readonly string _host;
|
|
private readonly int _port;
|
|
|
|
private TcpClient? _client;
|
|
private string? _lastPayload;
|
|
private CancellationTokenSource? _cts;
|
|
private Task? _reconnectTask;
|
|
|
|
public bool IsConnected => _client?.Connected == true;
|
|
|
|
public TcpPushClient(string host, int port)
|
|
{
|
|
_host = host;
|
|
_port = port;
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
_cts = new CancellationTokenSource();
|
|
_reconnectTask = KeepConnectedAsync(_cts.Token);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sendet den Payload mit STX/ETX-Framing an den verbundenen Server.
|
|
/// Bei fehlender Verbindung wird der Payload verworfen (Reconnect läuft im Hintergrund).
|
|
/// </summary>
|
|
public async Task SendStateAsync(string payload)
|
|
{
|
|
_lastPayload = payload;
|
|
|
|
// Lokale Referenz: verhindert NullRef bei gleichzeitigem DropClient()
|
|
var client = _client;
|
|
if (client?.Connected != true) return;
|
|
|
|
try
|
|
{
|
|
var frame = BuildFrame(payload);
|
|
var stream = client.GetStream();
|
|
await stream.WriteAsync(frame);
|
|
await stream.FlushAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] TCP-Client [{_host}:{_port}]: Sendefehler – {ex.Message}");
|
|
DropClient();
|
|
}
|
|
}
|
|
|
|
private async Task KeepConnectedAsync(CancellationToken ct)
|
|
{
|
|
while (!ct.IsCancellationRequested)
|
|
{
|
|
if (_client?.Connected != true)
|
|
{
|
|
DropClient();
|
|
TcpClient? newClient = null;
|
|
try
|
|
{
|
|
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] TCP-Client: Verbinde auf {_host}:{_port}...");
|
|
newClient = new TcpClient();
|
|
await newClient.ConnectAsync(_host, _port, ct);
|
|
_client = newClient;
|
|
newClient = null; // Ownership an _client übertragen
|
|
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] TCP-Client: Verbunden mit {_host}:{_port}");
|
|
|
|
// Letzten bekannten Zustand sofort senden
|
|
if (_lastPayload != null)
|
|
await SendStateAsync(_lastPayload);
|
|
}
|
|
catch (OperationCanceledException) { return; }
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] TCP-Client: Verbindungsfehler – {ex.Message}");
|
|
newClient?.Dispose(); // Bei Fehler vor Zuweisung aufräumen
|
|
DropClient();
|
|
await SafeDelay(ReconnectDelayMs, ct);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
await SafeDelay(ReconnectDelayMs, ct);
|
|
}
|
|
}
|
|
|
|
private void DropClient()
|
|
{
|
|
try { _client?.Dispose(); } catch { }
|
|
_client = null;
|
|
}
|
|
|
|
private static byte[] BuildFrame(string payload)
|
|
{
|
|
var payloadBytes = Encoding.UTF8.GetBytes(payload);
|
|
var frame = new byte[payloadBytes.Length + 2];
|
|
frame[0] = Stx;
|
|
payloadBytes.CopyTo(frame, 1);
|
|
frame[^1] = Etx;
|
|
return frame;
|
|
}
|
|
|
|
private static async Task SafeDelay(int ms, CancellationToken ct)
|
|
{
|
|
try { await Task.Delay(ms, ct); }
|
|
catch (OperationCanceledException) { }
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
_cts?.Cancel();
|
|
if (_reconnectTask != null)
|
|
{
|
|
try { await _reconnectTask; } catch { }
|
|
}
|
|
DropClient();
|
|
_lastPayload = null;
|
|
_cts?.Dispose();
|
|
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] TCP-Client gestoppt.");
|
|
}
|
|
}
|