From 1fafe1bd2d8dda352846d11b27c926cde6f187c1 Mon Sep 17 00:00:00 2001 From: Yoli <799480165@qq.com> Date: Wed, 3 Jul 2024 09:59:04 +0800 Subject: [PATCH] add a socket testing tool (#49) --- tools/README.md | 33 ++++++++ tools/SocketTester/ISocketTester.cs | 35 ++++++++ tools/SocketTester/Program.cs | 81 ++++++++++++++++++ tools/SocketTester/SocketTester.csproj | 16 ++++ tools/SocketTester/TcpTester.cs | 93 +++++++++++++++++++++ tools/SocketTester/UdpStream.cs | 111 +++++++++++++++++++++++++ tools/SocketTester/UdpTester.cs | 107 ++++++++++++++++++++++++ tools/SocketTester/Util.cs | 22 +++++ 8 files changed, 498 insertions(+) create mode 100644 tools/README.md create mode 100644 tools/SocketTester/ISocketTester.cs create mode 100644 tools/SocketTester/Program.cs create mode 100644 tools/SocketTester/SocketTester.csproj create mode 100644 tools/SocketTester/TcpTester.cs create mode 100644 tools/SocketTester/UdpStream.cs create mode 100644 tools/SocketTester/UdpTester.cs create mode 100644 tools/SocketTester/Util.cs diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000..77483cc --- /dev/null +++ b/tools/README.md @@ -0,0 +1,33 @@ +# Tools + +## SocketTester + +A socket testing tool. + +### Usage + +```bash +cd SocketTester + +# send tcp packages +dotnet run -- -t tcp -sp 9999 -cp 9999 -tp 10 -c + +# send udp packages +dotnet run -- -t udp -sp 9999 -cp 9999 -tp 10 -c +``` + +Startup parameters + +```bash +dotnet run -- -h +``` + +| Name | Default Value | Desc | +| ----------------- | ------------- | ---------------------------------------------- | +| --type,-t | tcp | Socket type, value is tcp,udp etc. | +| --server-port,-sp | 9999 | Server listen port | +| --client-port,-cp | 8888 | Client connection port | +| --compressed,-c | false | Enable stream compression | +| --frequency,-f | 1000 | Frequency of sending(ms) | +| --total-packs,-tp | 0 | Maximum number of packets sent. 0 is unlimited | +| --pack-size,-ps | 1024 | Package size | diff --git a/tools/SocketTester/ISocketTester.cs b/tools/SocketTester/ISocketTester.cs new file mode 100644 index 0000000..7a28979 --- /dev/null +++ b/tools/SocketTester/ISocketTester.cs @@ -0,0 +1,35 @@ +using System.CommandLine; +using System.CommandLine.Binding; + +namespace SocketTester; + +internal interface ISocketTester +{ + bool Compressed { get; set; } + int Frequency { get; set; } + int TotalPacks { get; set; } + int PackSize { get; set; } + Task RunServerAsync(int port = 9999); + Task RunClientAsync(int port = 8888, string ip = "127.0.0.1"); +} + +internal class SocketTesterBinder : BinderBase +{ + private readonly Option _typeOption; + + public SocketTesterBinder(Option typeOption) + { + _typeOption = typeOption; + } + + protected override ISocketTester GetBoundValue(BindingContext bindingContext) + { + var value = bindingContext.ParseResult.GetValueForOption(_typeOption); + return value switch + { + "tcp" => new TcpTester(), + "udp" => new UdpTester(), + _ => new TcpTester() + }; + } +} diff --git a/tools/SocketTester/Program.cs b/tools/SocketTester/Program.cs new file mode 100644 index 0000000..78e694c --- /dev/null +++ b/tools/SocketTester/Program.cs @@ -0,0 +1,81 @@ +// See https://aka.ms/new-console-template for more information + +using SocketTester; +using System.CommandLine; + +var returnCode = 0; +var rootCommand = new RootCommand("RhoAias Socket Tester."); + +var typeOption = new Option( + name: "--type", + description: "Socket type, value is tcp,udp etc.", + getDefaultValue: () => "tcp"); +typeOption.AddAlias("-t"); + +var serverPortOption = new Option( + name:"--server-port", + description: "Server listen port.", + getDefaultValue: () => 9999); +serverPortOption.AddAlias("-sp"); + +var clientPortOption = new Option( + name: "--client-port", + description: "Client connection port.", + getDefaultValue: () => 8888); +clientPortOption.AddAlias("-cp"); + +var compressedOption = new Option( + name: "--compressed", + description: "Enable stream compression.", + getDefaultValue: () => false); +compressedOption.AddAlias("-c"); + +var frequencyOption = new Option( + name: "--frequency", + description: "Frequency of sending(ms).", + getDefaultValue: () => 1000); +frequencyOption.AddAlias("-f"); + +var totalPacksOption = new Option( + name: "--total-packs", + description: "Maximum number of packets sent. 0 is unlimited.", + getDefaultValue: () => 0); +totalPacksOption.AddAlias("-tp"); + +var packSizeOption = new Option( + name: "--pack-size", + description: "Package size.", + getDefaultValue: () => 1024); +packSizeOption.AddAlias("-ps"); + +rootCommand.Add(typeOption); +rootCommand.Add(serverPortOption); +rootCommand.Add(clientPortOption); +rootCommand.Add(compressedOption); +rootCommand.Add(frequencyOption); +rootCommand.Add(totalPacksOption); +rootCommand.Add(packSizeOption); + +rootCommand.SetHandler(async (tester, serverPort, clientPort, compressed, frequency, totalPacks, packSize) => + { + tester.Compressed = compressed; + tester.Frequency = frequency; + tester.TotalPacks = totalPacks; + tester.PackSize = packSize; + + var task1 = tester.RunServerAsync(serverPort); + await Task.Delay(2000); + var task2 = tester.RunClientAsync(clientPort); + await Task.WhenAll(task1, task2); + }, + new SocketTesterBinder(typeOption), + serverPortOption, + clientPortOption, + compressedOption, + frequencyOption, + totalPacksOption, + packSizeOption); + +await rootCommand.InvokeAsync(args); + +return returnCode; diff --git a/tools/SocketTester/SocketTester.csproj b/tools/SocketTester/SocketTester.csproj new file mode 100644 index 0000000..96fdd80 --- /dev/null +++ b/tools/SocketTester/SocketTester.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + 1.0.0 + + + + + + + + diff --git a/tools/SocketTester/TcpTester.cs b/tools/SocketTester/TcpTester.cs new file mode 100644 index 0000000..dde3816 --- /dev/null +++ b/tools/SocketTester/TcpTester.cs @@ -0,0 +1,93 @@ +using System.IO.Compression; +using System.Net; +using System.Net.Sockets; +using Snappier; + +namespace SocketTester; + +internal class TcpTester : ISocketTester +{ + private long _packIndex = 1; + public bool Compressed { get; set; } = false; + public int Frequency { get; set; } = 2000; + public int TotalPacks { get; set; } = 0; + public int PackSize { get; set; } = 1024; + + public Task RunServerAsync(int port = 9999) + { + return Task.Run(async () => + { + var listener = new TcpListener(new IPEndPoint(IPAddress.Any, port)); + listener.Start(); + var client = await listener.AcceptTcpClientAsync(); + await using var sendStream = GetStream(client, Compressed, CompressionMode.Compress); + await using var recvStream = GetStream(client, Compressed, CompressionMode.Decompress); + while (TotalPacks == 0 || _packIndex < TotalPacks) + { + var readBytes = await RecvAsync(recvStream); + if (readBytes > 0) + { + await Task.Delay(Frequency); + await SendAsync(sendStream); + } + } + }); + } + + public Task RunClientAsync(int port = 8888, string ip = "127.0.0.1") + { + return Task.Run(async () => + { + var client = new TcpClient(); + await client.ConnectAsync(IPAddress.Parse(ip), port); + await using var sendStream = GetStream(client, Compressed, CompressionMode.Compress); + await using var recvStream = GetStream(client, Compressed, CompressionMode.Decompress); + await SendAsync(sendStream); + while (TotalPacks == 0 || _packIndex < TotalPacks) + { + var readBytes = await RecvAsync(recvStream); + if (readBytes > 0) + { + await Task.Delay(Frequency); + await SendAsync(sendStream); + } + } + }); + } + + private Stream GetStream(TcpClient client, bool compressed, CompressionMode mode) + { + var stream = client.GetStream(); + if (compressed) + { + return new SnappyStream(stream, mode); + } + + return stream; + } + + private async Task SendAsync(Stream stream) + { + if (TotalPacks != 0 && _packIndex > TotalPacks) + { + return; + } + var data = Util.GenerateRandomBytes(PackSize); + Console.WriteLine($"Send -> PackIndex: {_packIndex} PackSize: {PackSize} Length:{data.Length} CheckSum: {Util.CheckSum(data)}"); + await stream.WriteAsync(data); + await stream.FlushAsync(); + } + + private async Task RecvAsync(Stream stream) + { + var buffer = new byte[2048]; + var readBytes = await stream.ReadAsync(buffer, 0, buffer.Length); + + if (readBytes > 0) + { + Console.WriteLine($"Recv -> PackIndex: {_packIndex} PackSize: {PackSize} Length:{readBytes} CheckSum: {Util.CheckSum(buffer[..readBytes])}"); + _packIndex++; + } + return readBytes; + } +} diff --git a/tools/SocketTester/UdpStream.cs b/tools/SocketTester/UdpStream.cs new file mode 100644 index 0000000..f3caf48 --- /dev/null +++ b/tools/SocketTester/UdpStream.cs @@ -0,0 +1,111 @@ +using System.Net; +using System.Net.Sockets; + +namespace SocketTester +{ + internal class UdpStream : Stream + { + private readonly UdpClient _client; + private readonly bool _remoteWrite; + private IPEndPoint? _remoteEndPoint; + + public IPEndPoint RemoteEndPoint => _remoteEndPoint; + + public UdpStream(UdpClient client, bool remoteWrite) + { + _client = client; + _remoteWrite = remoteWrite; + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int offset, int count) + { + var recv = _client.Receive(ref _remoteEndPoint); + recv.CopyTo(buffer, 0); + return recv.Length; + } + + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var recv = await _client.ReceiveAsync(cancellationToken); + _remoteEndPoint = recv.RemoteEndPoint; + recv.Buffer.CopyTo(buffer, 0); + return recv.Buffer.Length; + } + + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = new CancellationToken()) + { + var recv = await _client.ReceiveAsync(cancellationToken); + _remoteEndPoint = recv.RemoteEndPoint; + recv.Buffer.CopyTo(buffer); + return recv.Buffer.Length; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (_remoteWrite && _remoteEndPoint != null) + { + _client.Send(buffer, count, _remoteEndPoint); + } + else + { + _client.Send(buffer, count); + } + } + + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (_remoteWrite && _remoteEndPoint != null) + { + await _client.SendAsync(buffer, count, _remoteEndPoint); + } + else + { + await _client.SendAsync(buffer, count); + } + } + + public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = new CancellationToken()) + { + if (_remoteWrite && _remoteEndPoint != null) + { + await _client.SendAsync(buffer, _remoteEndPoint, cancellationToken); + } + else + { + await _client.SendAsync(buffer, cancellationToken); + } + } + + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => true; + public override long Length => throw new NotSupportedException(); + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + } + + internal static class Ext + { + public static UdpStream GetStream(this UdpClient client, bool remoteWrite = false) + { + return new UdpStream(client, remoteWrite); + } + } +} diff --git a/tools/SocketTester/UdpTester.cs b/tools/SocketTester/UdpTester.cs new file mode 100644 index 0000000..0dabb91 --- /dev/null +++ b/tools/SocketTester/UdpTester.cs @@ -0,0 +1,107 @@ +using System.Net.Sockets; +using System.Net; +using Snappier; +using System.IO.Compression; + +namespace SocketTester +{ + internal class UdpTester : ISocketTester + { + private long _packIndex = 1; + public bool Compressed { get; set; } = false; + public int Frequency { get; set; } = 2000; + public int TotalPacks { get; set; } = 0; + public int PackSize { get; set; } = 1024; + + public Task RunServerAsync(int port = 9999) + { + return Task.Run(async () => + { + var client = new UdpClient(new IPEndPoint(IPAddress.Any, port)); + var stream = client.GetStream(true); + await using var sendStream = GetStream(stream, Compressed, CompressionMode.Compress); + await using var recvStream = GetStream(stream, Compressed, CompressionMode.Decompress); + while (TotalPacks == 0 || _packIndex < TotalPacks) + { + try + { + var readBytes = await RecvAsync(recvStream); + if (readBytes > 0) + { + await Task.Delay(Frequency); + await SendAsync(sendStream); + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + + } + }); + } + + public Task RunClientAsync(int port = 8888, string ip = "127.0.0.1") + { + return Task.Run(async () => + { + try + { + var client = new UdpClient(); + client.Connect(IPAddress.Parse(ip), port); + var stream = client.GetStream(); + await using var sendStream = GetStream(stream, Compressed, CompressionMode.Compress); + await using var recvStream = GetStream(stream, Compressed, CompressionMode.Decompress); + await SendAsync(sendStream); + while (TotalPacks == 0 || _packIndex < TotalPacks) + { + var readBytes = await RecvAsync(recvStream); + if (readBytes > 0) + { + await Task.Delay(Frequency); + await SendAsync(sendStream); + } + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + }); + } + + private Stream GetStream(UdpStream stream, bool compressed, CompressionMode mode) + { + if (compressed) + { + return new SnappyStream(stream, mode); + } + + return stream; + } + + private async Task SendAsync(Stream stream) + { + if (TotalPacks != 0 && _packIndex > TotalPacks) + { + return; + } + var data = Util.GenerateRandomBytes(PackSize); + Console.WriteLine($"Send -> PackIndex: {_packIndex} PackSize: {PackSize} Length:{data.Length} CheckSum: {Util.CheckSum(data)}"); + await stream.WriteAsync(data); + await stream.FlushAsync(); + } + + private async Task RecvAsync(Stream stream) + { + var buffer = new byte[1024]; + var readBytes = await stream.ReadAsync(buffer, 0, buffer.Length); + if (readBytes > 0) + { + Console.WriteLine($"Recv -> PackIndex: {_packIndex} PackSize: {PackSize} Length:{readBytes} CheckSum: {Util.CheckSum(buffer[..readBytes])}"); + _packIndex++; + } + return readBytes; + } + } +} diff --git a/tools/SocketTester/Util.cs b/tools/SocketTester/Util.cs new file mode 100644 index 0000000..7f9dafe --- /dev/null +++ b/tools/SocketTester/Util.cs @@ -0,0 +1,22 @@ +using System.IO.Hashing; +using Snappier; + +namespace SocketTester; + +internal static class Util +{ + public static byte[] GenerateRandomBytes(int size = 1024) + { + var data = new byte[size]; + var rand = new Random(); + rand.NextBytes(data); + return data; + } + + public static uint CheckSum(byte[] data) + { + var crc = new Crc32(); + crc.Append(data); + return crc.GetCurrentHashAsUInt32(); + } +}