C# Socket
last modified October 18, 2026
In this article we show how to work with sockets in C#.
Network protocols
TCP/IP is a suite of protocols used by devices to communicate over the Internet and most local networks. TCP is reliable: it establishes a connection, retransmits lost data, and delivers the bytes in order. It is used by services such as HTTP, SMTP, or FTP. UDP is connectionless; it sends independent datagrams without a guarantee of delivery or order. It has lower overhead and is used by services such as DNS, VoIP, or online games.
Socket
In programming, a socket is an endpoint of a communication between
two programs running on a network. In .NET, the Socket class from
the System.Net.Sockets namespace is a thin wrapper over the
operating system's sockets. It provides both synchronous and asynchronous
methods for sending and receiving data.
The higher-level TcpClient, TcpListener, and
UdpClient classes are built on top of Socket; for
HTTP, we use HttpClient. Working with the Socket
class directly is useful for learning how network protocols work and when we
need full control over the connection.
C# UDP Socket example
The Quote of the Day (QOTD) is a simple service defined in RFC 865. The client sends a datagram to port 17 and the server replies with a short quote.
using System.Net;
using System.Net.Sockets;
using System.Text;
IPAddress[] addresses = await Dns.GetHostAddressesAsync("djxmmx.net");
IPAddress address = addresses[0];
var ep = new IPEndPoint(address, 17);
using var socket = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
await socket.SendToAsync(Array.Empty<byte>(), ep);
byte[] buf = new byte[1024];
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var res = await socket.ReceiveFromAsync(buf, ep, cts.Token);
Console.WriteLine($"Message size: {res.ReceivedBytes}");
Console.WriteLine($"Received from: {res.RemoteEndPoint}");
Console.WriteLine(Encoding.ASCII.GetString(buf, 0, res.ReceivedBytes));
The program is a client of a QOTD service. Note that such public services are often short-lived.
IPAddress[] addresses = await Dns.GetHostAddressesAsync("djxmmx.net");
IPAddress address = addresses[0];
The Dns.GetHostAddressesAsync method resolves the host name to its
IP addresses. A host may have IPv4 and IPv6 addresses.
using var socket = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
A UDP socket is created. We pass the address family, the socket type, and the protocol type. We take the address family from the resolved address; an IPv4 socket cannot send to an IPv6 address and vice versa.
await socket.SendToAsync(Array.Empty<byte>(), ep);
We send an empty datagram to the endpoint. The content of the request is ignored by the server.
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var res = await socket.ReceiveFromAsync(buf, ep, cts.Token);
The ReceiveFromAsync method waits for a datagram and stores it in
the buffer. Since UDP does not guarantee delivery, the reply may never come; we
give up after five seconds. The ReceiveTimeout property applies
only to the synchronous methods, so we use a cancellation token. The method
returns a SocketReceiveFromResult with the number of received
bytes and the sender's endpoint.
$ dotnet run Message size: 134 Received from: 104.9.242.101:17 "Here's the rule for bargains: "Do other men, for they would do you." That's the true business precept." Charles Dickens (1812-70)
C# Socket HTTP HEAD request
A HEAD request is an HTTP GET request without a message body. The headers of a response contain metadata, such as the status, the content type, or the date of the last modification.
using System.Net.Sockets;
using System.Text;
string host = "example.com";
int port = 80;
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(host, port);
Console.WriteLine($"Connected to {socket.RemoteEndPoint}");
string request = $"HEAD / HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n";
byte[] data = Encoding.ASCII.GetBytes(request);
await socket.SendAsync(data);
byte[] buf = new byte[4096];
int n = await socket.ReceiveAsync(buf);
Console.WriteLine(Encoding.ASCII.GetString(buf, 0, n));
In the code example, we send a HEAD request to example.com.
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); await socket.ConnectAsync(host, port);
HTTP/1.1 and HTTP/2 run over TCP, so we create a stream socket. The
constructor without the address family creates a dual-mode socket,
which can connect to both IPv6 and IPv4 addresses. The
ConnectAsync overload that takes a host name resolves it and
tries the addresses one by one until it succeeds. If the connection fails, it
throws a SocketException.
Console.WriteLine($"Connected to {socket.RemoteEndPoint}");
On a dual-mode socket, an IPv4 address is shown as an IPv4-mapped IPv6 address.
string request = $"HEAD / HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n";
The request line contains the method, the resource path, and the protocol
version. HTTP/1.1 requires the Host header. With
Connection: close, we ask the server to close the connection after
the response. Each line ends with \r\n; an empty line ends the
headers. The format is described in RFC 9112.
int n = await socket.ReceiveAsync(buf); Console.WriteLine(Encoding.ASCII.GetString(buf, 0, n));
We receive and print the headers. A short response fits into a single read; in general, we must read in a loop, as shown in the next example.
$ dotnet run Connected to [::ffff:104.20.23.154]:80 HTTP/1.1 200 OK Date: Sun, 18 Oct 2026 07:40:48 GMT Content-Type: text/html; charset=utf-8 Connection: close Server: cloudflare Last-Modified: Tue, 13 Oct 2026 22:05:48 GMT Allow: GET, HEAD Accept-Ranges: bytes Age: 14043 cf-cache-status: HIT CF-RAY: a4c5e5a38e1d696b-BTS alt-svc: h3=":443"; ma=86400
C# Socket HTTP GET request
The HTTP GET method requests a representation of the specified resource.
using System.Net.Sockets;
using System.Text;
string host = "example.com";
int port = 80;
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(host, port);
string request = $"GET / HTTP/1.0\r\nHost: {host}\r\n\r\n";
await socket.SendAsync(Encoding.ASCII.GetBytes(request));
using var ms = new MemoryStream();
byte[] buf = new byte[1024];
int n;
while ((n = await socket.ReceiveAsync(buf)) > 0)
{
ms.Write(buf, 0, n);
}
string response = Encoding.UTF8.GetString(ms.ToArray());
int idx = response.IndexOf("\r\n\r\n");
string headers = response[..idx];
string body = response[(idx + 4)..];
Console.WriteLine($"Received {ms.Length} bytes");
Console.WriteLine(headers.Split("\r\n")[0]);
int start = body.IndexOf("<title>") + "<title>".Length;
int end = body.IndexOf("</title>");
Console.WriteLine($"Title: {body[start..end]}");
The example reads the home page of example.com and prints the status line and the title of the page.
string request = $"GET / HTTP/1.0\r\nHost: {host}\r\n\r\n";
This time we use HTTP/1.0. An HTTP/1.1 server may send the body in chunks, each preceded by its size, which we would have to decode. An HTTP/1.0 response contains the body as is, and the server closes the connection after sending it.
while ((n = await socket.ReceiveAsync(buf)) > 0)
{
ms.Write(buf, 0, n);
}
We do not know the size of the response in advance, so we read it in chunks
until ReceiveAsync returns 0, which means that the server has
closed the connection. We collect the bytes in a memory stream and decode them
at the end; decoding each chunk separately could split a multi-byte UTF-8
character.
int idx = response.IndexOf("\r\n\r\n");
string headers = response[..idx];
string body = response[(idx + 4)..];
The headers are separated from the body by an empty line.
$ dotnet run Received 892 bytes HTTP/1.1 200 OK Title: Example Domain
The server replies with its own protocol version in the status line, but it follows the rules of HTTP/1.0 in the response.
C# Socket echo server
In the following example, we create a TCP server and a client. The server sends back each received message in uppercase.
using System.Net;
using System.Net.Sockets;
using System.Text;
using var listener = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
listener.Bind(new IPEndPoint(IPAddress.Loopback, 5050));
listener.Listen(backlog: 10);
Console.WriteLine($"Listening on {listener.LocalEndPoint}");
while (true)
{
Socket client = await listener.AcceptAsync();
_ = HandleClientAsync(client);
}
static async Task HandleClientAsync(Socket client)
{
using (client)
{
Console.WriteLine($"Client connected: {client.RemoteEndPoint}");
byte[] buf = new byte[1024];
int n;
while ((n = await client.ReceiveAsync(buf)) > 0)
{
string msg = Encoding.UTF8.GetString(buf, 0, n);
Console.WriteLine($"Received: {msg}");
byte[] reply = Encoding.UTF8.GetBytes(msg.ToUpperInvariant());
await client.SendAsync(reply);
}
Console.WriteLine("Client disconnected");
}
}
This is the server.
listener.Bind(new IPEndPoint(IPAddress.Loopback, 5050)); listener.Listen(backlog: 10);
We bind the socket to port 5050 on the loopback interface, so that only
programs on the same machine can connect. The Listen method puts
the socket into the listening state; the backlog is the maximum number of
pending connections waiting to be accepted.
Socket client = await listener.AcceptAsync(); _ = HandleClientAsync(client);
The AcceptAsync method waits for a client and returns a new
socket for the connection. We handle each client in a separate task without
awaiting it, so the server can accept other clients in the meantime.
while ((n = await client.ReceiveAsync(buf)) > 0)
We read messages until the client closes the connection.
using System.Net;
using System.Net.Sockets;
using System.Text;
using var socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 5050));
string[] messages = ["hello", "an old falcon", "bye"];
byte[] buf = new byte[1024];
foreach (var msg in messages)
{
await socket.SendAsync(Encoding.UTF8.GetBytes(msg));
int n = await socket.ReceiveAsync(buf);
Console.WriteLine($"{msg} -> {Encoding.UTF8.GetString(buf, 0, n)}");
}
socket.Shutdown(SocketShutdown.Both);
This is the client. It sends three messages and prints the replies.
socket.Shutdown(SocketShutdown.Both);
The Shutdown method tells the other side that no more data will be
sent; the server's ReceiveAsync then returns 0.
$ dotnet run hello -> HELLO an old falcon -> AN OLD FALCON bye -> BYE
We start the server in one terminal and the client in another. This is the output of the server:
$ dotnet run Listening on 127.0.0.1:5050 Client connected: 127.0.0.1:40290 Received: hello Received: an old falcon Received: bye Client disconnected
TCP is a byte stream; it does not preserve message boundaries. Here each
message is answered before the next one is sent, so the messages arrive
separately. In general, two messages sent quickly one after another may be
received in a single ReceiveAsync call, and a long message may be
split into several. Real protocols therefore delimit messages, for instance
with a newline character or a length prefix. The
TcpListener article shows a line-based
protocol built on TcpListener and streams.
Source
Socket class - language reference
In this article we have worked with sockets in C#.
Author
List all C# tutorials.