C# TcpClient
last modified October 18, 2026
In this article we show how to create network programs with TcpClient
in C#. The C# tutorial is a comprehensive tutorial on
the C# language.
C# TcpClient
The TcpClient class provides simple methods for connecting, sending,
and receiving stream data over a TCP network. It wraps a Socket and
exposes the data through a NetworkStream. The server side is
implemented with TcpListener; see the
C# TcpListener tutorial. For lower-level
control, there is the Socket class.
TCP is a reliable, connection-oriented protocol that delivers a stream of bytes.
It has no notion of messages. A single Write on one side can arrive as
several Read calls on the other, and the other way around, so the
application protocol must define where a message ends: a line terminator, a length
prefix, or closing the connection.
HttpClient. The
examples below send raw HTTP requests only to demonstrate how
TcpClient works. They use plain, unencrypted HTTP on port 80.
C# TcpClient HEAD request
The HTTP HEAD method requests the headers that would be returned if the specified resource were requested with the GET method.
using System.Net.Sockets;
using System.Text;
using var client = new TcpClient();
var hostname = "example.com";
client.Connect(hostname, 80);
using NetworkStream stream = client.GetStream();
stream.ReadTimeout = 2000;
var message = $"HEAD / HTTP/1.1\r\nHost: {hostname}\r\nUser-Agent: C# program\r\n"
+ "Connection: close\r\nAccept: text/html\r\n\r\n";
Console.WriteLine(message);
byte[] bytes = Encoding.UTF8.GetBytes(message);
stream.Write(bytes, 0, bytes.Length);
using var reader = new StreamReader(stream, Encoding.UTF8);
Console.WriteLine(reader.ReadToEnd());
In the example, we send a HEAD request to example.com. The request is
synchronous.
using var client = new TcpClient();
A new TcpClient is created. The using declaration
releases the underlying socket when the variable goes out of scope.
var hostname = "example.com"; client.Connect(hostname, 80);
With the Connect method, we connect to the host on the given port. The
name is resolved through DNS.
using NetworkStream stream = client.GetStream(); stream.ReadTimeout = 2000;
We get a NetworkStream and set a read timeout of two seconds. If no
data arrives in this time, a read throws an IOException.
var message = $"HEAD / HTTP/1.1\r\nHost: {hostname}\r\nUser-Agent: C# program\r\n"
+ "Connection: close\r\nAccept: text/html\r\n\r\n";
We define a HEAD request. It consists of the request line (method, resource, and
protocol version), the header fields, and an empty line. The
\r\n characters are part of the syntax. The Host header
is mandatory in HTTP/1.1, and Connection: close asks the server to
close the connection after the response, which lets us read until the end of the
stream. The details are described in
RFC 9110.
byte[] bytes = Encoding.UTF8.GetBytes(message); stream.Write(bytes, 0, bytes.Length);
The text request is converted to bytes with the GetBytes method and
written to the network stream.
using var reader = new StreamReader(stream, Encoding.UTF8); Console.WriteLine(reader.ReadToEnd());
A StreamReader reads the response until the server closes the
connection.
$ dotnet run HEAD / HTTP/1.1 Host: example.com User-Agent: C# program Connection: close Accept: text/html HTTP/1.1 200 OK Date: Sun, 18 Oct 2026 17:51:08 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 ...
The exact set of response headers depends on the server and changes over time.
C# TcpClient GET request
The HTTP GET method requests a representation of the specified resource.
using System.Net.Sockets;
using System.Text;
using var client = new TcpClient();
var hostname = "example.com";
client.Connect(hostname, 80);
using NetworkStream stream = client.GetStream();
stream.ReadTimeout = 2000;
var message = $"GET / HTTP/1.1\r\nHost: {hostname}\r\nAccept: text/html, charset=utf-8\r\n"
+ "Accept-Language: en-US\r\nUser-Agent: C# program\r\nConnection: close\r\n\r\n";
Console.WriteLine(message);
byte[] bytes = Encoding.UTF8.GetBytes(message);
stream.Write(bytes, 0, bytes.Length);
using var reader = new StreamReader(stream, Encoding.UTF8);
Console.WriteLine(reader.ReadToEnd());
The example retrieves the home page of the specified website.
var message = $"GET / HTTP/1.1\r\nHost: {hostname}\r\nAccept: text/html, charset=utf-8\r\n"
+ "Accept-Language: en-US\r\nUser-Agent: C# program\r\nConnection: close\r\n\r\n";
We define a GET request. Compared to HEAD, only the method differs.
$ dotnet run GET / HTTP/1.1 Host: example.com Accept: text/html, charset=utf-8 Accept-Language: en-US User-Agent: C# program Connection: close HTTP/1.1 200 OK Date: Sun, 18 Oct 2026 17:51:08 GMT Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: close Server: cloudflare ... 241 <!doctype html><html lang=en><head><meta charset=utf-8>... <title>Example Domain</title>...</html> 0
The response uses Transfer-Encoding: chunked. The body is split into
chunks, each preceded by its length in hexadecimal (here 241, which is
577 bytes), and the final chunk has length 0. A raw socket client sees these
markers; HttpClient removes them for us. We could avoid chunking by
sending an HTTP/1.0 request.
TcpClient POST request
The POST request sends data to the server. With the Content-Type
header field, we tell the server what kind of data is sent. With the
application/x-www-form-urlencoded type, the data is sent in the body
of the request as key-value pairs separated by '&', with a '=' between the key
and the value. This type is not suitable for binary data, because
non-alphanumeric characters in both keys and values are percent encoded.
using System.Net.Sockets;
using System.Text;
using var client = new TcpClient();
var hostname = "httpbin.org";
client.Connect(hostname, 80);
using NetworkStream stream = client.GetStream();
stream.ReadTimeout = 5000;
var data = "name=John+Doe&occupation=gardener";
int n = Encoding.UTF8.GetByteCount(data);
var message = $"POST /anything HTTP/1.1\r\nHost: {hostname}\r\nAccept: */*\r\n"
+ "User-Agent: C# program\r\nContent-Type: application/x-www-form-urlencoded\r\n"
+ $"Content-Length: {n}\r\nConnection: close\r\n\r\n{data}";
Console.WriteLine(message);
byte[] bytes = Encoding.UTF8.GetBytes(message);
stream.Write(bytes, 0, bytes.Length);
Console.WriteLine("---------------------");
using var reader = new StreamReader(stream, Encoding.UTF8);
Console.WriteLine(reader.ReadToEnd());
The example sends a POST request with URL-encoded data in its body.
var data = "name=John+Doe&occupation=gardener"; int n = Encoding.UTF8.GetByteCount(data);
This is the data to be sent. We also need its length in bytes, not in characters; the two differ for non-ASCII text.
var message = $"POST /anything HTTP/1.1\r\nHost: {hostname}\r\nAccept: */*\r\n"
+ "User-Agent: C# program\r\nContent-Type: application/x-www-form-urlencoded\r\n"
+ $"Content-Length: {n}\r\nConnection: close\r\n\r\n{data}";
We construct a POST request. We must set the Content-Type and the
Content-Length header fields. The header and the body are separated
by an empty line, that is by \r\n\r\n.
$ dotnet run
POST /anything HTTP/1.1
Host: httpbin.org
Accept: */*
User-Agent: C# program
Content-Type: application/x-www-form-urlencoded
Content-Length: 33
Connection: close
name=John+Doe&occupation=gardener
---------------------
HTTP/1.1 200 OK
Date: Sun, 18 Oct 2026 17:51:08 GMT
Content-Type: application/json
Content-Length: 487
Connection: close
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
"args": {},
"data": "",
"files": {},
"form": {
"name": "John Doe",
"occupation": "gardener"
},
"headers": {
"Accept": "*/*",
"Content-Length": "33",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "C# program",
"X-Amzn-Trace-Id": "Root=1-6ad5070c-2ee56d8809ca237e7f6bb310"
},
"json": null,
"method": "POST",
"origin": "203.0.113.7",
"url": "http://httpbin.org/anything"
}
C# TcpClient async request
In the following example, we create an asynchronous request. Network I/O spends most of its time waiting, so the asynchronous methods avoid blocking a thread.
using System.Net.Sockets;
using System.Text;
string host = "example.com";
int port = 80;
using var client = new TcpClient();
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await client.ConnectAsync(host, port, cts.Token);
await using NetworkStream stream = client.GetStream();
var request = $"GET / HTTP/1.1\r\nHost: {host}\r\nUser-Agent: Console app\r\nConnection: close\r\n\r\n";
await stream.WriteAsync(Encoding.UTF8.GetBytes(request), cts.Token);
using var reader = new StreamReader(stream, Encoding.UTF8);
string response = await reader.ReadToEndAsync(cts.Token);
Console.WriteLine(response.Split("\r\n")[0]);
Console.WriteLine($"received {response.Length} characters");
The example sends an asynchronous GET request to example.com and
prints the status line and the size of the response.
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); await client.ConnectAsync(host, port, cts.Token);
We connect asynchronously. The cancellation token gives us a simple overall
timeout: when five seconds pass, the pending operation is cancelled and throws an
OperationCanceledException. The same token is passed to the write and
read operations below.
await using NetworkStream stream = client.GetStream();
NetworkStream supports asynchronous disposal.
await stream.WriteAsync(Encoding.UTF8.GetBytes(request), cts.Token);
The WriteAsync overload with a ReadOnlyMemory<byte>
writes the request bytes. We build the request as a single string with explicit
\r\n terminators. Using WriteLineAsync would append an
extra newline, which does not belong to the request.
string response = await reader.ReadToEndAsync(cts.Token);
The ReadToEndAsync method reads the response until the server closes
the connection.
$ dotnet run HTTP/1.1 200 OK received 931 characters
C# TcpClient echo client
To see both sides of a TCP conversation, we start a small echo server in the same
program, using TcpListener on a free local port, and connect to it
with TcpClient. The messages are lines of text, so a line terminator
marks where a message ends.
using System.Net;
using System.Net.Sockets;
using System.Text;
// a tiny echo server on a free local port
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
int port = ((IPEndPoint)listener.LocalEndpoint).Port;
var server = Task.Run(async () =>
{
using TcpClient peer = await listener.AcceptTcpClientAsync();
using var s = peer.GetStream();
byte[] buf = new byte[1024];
int n;
while ((n = await s.ReadAsync(buf)) > 0)
{
await s.WriteAsync(buf.AsMemory(0, n));
}
});
using var client = new TcpClient();
await client.ConnectAsync(IPAddress.Loopback, port);
Console.WriteLine($"connected: {client.Connected}");
NetworkStream stream = client.GetStream();
using var reader = new StreamReader(stream, Encoding.UTF8);
using var writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true };
foreach (var msg in new[] { "hello", "falcon", "owl" })
{
await writer.WriteLineAsync(msg);
string? echo = await reader.ReadLineAsync();
Console.WriteLine($"sent '{msg}', received '{echo}'");
}
client.Client.Shutdown(SocketShutdown.Send); // tells the server that we are done
await server;
listener.Stop();
The server accepts one connection and writes back everything it receives. The client sends three lines and prints the echoes.
var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); int port = ((IPEndPoint)listener.LocalEndpoint).Port;
Port 0 asks the operating system for any free port, which we then read from the local endpoint. This avoids clashes with other programs.
using var writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true };
We wrap the network stream in a StreamReader and a
StreamWriter for line-oriented text. AutoFlush is
important here; without it the line would sit in the writer's buffer and the
server would never receive it, so we would wait for the echo forever.
client.Client.Shutdown(SocketShutdown.Send); await server;
Shutting down the sending side tells the server that no more data will come, its read returns 0 and its loop ends. Then we wait for the server task to finish.
$ dotnet run connected: True sent 'hello', received 'hello' sent 'falcon', received 'falcon' sent 'owl', received 'owl'
C# TcpClient connection errors and timeouts
Connections fail in several ways. When nothing listens on the port, the operating
system rejects the attempt at once with a SocketException. When the
address does not respond, the attempt hangs until the operating system gives up,
which can take a long time; a cancellation token gives us control over this wait.
using System.Net;
using System.Net.Sockets;
// a port with no listener refuses the connection
using var probe = new TcpListener(IPAddress.Loopback, 0);
probe.Start();
int freePort = ((IPEndPoint)probe.LocalEndpoint).Port;
probe.Stop();
try
{
using var client = new TcpClient();
await client.ConnectAsync(IPAddress.Loopback, freePort);
}
catch (SocketException e)
{
Console.WriteLine($"{e.SocketErrorCode}: {e.Message}");
}
// a non-routable address: the connection attempt times out
try
{
using var client = new TcpClient();
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(500));
await client.ConnectAsync("10.255.255.1", 80, cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("connection attempt timed out");
}
catch (SocketException e)
{
Console.WriteLine($"{e.SocketErrorCode}");
}
The program provokes both failures.
catch (SocketException e)
{
Console.WriteLine($"{e.SocketErrorCode}: {e.Message}");
}
The SocketErrorCode property tells what went wrong, for example
ConnectionRefused, HostNotFound, or
TimedOut.
await client.ConnectAsync("10.255.255.1", 80, cts.Token);
The address is in a private range that is not routed anywhere on most networks, so
the connection attempt never completes and our 500 ms token cancels it. On some
networks this address may answer immediately with an error, in which case the
SocketException branch runs instead.
$ dotnet run ConnectionRefused: Connection refused connection attempt timed out
Source
In this article we have worked with C# TcpClient.
Author
List all C# tutorials.