Simplest single-client TCP client and server

Single-client 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;
    }
}

Test the server

Using netcat in linux

netcat [SERVER_IP] [PORT]
hello server!

Note on server IP

The IP to bind the server shouldn’t be hardcoded to localhost or similar, because the server would bound to it, and when it’s deployed in a network (or in a docker container), it would not respond to its real IP.

Simplest 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

# 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, run and test with netcat

docker build -t tcp-server .
docker run -p 8080:8080 tcp-server

From another terminal, use netcat

netcat [SERVER_IP] [PORT]
hello tcp!