Simplest Single-Client TCP Server and Client in .NET Core
Goal
Build a minimal TCP server and client in .NET Core 3.1 that can exchange messages.
TCP Server
//Program.cs
public class Program
{
private const int Port = 8080;
public static async Task Main(String[] args)
{
var address = GetLocalIPAddress();
var tcpListener = new TcpListener(GetLocalIPAddress(), Port);
tcpListener.Start();
Console.WriteLine($"Server started. Listening to TCP clients at {address}, port {Port}");
while (true)
{
await WaitForClientConnectionAndHandleAsync(tcpListener);
}
}
static IPAddress GetLocalIPAddress()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
return ip;
}
}
throw new Exception("No network adapters with an IPv4 address in the system!");
}
static async Task WaitForClientConnectionAndHandleAsync(TcpListener tcpListener)
{
Console.WriteLine($"Waiting for a client connection at {DateTime.Now.ToString()}..");
using(var client = tcpListener.AcceptTcpClient())
using(var networkStream = client.GetStream())
{
Console.WriteLine($"Client connected at {DateTime.Now.ToString()}...");
Console.WriteLine($"Receiving message...");
var receivedMessage = await ReceiveMessageAsync(networkStream);
Console.WriteLine($"Message received from client at {DateTime.Now.ToString()}: {receivedMessage}");
var messageToSend = GenerateAnswerMessageText(receivedMessage);
await SendMessageAsync(networkStream, messageToSend);
Console.WriteLine($"Sent message at {DateTime.Now.ToString()}: {messageToSend} ");
}
}
private static string GenerateAnswerMessageText(string receivedMessage)
{
return "makes Jack a dull boy";
}
static async Task SendMessageAsync(NetworkStream stream, string message)
{
byte[] helloMessage = new byte[100];
helloMessage = Encoding.Default.GetBytes(message);
await stream.WriteAsync(helloMessage, 0, helloMessage.Length);
}
static async Task<string> ReceiveMessageAsync(NetworkStream networkStream)
{
byte[] receivedMessage = new byte[1024];
await networkStream.ReadAsync(receivedMessage, 0, receivedMessage.Length);
var receivedMessageText = Encoding.Default.GetString(receivedMessage).Trim(' ', '\r', '\n');
return receivedMessageText;
}
}
Important: Don’t bind the server to localhost or 127.0.0.1. The server would only respond to that specific address, not its real IP when deployed on a network or in a container.
Test with netcat
netcat [SERVER_IP] [PORT]
hello server!
TCP Client
class Program
{
static async Task Main(string[] args)
{
try
{
Console.WriteLine("Enter server IP:");
var serverIp = Console.ReadLine();
Console.WriteLine("Enter server port:");
var serverPort = int.Parse(Console.ReadLine());
using(var client = new TcpClient(serverIp, serverPort))
{
using (var networkStream = client.GetStream())
{
Console.WriteLine("Connected to server...");
var messageToSendText = "All work and no play..";
await SendMessageAsync(networkStream, messageToSendText);
Console.WriteLine($"Sent message at {DateTime.Now.ToString()}: {messageToSendText}");
var messageReceivedText = await ReceiveMessageAsync(networkStream);
Console.WriteLine($"Received message at {DateTime.Now.ToString()}: {messageReceivedText}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed with exception: {ex.ToString()}");
}
}
static async Task SendMessageAsync(NetworkStream stream, string message)
{
var messageToSend = System.Text.Encoding.ASCII.GetBytes(message);
await stream.WriteAsync(messageToSend, 0, messageToSend.Length);
}
static async Task<string> ReceiveMessageAsync(NetworkStream stream)
{
var messageReceived = new Byte[1024];
var numberOfBytes = await stream.ReadAsync(messageReceived, 0, messageReceived.Length);
var messageReceivedText = System.Text.Encoding.ASCII.GetString(messageReceived, 0, numberOfBytes);
return messageReceivedText;
}
}
Docker Deployment
## Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:3.1 AS build-env
WORKDIR /app
## Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore
## Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o publish
## Build runtime image
FROM mcr.microsoft.com/dotnet/runtime:3.1
WORKDIR /app
COPY --from=build-env /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "Bustroker.TcpServer.ConsoleUI.dll"]
Build and run
docker build -t tcp-server .
docker run -p 8080:8080 tcp-server
Test from another terminal
netcat [SERVER_IP] [PORT]
hello tcp!