C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
75: Building a Simple TCP Client and Server
When you first dive into System.Net.Sockets, it feels a bit like stepping back in time. You aren't dealing with high-level APIs or JSON objects; you're dealing with raw streams of bytes. I remember the first time I tried this—I expected a SendString() method and was annoyed to find out I had to manually encode everything. But once you wrap your head around the "stream" concept, it all clicks.
How do I actually get a server to "listen" for a client?
The core of any TCP server is the TcpListener. You tell it which IP address and port to bind to, and then you put it in a state where it's actively waiting. The trick is the AcceptTcpClientAsync() method. This is a blocking call—meaning your code just stops there until a client actually connects. In a real production app, you'd handle this with a more complex asynchronous loop, but for a simple tool, a while loop does the trick.
using System.Net;
using System.Net.Sockets;
TcpListener server = new TcpListener(IPAddress.Any, 5000);
server.Start();
Console.WriteLine("Server started. Waiting for a connection...");
while (true)
{
// The code pauses here until a client connects
using TcpClient client = await server.AcceptTcpClientAsync();
Console.WriteLine("Client connected!");
// Now we handle the communication (covered in the next section)
}
Since TCP uses bytes, how do I send and receive actual text?
This is where most people get tripped up. TCP doesn't know what a "string" or a "message" is; it only knows bytes. To send text, you have to encode your string into a byte array using Encoding.UTF8. On the other end, you do the exact opposite: you read the bytes into a buffer and decode them back into a string.
Let's say we're building a "Remote Status Monitor." The client sends the word "STATUS", and the server replies with the current server time.
// SERVER SIDE: Reading the request
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
string request = Encoding.UTF8.GetString(buffer, 0, bytesRead);
if (request == "STATUS")
{
string response = $"Server is online. Time: {DateTime.Now}";
byte[] responseBytes = Encoding.UTF8.GetBytes(response);
await stream.WriteAsync(responseBytes, 0, responseBytes.Length);
}
How do I stop the server from crashing when a client disconnects?
If you've ever run a loop that reads from a stream, you've probably hit the "zero byte" problem. When a client closes the connection gracefully, stream.ReadAsync() doesn't throw an exception—it simply returns 0. If you don't check for this, your code might enter an infinite loop trying to process an empty string, which will eventually eat up your CPU or crash your app.
I always tell my juniors to treat a return value of 0 from a read operation as a "Goodbye" signal. You should immediately break your communication loop and dispose of the client.
while (true)
{
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 0)
{
Console.WriteLine("Client disconnected.");
break;
}
// Process data...
}
What's the right way to write the client side?
The client is much simpler because it doesn't have to "listen"—it just initiates. You use TcpClient to connect to the server's IP and port. Just remember that the NetworkStream is a two-way street; you can read from it and write to it using the same object. I highly recommend wrapping the client in a using block so that the socket closes properly even if an error occurs during the transmission.
using TcpClient client = new TcpClient();
await client.ConnectAsync("127.0.0.1", 5000);
NetworkStream stream = client.GetStream();
byte[] data = Encoding.UTF8.GetBytes("STATUS");
await stream.WriteAsync(data, 0, data.Length);
byte[] buffer = new byte[1024];
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
Console.WriteLine("Server says: " + Encoding.UTF8.GetString(buffer, 0, bytesRead));
📋 Practical Task
Exercise: Build a Remote Command Trigger
Your goal is to expand the TCP logic from this lesson to create a "Remote Command Trigger." Instead of just a status check, the server should handle three specific commands sent from the client:
- "TIME": The server responds with the current system time.
- "UPTIME": The server responds with how long the server has been running (calculate this by storing a
DateTimewhen the server first starts). - "QUIT": The server responds with "Shutting down..." and then terminates the server application.
Requirements:
- Create a separate
Serverproject andClientproject. - The server must be able to handle multiple clients sequentially (one after another).
- Ensure the server correctly detects when a client has disconnected to avoid infinite loops.
- Use
UTF8encoding for all string-to-byte conversions.
There are no comments for now.