Intro
In this time, I will try using sockets to send and receive data over TCP/UDP.
Environments
- .NET ver.7.0.105
TCP
I can use TcpClient to send and receive data over TCP.
- TcpClient Class(System.Net.Sockets) - Microsoft Learn
- Use Sockets to send and receive data over TCP - .NET - Microsoft Learn
TcpSender.cs
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace SocketSample.Networks;
public class TcpSender
{
public async Task SendAsync(IPAddress? ipAddress, string message)
{
if(string.IsNullOrEmpty(message))
{
return;
}
await SendAsync(ipAddress, Encoding.UTF8.GetBytes(message));
}
public async Task SendAsync(IPAddress? ipAddress, byte[] data)
{
if(ipAddress == null ||
data.Length <= 0)
{
return;
}
using var client = new TcpClient();
await client.ConnectAsync(new IPEndPoint(ipAddress, 13));
await using var stream = client.GetStream();
await stream.WriteAsync(data);
}
}
TcpReceiver.cs
using System.Net;
using System.Net.Sockets;
namespace SocketSample.Networks;
public class TcpReceiver
{
public Action<byte[]>? OnReceived;
public async Task ReceiveAsync(CancellationToken cancel)
{
// To accept access from other machines, I have to set "IPAddress.Any".
var ipEndPoint = new IPEndPoint(IPAddress.Any, 13);
var listener = new TcpListener(ipEndPoint);
listener.Start();
using TcpClient client = listener.AcceptTcpClient();
await using NetworkStream stream = client.GetStream();
var buffer = new byte[1_024];
while(true)
{
if(cancel.IsCancellationRequested)
{
break;
}
var received = await stream.ReadAsync(buffer, 0, buffer.Length, cancel);
if(received == 0)
{
await Task.Delay(100);
continue;
}
OnReceived?.Invoke(buffer);
}
listener.Stop();
}
}
UDP
I can use UdpClient to send and receive data over UDP like TCP.
UdpSender.cs
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace SocketSample.Networks;
public class UdpSender
{
public async Task SendAsync(IPAddress? ipAddress, string message)
{
if(string.IsNullOrEmpty(message))
{
return;
}
await SendAsync(ipAddress, Encoding.UTF8.GetBytes(message));
}
public async Task SendAsync(IPAddress? ipAddress, byte[] data)
{
if(ipAddress == null ||
data.Length <= 0)
{
return;
}
var ipEndPoint = new IPEndPoint(ipAddress, 13);
using var udpClient = new UdpClient();
await udpClient.SendAsync(data, data.Length, ipEndPoint);
}
}
UdpReceiver.cs
using System.Net;
using System.Net.Sockets;
namespace SocketSample.Networks;
public class UdpReceiver
{
public Action<byte[]>? OnReceived;
public async Task ReceiveAsync(CancellationToken cancel)
{
// To accept access from other machines, I have to set "IPAddress.Any".
var ipEndPoint = new IPEndPoint(IPAddress.Any, 13);
using var udpClient = new UdpClient(ipEndPoint);
while(true)
{
if(cancel.IsCancellationRequested)
{
break;
}
var received = await udpClient.ReceiveAsync(cancel);
if(received.Buffer.Length > 0)
{
this.OnReceived?.Invoke(received.Buffer);
}
}
}
}
Switching roles with command line arguments
Because I don't want to create four applications for sending and receiving data over TCP and UDP, I decided switch the processing with command line arguments.
In this time, I used "System.CommandLine".
I can get the result by EventHandler, but because I want to wait for the result of "RootCommand.Invoke", I used "TaskCompletionSource".
- Tutorial: Get started with System.CommandLine - Microsoft Learn
- TaskCompletionSource Class - Microsoft Learn
ArgsParser.cs
using System.Net;
using System.CommandLine;
namespace SocketSample.Commands;
public enum Protocol
{
Tcp = 0,
Udp,
}
public enum Role
{
Send = 0,
Receive
}
public enum DataType
{
Text = 0,
}
public record CommandLineArgs(Protocol Protocol, Role Role, DataType DataType, IPAddress? IPAddress, string Value);
public class ArgsParser
{
public Task<CommandLineArgs> ParseAsync(string[] args)
{
// For waiting the result of "RootCommand.Invoke"
var task = new TaskCompletionSource<CommandLineArgs>();
// Specify option name(-p), type of input value(string), description("Protocol: ~"), and default value(empty string).
var protocolOption = new Option<string>(
name: "-p",
description: "Protocol: TCP or UDP",
getDefaultValue: () => "");
var ipAddressOption = new Option<string>(
name: "-a",
description: "IPAddress to send",
getDefaultValue: () => "");
var rollOption = new Option<string>(
name: "-r",
description: "Role: Send or Receive",
getDefaultValue: () => "");
var dataTypeOption = new Option<string>(
name: "-t",
description: "DataType: Text or file path",
getDefaultValue: () => "");
var valueOption = new Option<string>(
name: "-v",
description: "Value to send",
getDefaultValue: () => "");
var rootCommand = new RootCommand("TCP/UDP sample");
rootCommand.AddOption(protocolOption);
rootCommand.AddOption(ipAddressOption);
rootCommand.AddOption(rollOption);
rootCommand.AddOption(dataTypeOption);
rootCommand.AddOption(valueOption);
rootCommand.SetHandler((protocol, ipAddress, roll, dataType, value) =>
{
task.SetResult(new CommandLineArgs(ParseProtocol(protocol), ParseRole(roll),
ParseDataType(dataType), ParseIPAddress(ipAddress), value));
},
protocolOption, ipAddressOption, rollOption, dataTypeOption, valueOption);
rootCommand.Invoke(args);
return task.Task;
}
private Protocol ParseProtocol(string protocolText)
{
switch(protocolText.ToUpper())
{
case "TCP":
return Protocol.Tcp;
case "UDP":
return Protocol.Udp;
default:
return Protocol.Tcp;
}
}
private Role ParseRole(string roleText)
{
switch(roleText.ToUpper())
{
case "SEND":
return Role.Send;
case "RECEIVE":
return Role.Receive;
default:
return Role.Send;
}
}
private DataType ParseDataType(string dataTypeText)
{
switch(dataTypeText.ToUpper())
{
case "TEXT":
return DataType.Text;
default:
return DataType.Text;
}
}
private IPAddress? ParseIPAddress(string ipAddressText)
{
Console.WriteLine(ipAddressText);
if(string.IsNullOrEmpty(ipAddressText) ||
IPAddress.TryParse(ipAddressText, out var ipAddress) == false)
{
return null;
}
return ipAddress;
}
}
Now, I can call these classes like below.
Program.cs
using SocketSample.Commands;
using SocketSample.Networks;
var parser = new ArgsParser();
var parsedArgs = await parser.ParseAsync(args);
await ExecuteAsync(parsedArgs);
async Task ExecuteAsync(CommandLineArgs args)
{
var cancel = new CancellationTokenSource();
switch(args.Protocol)
{
case Protocol.Tcp:
await ExecuteTcpAsync(args);
break;
case Protocol.Udp:
await ExecuteUdpAsync(args);
break;
}
}
async Task ExecuteTcpAsync(CommandLineArgs args)
{
var cancel = new CancellationTokenSource();
switch(args.Role)
{
case Role.Send:
var sender = new TcpSender();
await sender.SendAsync(args.IPAddress, args.Value);
break;
case Role.Receive:
var receiver = new TcpReceiver();
receiver.OnReceived += (data) => {
Console.WriteLine($"Receive {System.Text.Encoding.UTF8.GetString(data)}");
};
await receiver.ReceiveAsync(cancel.Token);
cancel.Cancel();
break;
}
}
async Task ExecuteUdpAsync(CommandLineArgs args)
{
var cancel = new CancellationTokenSource();
switch(args.Role)
{
case Role.Send:
var sender = new UdpSender();
await sender.SendAsync(args.IPAddress, args.Value);
break;
case Role.Receive:
var receiver = new UdpReceiver();
receiver.OnReceived += (data) => {
Console.WriteLine($"Receive {System.Text.Encoding.UTF8.GetString(data)}");
};
await receiver.ReceiveAsync(cancel.Token);
cancel.Cancel();
break;
}
}