C# tutorials > Input/Output (I/O) and Networking > .NET Networking > Implementing client-server communication
Implementing client-server communication
This tutorial demonstrates how to implement client-server communication in C# using the System.Net
namespace. We'll cover creating a simple server that listens for connections and a client that connects to the server, sends data, and receives a response. This is a fundamental concept in network programming and is used in various applications, from web servers to multiplayer games.
Basic Client-Server Architecture
Before diving into the code, let's understand the basic client-server architecture. A server listens for incoming connections on a specific port. A client initiates a connection to the server's IP address and port. Once a connection is established, the client and server can exchange data. This exchange typically follows a protocol, defining the format and meaning of the data being transmitted.
Creating the Server
This code creates a simple TCP server that listens for connections on port 13000. It uses a TcpListener
to listen for incoming connections. When a client connects, the server accepts the connection using AcceptTcpClient()
. It then reads data from the client using a NetworkStream
, converts the data to uppercase, and sends the uppercase data back to the client. Finally, it closes the connection.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
public class Server
{
public static void Main(string[] args)
{
TcpListener server = null;
try
{
// Set the TcpListener on port 13000.
Int32 port = 13000;
IPAddress localAddr = IPAddress.Loopback; // Or IPAddress.Any for all interfaces
// TcpListener server = new TcpListener(port);
server = new TcpListener(localAddr, port);
// Start listening for client requests.
server.Start();
// Buffer for reading data
Byte[] bytes = new Byte[256];
String data = null;
// Enter the listening loop.
while (true)
{
Console.Write("Waiting for a connection... ");
// Perform a blocking call to accept incoming connections.
TcpClient client = server.AcceptTcpClient();
Console.WriteLine("Connected!");
data = null;
// Get a stream object for reading and writing
NetworkStream stream = client.GetStream();
int i;
// Loop to receive all the data sent by the client.
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
{
// Translate data bytes to a ASCII string.
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
Console.WriteLine("Received: {0}", data);
// Process the data sent by the client.
data = data.ToUpper();
byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);
// Send back a response.
stream.Write(msg, 0, msg.Length);
Console.WriteLine("Sent: {0}", data);
}
// Shutdown and end connection
client.Close();
}
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
finally
{
// Stop listening for new clients.
server.Stop();
}
Console.WriteLine("\nHit enter to continue...");
Console.Read();
}
}
Creating the Client
This code creates a simple TCP client that connects to the server at 127.0.0.1 (localhost) on port 13000. It creates a TcpClient
and connects to the server. It then sends the message "Hello Server" to the server using a NetworkStream
and receives a response from the server. Finally, it closes the connection.
using System;
using System.Net.Sockets;
using System.Text;
public class Client
{
public static void Main(string[] args)
{
try
{
// Create a TcpClient.
// Note, for this client to work you need to have a TcpServer
// connected to the same address as specified.
Int32 port = 13000;
TcpClient client = new TcpClient("127.0.0.1", port);
// Translate the passed message into ASCII and store it as a Byte array.
String message = "Hello Server";
Byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
// Get a client stream for reading and writing.
NetworkStream stream = client.GetStream();
// Send the message to the connected TcpServer.
stream.Write(data, 0, data.Length);
Console.WriteLine("Sent: {0}", message);
// Receive the TcpServer.response.
// Buffer to store the response bytes.
data = new Byte[256];
// String to store the response ASCII representation.
String responseData = string.Empty;
// Read the first batch of the TcpServer response bytes.
Int32 bytes = stream.Read(data, 0, data.Length);
responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
Console.WriteLine("Received: {0}", responseData);
// Close everything.
stream.Close();
client.Close();
}
catch (ArgumentNullException e)
{
Console.WriteLine("ArgumentNullException: {0}", e);
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
Console.WriteLine("\n Press Enter to continue...");
Console.Read();
}
}
Running the Code
1. **Compile the code:** Compile both the `Server.cs` and `Client.cs` files using a C# compiler (like `csc.exe` or Visual Studio). 2. **Run the server:** Execute the compiled `Server.exe` file. The server will start listening for connections. 3. **Run the client:** Execute the compiled `Client.exe` file. The client will connect to the server, send the message, receive the response, and then exit. You should see the server output displaying "Waiting for a connection... Connected! Received: Hello Server Sent: HELLO SERVER" and the client output displaying "Sent: Hello Server Received: HELLO SERVER".
Concepts Behind the Snippet
This snippet illustrates several key networking concepts:
TcpClient
and TcpListener
use sockets internally.
Real-Life Use Case
Client-server communication is fundamental to many applications:
Best Practices
try-catch
blocks to catch potential exceptions.async
and await
) to avoid blocking the main thread while waiting for I/O operations to complete. This prevents the application from becoming unresponsive.TcpClient
, NetworkStream
, etc.) using using
statements or explicit calls to Dispose()
to prevent resource leaks.
Interview Tip
When discussing client-server communication in an interview, be prepared to discuss the following:
When to use them
Use client-server communication when:
Memory Footprint
The memory footprint of client-server communication depends on the amount of data being transmitted and the number of concurrent connections. Minimize the amount of data transmitted and use efficient data structures to reduce memory consumption. Using asynchronous operations can also help to reduce the memory footprint by allowing the server to handle more concurrent connections without creating a large number of threads.
Alternatives
Alternatives to raw sockets include:
HttpClient
in C#) is a more common and higher-level protocol.
Pros
Cons
FAQ
-
What is the difference between TCP and UDP?
TCP is a connection-oriented protocol that provides reliable, ordered, and error-checked delivery of data. UDP is a connectionless protocol that is faster but less reliable. TCP is suitable for applications that require reliable data transfer, such as web browsing and file transfer. UDP is suitable for applications that can tolerate some data loss, such as streaming video and online games. -
How do I handle multiple concurrent client connections?
You can handle multiple concurrent client connections using threads or asynchronous operations. With threads, each client connection is handled by a separate thread. With asynchronous operations, a single thread can handle multiple client connections without blocking. Asynchronous operations are generally more scalable and efficient than threads, especially for a large number of concurrent connections. -
How do I secure my client-server communication?
You can secure your client-server communication using encryption. TLS/SSL is a common encryption protocol that is used to protect data in transit. You can also use authentication to verify the identity of the client and server.