Skip to content
Open

Lab7 #43

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7ca46f3
lab1
slavik22 May 24, 2026
a701afc
Merge pull request #2 from slavik22/lab1
slavik22 May 24, 2026
4fb8cef
lab2
slavik22 May 24, 2026
b767d8a
Merge pull request #3 from slavik22/lab2
slavik22 May 24, 2026
78b60e6
make private fields readonly
slavik22 May 24, 2026
ef1262c
fix: make private fields readonly (S2933)
slavik22 May 24, 2026
a07a541
fix: dispose CancellationTokenSource to prevent resource leak (S2930)
slavik22 May 24, 2026
e968a0f
fix: remove unused exception variable in empty catch (S2486)
slavik22 May 24, 2026
3ca003c
fix: move IUdpClient and UdpClientWrapper into named namespace (S3903)
slavik22 May 24, 2026
2aafcda
fix: provide message in ArgumentOutOfRangeException (S3928)
slavik22 May 24, 2026
b972a36
fix: use static MD5.HashData instead of ComputeHash (CA1850)
slavik22 May 24, 2026
042aa56
Merge pull request #4 from slavik22/lab2-smells
slavik22 May 24, 2026
4634d91
chore: add coverlet.msbuild for OpenCover report generation
slavik22 May 24, 2026
726d635
ci: enable test coverage step in SonarCloud workflow
slavik22 May 24, 2026
064336d
fix: cast ushort to int before Enum.IsDefined to match enum underlyin…
slavik22 May 24, 2026
f726d15
test: add 5 unit tests for NetSdrClient and NetSdrMessageHelper
slavik22 May 24, 2026
2b303c9
Merge pull request #5 from slavik22/lab3
slavik22 May 24, 2026
4431a31
refactor: replace duplicate Exit body with StopListening delegation
slavik22 May 24, 2026
5471cbf
refactor: extract SendBytesAsync to remove duplicated stream write logic
slavik22 May 24, 2026
3b3ff41
Merge pull request #6 from slavik22/lab4
slavik22 May 24, 2026
13ff71f
test: add architecture rules (NetArchTest) — RED: Messages depends on…
slavik22 May 24, 2026
7d8aba5
fix: remove MessageDispatcher that violated Messages→Networking depen…
slavik22 May 24, 2026
cf96cfb
Merge pull request #7 from slavik22/lab5
slavik22 May 24, 2026
c413acb
refactor: extract ITcpListener, split EchoServer into separate files …
slavik22 May 24, 2026
7b5dea3
test: add EchoTcpServerTests project with 13 unit tests for EchoServe…
slavik22 May 24, 2026
ee6bf1d
Merge pull request #8 from slavik22/lab6
slavik22 May 24, 2026
fb5282c
ci: add Dependabot weekly NuGet update schedule
slavik22 May 24, 2026
a9afc32
fix(deps): bump Newtonsoft.Json 13.0.0→13.0.3, SharpZipLib 1.3.2→1.4.2
slavik22 May 24, 2026
846ef06
chore(deps): bump Microsoft.NET.Test.Sdk 17.8.0→17.13.0, coverlet.col…
slavik22 May 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "nuget"
directory: "/"
schedule:
interval: "weekly"
20 changes: 11 additions & 9 deletions .github/workflows/sonarcloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ on:

permissions:
pull-requests: read # allows SonarCloud to decorate PRs with analysis results
contents: read


jobs:
sonar-check:
Expand All @@ -56,8 +58,8 @@ jobs:
dotnet tool install --global dotnet-sonarscanner
echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH
dotnet sonarscanner begin `
/k:"ppanchen_NetSdrClient" `
/o:"ppanchen" `
/k:"slavik22_ReengineeringCourse" `
/o:"slavik22" `
/d:sonar.token="${{ secrets.SONAR_TOKEN }}" `
/d:sonar.cs.opencover.reportsPaths="**/coverage.xml" `
/d:sonar.cpd.cs.minimumTokens=40 `
Expand All @@ -70,13 +72,13 @@ jobs:
run: dotnet restore NetSdrClient.sln
- name: Build
run: dotnet build NetSdrClient.sln -c Release --no-restore
#- name: Tests with coverage (OpenCover)
# run: |
# dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
# /p:CollectCoverage=true `
# /p:CoverletOutput=TestResults/coverage.xml `
# /p:CoverletOutputFormat=opencover
# shell: pwsh
- name: Tests with coverage (OpenCover)
run: |
dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
/p:CollectCoverage=true `
/p:CoverletOutput=TestResults/coverage.xml `
/p:CoverletOutputFormat=opencover
shell: pwsh
# 3) END: SonarScanner
- name: SonarScanner End
run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
Expand Down
71 changes: 71 additions & 0 deletions EchoTcpServer/EchoServer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using EchoTcpServer.Networking;

namespace EchoTcpServer;

public class EchoServer
{
private readonly ITcpListener _listener;
private readonly Action<string> _log;
private CancellationTokenSource _cts = new();

public EchoServer(ITcpListener listener, Action<string>? log = null)
{
_listener = listener;
_log = log ?? Console.WriteLine;
}

public async Task StartAsync()
{
_listener.Start();
_log("Server started.");

while (!_cts.Token.IsCancellationRequested)
{
try
{
var stream = await _listener.AcceptClientStreamAsync();
_log("Client connected.");
_ = Task.Run(() => HandleClientAsync(stream, _cts.Token));
}
catch (ObjectDisposedException)
{
break;
}
}

_log("Server shutdown.");
}

public async Task HandleClientAsync(Stream stream, CancellationToken token)
{
using (stream)
{
try
{
byte[] buffer = new byte[8192];
int bytesRead;

while (!token.IsCancellationRequested
&& (bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
{
await stream.WriteAsync(buffer, 0, bytesRead, token);
_log($"Echoed {bytesRead} bytes.");
}
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_log($"Error: {ex.Message}");
}
}

_log("Client disconnected.");
}

public void Stop()
{
_cts.Cancel();
_listener.Stop();
_cts.Dispose();
_log("Server stopped.");
}
}
1 change: 1 addition & 0 deletions EchoTcpServer/EchoServer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>EchoTcpServer</RootNamespace>
</PropertyGroup>

</Project>
8 changes: 8 additions & 0 deletions EchoTcpServer/Networking/ITcpListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace EchoTcpServer.Networking;

public interface ITcpListener
{
void Start();
void Stop();
Task<Stream> AcceptClientStreamAsync();
}
23 changes: 23 additions & 0 deletions EchoTcpServer/Networking/TcpListenerWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Net;
using System.Net.Sockets;

namespace EchoTcpServer.Networking;

public class TcpListenerWrapper : ITcpListener
{
private readonly TcpListener _listener;

public TcpListenerWrapper(int port)
{
_listener = new TcpListener(IPAddress.Any, port);
}

public void Start() => _listener.Start();
public void Stop() => _listener.Stop();

public async Task<Stream> AcceptClientStreamAsync()
{
var client = await _listener.AcceptTcpClientAsync();
return client.GetStream();
}
}
181 changes: 12 additions & 169 deletions EchoTcpServer/Program.cs
Original file line number Diff line number Diff line change
@@ -1,173 +1,16 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using EchoTcpServer;
using EchoTcpServer.Networking;

/// <summary>
/// This program was designed for test purposes only
/// Not for a review
/// </summary>
public class EchoServer
{
private readonly int _port;
private TcpListener _listener;
private CancellationTokenSource _cancellationTokenSource;
var server = new EchoServer(new TcpListenerWrapper(5000));
_ = Task.Run(() => server.StartAsync());

using var sender = new UdpTimedSender("127.0.0.1", 60000);
Console.WriteLine("Press any key to stop sending...");
sender.StartSending(5000);

public EchoServer(int port)
{
_port = port;
_cancellationTokenSource = new CancellationTokenSource();
}
Console.WriteLine("Press 'q' to quit...");
while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q) { }

public async Task StartAsync()
{
_listener = new TcpListener(IPAddress.Any, _port);
_listener.Start();
Console.WriteLine($"Server started on port {_port}.");

while (!_cancellationTokenSource.Token.IsCancellationRequested)
{
try
{
TcpClient client = await _listener.AcceptTcpClientAsync();
Console.WriteLine("Client connected.");

_ = Task.Run(() => HandleClientAsync(client, _cancellationTokenSource.Token));
}
catch (ObjectDisposedException)
{
// Listener has been closed
break;
}
}

Console.WriteLine("Server shutdown.");
}

private async Task HandleClientAsync(TcpClient client, CancellationToken token)
{
using (NetworkStream stream = client.GetStream())
{
try
{
byte[] buffer = new byte[8192];
int bytesRead;

while (!token.IsCancellationRequested && (bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
{
// Echo back the received message
await stream.WriteAsync(buffer, 0, bytesRead, token);
Console.WriteLine($"Echoed {bytesRead} bytes to the client.");
}
}
catch (Exception ex) when (!(ex is OperationCanceledException))
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
client.Close();
Console.WriteLine("Client disconnected.");
}
}
}

public void Stop()
{
_cancellationTokenSource.Cancel();
_listener.Stop();
_cancellationTokenSource.Dispose();
Console.WriteLine("Server stopped.");
}

public static async Task Main(string[] args)
{
EchoServer server = new EchoServer(5000);

// Start the server in a separate task
_ = Task.Run(() => server.StartAsync());

string host = "127.0.0.1"; // Target IP
int port = 60000; // Target Port
int intervalMilliseconds = 5000; // Send every 3 seconds

using (var sender = new UdpTimedSender(host, port))
{
Console.WriteLine("Press any key to stop sending...");
sender.StartSending(intervalMilliseconds);

Console.WriteLine("Press 'q' to quit...");
while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q)
{
// Just wait until 'q' is pressed
}

sender.StopSending();
server.Stop();
Console.WriteLine("Sender stopped.");
}
}
}


public class UdpTimedSender : IDisposable
{
private readonly string _host;
private readonly int _port;
private readonly UdpClient _udpClient;
private Timer _timer;

public UdpTimedSender(string host, int port)
{
_host = host;
_port = port;
_udpClient = new UdpClient();
}

public void StartSending(int intervalMilliseconds)
{
if (_timer != null)
throw new InvalidOperationException("Sender is already running.");

_timer = new Timer(SendMessageCallback, null, 0, intervalMilliseconds);
}

ushort i = 0;

private void SendMessageCallback(object state)
{
try
{
//dummy data
Random rnd = new Random();
byte[] samples = new byte[1024];
rnd.NextBytes(samples);
i++;

byte[] msg = (new byte[] { 0x04, 0x84 }).Concat(BitConverter.GetBytes(i)).Concat(samples).ToArray();
var endpoint = new IPEndPoint(IPAddress.Parse(_host), _port);

_udpClient.Send(msg, msg.Length, endpoint);
Console.WriteLine($"Message sent to {_host}:{_port} ");
}
catch (Exception ex)
{
Console.WriteLine($"Error sending message: {ex.Message}");
}
}

public void StopSending()
{
_timer?.Dispose();
_timer = null;
}

public void Dispose()
{
StopSending();
_udpClient.Dispose();
}
}
sender.StopSending();
server.Stop();
Console.WriteLine("Sender stopped.");
Loading