Skip to content

Commit 5c2cb7a

Browse files
author
LoneWandererProductions
committed
New TCP server architecture & logging infrastructure
Implements modular TCP server base (TcpServerBase) and specialized servers based on it (LogCollectorServer, PortCheckerServer). Introduces the ILogProcessor interface for flexible log processing and helper classes (ActionLogProcessor, SimpleLogServer) for easy use. Converted existing Listener.cs to trace logging and modern resource management.
1 parent 54819b3 commit 5c2cb7a

7 files changed

Lines changed: 394 additions & 6 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: Communication
4+
* FILE: ActionLogProcessor.cs
5+
* PURPOSE: Sample implementation of ILogProcessor that simply wraps an Action<string> delegate,
6+
* allowing users to easily provide a lambda or method group for log processing without needing to create a full class that implements ILogProcessor.
7+
* PROGRAMMER: Peter Geinitz (Wayfarer)
8+
*/
9+
10+
using System;
11+
using System.Threading.Tasks;
12+
using Communication.Interfaces;
13+
14+
namespace Communication
15+
{
16+
/// <inheritdoc />
17+
/// <summary>
18+
/// Internal Helper Class to adapt the Action to the Interface
19+
/// </summary>
20+
/// <seealso cref="Interfaces.ILogProcessor" />
21+
internal class ActionLogProcessor : ILogProcessor
22+
{
23+
/// <summary>
24+
/// The action
25+
/// </summary>
26+
private readonly Action<string> _action;
27+
28+
/// <summary>
29+
/// Initializes a new instance of the <see cref="ActionLogProcessor"/> class.
30+
/// </summary>
31+
/// <param name="action">The action.</param>
32+
public ActionLogProcessor(Action<string> action)
33+
{
34+
_action = action;
35+
}
36+
37+
/// <inheritdoc />
38+
/// <summary>
39+
/// Processes the received message (e.g., save to DB, log to file).
40+
/// </summary>
41+
/// <param name="message">The message received from the client.</param>
42+
/// <returns>Message task.</returns>
43+
public Task ProcessMessageAsync(string message)
44+
{
45+
// Run the user's lambda
46+
_action(message);
47+
return Task.CompletedTask;
48+
}
49+
}
50+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: Communication.Interfaces
4+
* FILE: ILogProcessor.cs
5+
* PURPOSE: Interface for processing incoming log messages.
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
10+
using System.Threading.Tasks;
11+
12+
namespace Communication.Interfaces
13+
{
14+
/// <summary>
15+
/// Interface for processing incoming log messages.
16+
/// </summary>
17+
public interface ILogProcessor
18+
{
19+
/// <summary>
20+
/// Processes the received message (e.g., save to DB, log to file).
21+
/// </summary>
22+
/// <param name="message">The message received from the client.</param>
23+
Task ProcessMessageAsync(string message);
24+
}
25+
}

Communication/Listener.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
* COPYRIGHT: See COPYING in the top level directory
33
* PROJECT: Communication
44
* FILE: Listener.cs
5-
* PURPOSE: Simple port checker.
5+
* PURPOSE: Simple port checker. Just listens to a port and answers with a fixed message.
66
* PROGRAMMER: Peter Geinitz (Wayfarer)
77
*/
88

99
using System;
10+
using System.Diagnostics;
1011
using System.Net;
1112
using System.Net.Sockets;
1213
using System.Text;
@@ -53,7 +54,7 @@ public async Task StartListeningAsync(CancellationToken cancellationToken)
5354
{
5455
_isRunning = true;
5556
_tcpListener.Start();
56-
Console.WriteLine(ComResource.MessageListening, _port);
57+
Trace.WriteLine(ComResource.MessageListening, _port.ToString());
5758

5859
try
5960
{
@@ -73,7 +74,7 @@ public async Task StartListeningAsync(CancellationToken cancellationToken)
7374
}
7475
catch (Exception ex)
7576
{
76-
Console.WriteLine(ComResource.ErrorAcceptingCommunication, ex.Message);
77+
Trace.WriteLine(ComResource.ErrorAcceptingCommunication, ex.Message);
7778
}
7879
finally
7980
{
@@ -88,7 +89,7 @@ public void StopListening()
8889
{
8990
_isRunning = false;
9091
_tcpListener.Stop();
91-
Console.WriteLine(ComResource.ServerStatusStop);
92+
Trace.WriteLine(ComResource.ServerStatusStop);
9293
}
9394

9495
/// <summary>
@@ -100,7 +101,7 @@ private static async Task HandleClientAsync(TcpClient client)
100101
try
101102
{
102103
using (client) // Ensures the client is closed
103-
using (var stream = client.GetStream()) // Ensures the stream is closed
104+
await using (var stream = client.GetStream()) // Ensures the stream is closed
104105
{
105106
byte[] buffer = Encoding.ASCII.GetBytes(ComResource.AnswerMessage);
106107

@@ -113,7 +114,7 @@ private static async Task HandleClientAsync(TcpClient client)
113114
}
114115
catch (Exception ex)
115116
{
116-
Console.WriteLine("Error handling client: " + ex.Message);
117+
Trace.WriteLine("Error handling client: " + ex.Message);
117118
}
118119
}
119120
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: Communication
4+
* FILE: LogCollectorServer.cs
5+
* PURPOSE: Central log collector server that listens for incoming log messages and processes them using a provided ILogProcessor implementation.
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
using System.IO;
10+
using System.Net.Sockets;
11+
using System.Text;
12+
using System.Threading;
13+
using System.Threading.Tasks;
14+
using Communication.Interfaces;
15+
16+
namespace Communication
17+
{
18+
/// <summary>
19+
/// Connects and listens for logs continuously.
20+
/// Central entry and processing point for logs, which can be extended to save logs to a database, write to files, or perform any other processing as needed.
21+
/// </summary>
22+
public class LogCollectorServer : TcpServerBase
23+
{
24+
/// <summary>
25+
/// The log processor, which can be implemented to save logs to a database, write to a file, or perform any other processing as needed.
26+
/// </summary>
27+
private readonly ILogProcessor _processor;
28+
29+
/// <summary>
30+
/// Initializes a new instance of the <see cref="LogCollectorServer"/> class.
31+
/// </summary>
32+
/// <param name="port">The port.</param>
33+
/// <param name="processor">The processor.</param>
34+
public LogCollectorServer(int port, ILogProcessor processor) : base(port)
35+
{
36+
_processor = processor;
37+
}
38+
39+
/// <summary>
40+
/// Handles the client asynchronous.
41+
/// </summary>
42+
/// <param name="client">The client.</param>
43+
/// <param name="token">The token.</param>
44+
protected override async Task HandleClientAsync(TcpClient client, CancellationToken token)
45+
{
46+
using (var stream = client.GetStream())
47+
using (var reader = new StreamReader(stream, Encoding.UTF8))
48+
{
49+
string? line;
50+
while ((line = await reader.ReadLineAsync()) != null)
51+
{
52+
if (token.IsCancellationRequested) break;
53+
54+
if (!string.IsNullOrWhiteSpace(line))
55+
{
56+
await _processor.ProcessMessageAsync(line);
57+
}
58+
}
59+
}
60+
}
61+
}
62+
}

Communication/PortCheckerServer.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: Communication
4+
* FILE: PortCheckerServer.cs
5+
* PURPOSE: Connects, sends a message, and disconnects.
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
using System.Diagnostics;
10+
using System.Net.Sockets;
11+
using System.Threading;
12+
using System.Threading.Tasks;
13+
14+
namespace Communication
15+
{
16+
/// <inheritdoc />
17+
/// <summary>
18+
/// Connects, sends a message, and disconnects.
19+
/// </summary>
20+
public class PortCheckerServer : TcpServerBase
21+
{
22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="PortCheckerServer"/> class.
24+
/// </summary>
25+
/// <param name="port">The port.</param>
26+
public PortCheckerServer(int port) : base(port) { }
27+
28+
/// <inheritdoc />
29+
/// <summary>
30+
/// Handles the client asynchronous.
31+
/// </summary>
32+
/// <param name="client">The client.</param>
33+
/// <param name="token">The token.</param>
34+
protected override async Task HandleClientAsync(TcpClient client, CancellationToken token)
35+
{
36+
using (var stream = client.GetStream())
37+
{
38+
byte[] buffer = System.Text.Encoding.ASCII.GetBytes(ComResource.AnswerMessage);
39+
await stream.WriteAsync(buffer, 0, buffer.Length, token);
40+
await stream.FlushAsync(token);
41+
Trace.WriteLine("Port check handled.");
42+
}
43+
}
44+
}
45+
}

Communication/SimpleLogServer.cs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: Communication
4+
* FILE: SimpleLogServer.cs
5+
* PURPOSE: Facade to simplify usage of LogCollectorServer for users who just want to provide a simple callback for log processing without needing to implement the ILogProcessor interface themselves.
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
using System;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
using Communication.Interfaces;
13+
14+
namespace Communication
15+
{
16+
/// <summary>
17+
/// A "Stupid Simple" wrapper for the LogCollector.
18+
/// No DI required. Just new it up and go.
19+
/// </summary>
20+
public class SimpleLogServer : IDisposable
21+
{
22+
/// <summary>
23+
/// The internal server
24+
/// </summary>
25+
private readonly LogCollectorServer _internalServer;
26+
27+
/// <summary>
28+
/// The CTS
29+
/// </summary>
30+
private readonly CancellationTokenSource _cts;
31+
32+
/// <summary>
33+
/// The server task
34+
/// </summary>
35+
private Task _serverTask;
36+
37+
/// <summary>
38+
/// Initializes a new instance of the <see cref="SimpleLogServer"/> class.
39+
/// </summary>
40+
/// <param name="port">The port.</param>
41+
/// <param name="onLogReceived">The on log received.</param>
42+
public SimpleLogServer(int port, Action<string> onLogReceived)
43+
: this(port, new ActionLogProcessor(onLogReceived))
44+
{
45+
// This constructor just wraps the Action into our helper class
46+
// and chains to Constructor 2.
47+
}
48+
/// <summary>
49+
/// Initializes a new instance of the <see cref="SimpleLogServer"/> class.
50+
/// </summary>
51+
/// <param name="port">The port.</param>
52+
/// <param name="processor">The processor.</param>
53+
/// <exception cref="System.ArgumentNullException">processor</exception>
54+
public SimpleLogServer(int port, ILogProcessor processor)
55+
{
56+
if (processor == null) throw new ArgumentNullException(nameof(processor));
57+
58+
_cts = new CancellationTokenSource();
59+
_internalServer = new LogCollectorServer(port, processor);
60+
}
61+
62+
/// <summary>
63+
/// Starts this instance.
64+
/// </summary>
65+
public void Start()
66+
{
67+
if (_serverTask != null) return; // Already running
68+
69+
// Run the server in a background task so it doesn't block the main thread
70+
_serverTask = Task.Run(() => _internalServer.StartAsync(_cts.Token));
71+
}
72+
73+
/// <summary>
74+
/// Stops this instance.
75+
/// </summary>
76+
public void Stop()
77+
{
78+
_cts.Cancel();
79+
try
80+
{
81+
_serverTask?.Wait(); // Wait for it to finish cleaning up
82+
}
83+
catch (AggregateException) { /* Ignore cancellation errors */ }
84+
}
85+
86+
/// <summary>
87+
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
88+
/// </summary>
89+
public void Dispose()
90+
{
91+
Stop();
92+
_cts.Dispose();
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)