Implementing bidirectional data exchange over TCP in C# requires coordinating a listener that binds to a network interface and a client that initiates the connection. The .NET framework provides TcpListener and TcpClient to abstract low-level socket operations, while NetworkStream manages the underlying byte transmission pipeline.
Server-Side Implemnetation
The host application binds to a specific endpoint, enters a listening state, and blocks until a remote connection request arrives. Once established, the server reads the inbound payload, processes it, and reeturns a response through the same stream. Resource cleanup is enforced via deterministic disposal to prevent port leaks.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
public class TcpEndpoint
{
public static void Start()
{
const int ListeningPort = 9000;
var hostAddress = IPAddress.Loopback;
// Bind to the network interface and begin listening
var listener = new TcpListener(hostAddress, ListeningPort);
listener.Start();
Console.WriteLine($"Service active on {hostAddress}:{ListeningPort}. Awaiting requests...");
// Block until a client establishes a handshake
using (var activeConnection = listener.AcceptTcpClient())
using (var transmissionChannel = activeConnection.GetStream())
{
Console.WriteLine("Remote peer successfully connected.");
// Allocate buffer and capture incoming bytes
byte[] receptionBuffer = new byte[1024];
int bytesRead = transmissionChannel.Read(receptionBuffer, 0, receptionBuffer.Length);
string inboundPayload = Encoding.UTF8.GetString(receptionBuffer, 0, bytesRead);
Console.WriteLine($"Inbound: {inboundPayload}");
// Encode and dispatch outbound acknowledgment
string outboundMessage = "Server received your request.";
byte[] responseBytes = Encoding.UTF8.GetBytes(outboundMessage);
transmissionChannel.Write(responseBytes, 0, responseBytes.Length);
Console.WriteLine($"Outbound: {outboundMessage}");
}
listener.Stop();
}
}
Client-Side Implementation
The client component initiates the socket handshake, serializes a string into bytes, writes them to the network stream, and subsequently reads the server's reply. Both endpoints rely on synchronous I/O operations for straightforward data handling.
using System;
using System.Net.Sockets;
using System.Text;
public class TcpInitiator
{
public static void RunClient()
{
const int TargetPort = 9000;
const string TargetHost = "127.0.0.1";
// Create socket and establish connection
using (var socketHandler = new TcpClient())
{
socketHandler.Connect(TargetHost, TargetPort);
Console.WriteLine("Handshake complete. Tunnel established.");
using (var dataStream = socketHandler.GetStream())
{
// Serialize and transmit outbound payload
string requestMessage = "Client initiating data exchange.";
byte[] payloadBytes = Encoding.UTF8.GetBytes(requestMessage);
dataStream.Write(payloadBytes, 0, payloadBytes.Length);
Console.WriteLine($"Dispatched: {requestMessage}");
// Synchronously await and decode server response
byte[] receiveBuffer = new byte[1024];
int responseLength = dataStream.Read(receiveBuffer, 0, receiveBuffer.Length);
string serverReply = Encoding.UTF8.GetString(receiveBuffer, 0, responseLength);
Console.WriteLine($"Received: {serverReply}");
}
}
}
}
The exchange relies on TCP's connection-oriented guarantee, which ensures ordered delivery and integrity checks at the transport layer. The NetworkStream.Read method blocks the calling thread until data becomes available or the connection closes. UTF-8 encoding standardizes the conversion between string representations and the raw byte sequences required by the socket layer. Wrapping TcpClient and NetworkStream in using blocks ensures immediate socket disposal and prevents resource exhaustion in long-running applications.