ZetCode

C# network

last modified October 18, 2026

In this article we show how to create basic network programs in C#.

The System.Net namespace and its child namespaces, such as System.Net.Sockets, System.Net.Http, and System.Net.NetworkInformation, provide a programming interface for many of the protocols used on networks today.

C# Uri

Uri provides an object representation of a uniform resource identifier (URI) and easy access to the parts of the URI.

Program.cs
var uri = new Uri("https://user:pass@example.com:8443/users/index.html?name=Peter&age=23#info");

Console.WriteLine($"Scheme: {uri.Scheme}");
Console.WriteLine($"UserInfo: {uri.UserInfo}");
Console.WriteLine($"Host: {uri.Host}");
Console.WriteLine($"Port: {uri.Port}");
Console.WriteLine($"Authority: {uri.Authority}");
Console.WriteLine($"AbsolutePath: {uri.AbsolutePath}");
Console.WriteLine($"Query: {uri.Query}");
Console.WriteLine($"PathAndQuery: {uri.PathAndQuery}");
Console.WriteLine($"Fragment: {uri.Fragment}");
Console.WriteLine($"Segments: {string.Join(", ", uri.Segments)}");

var uri2 = new Uri("http://example.com/");
Console.WriteLine($"Default port: {uri2.Port}, IsDefaultPort: {uri2.IsDefaultPort}");

var baseUri = new Uri("https://example.com/docs/");
var combined = new Uri(baseUri, "../about.html");
Console.WriteLine($"Combined: {combined}");

Console.WriteLine(Uri.TryCreate("not a url", UriKind.Absolute, out _));

In this example, we work with the Uri class.

var uri = new Uri("https://user:pass@example.com:8443/users/index.html?name=Peter&age=23#info");

We create a Uri that contains all the common parts: a scheme, user information, a host, a port, a path, a query, and a fragment. The constructor throws a UriFormatException if the string is not a valid URI.

Console.WriteLine($"Scheme: {uri.Scheme}");
Console.WriteLine($"UserInfo: {uri.UserInfo}");
Console.WriteLine($"Host: {uri.Host}");
...

We print the various parts of the Uri. The Authority is the host with the port; the port is omitted if it is the default port of the scheme.

var uri2 = new Uri("http://example.com/");
Console.WriteLine($"Default port: {uri2.Port}, IsDefaultPort: {uri2.IsDefaultPort}");

If the port is not specified, the Port property returns the default port of the scheme; 80 for HTTP.

var baseUri = new Uri("https://example.com/docs/");
var combined = new Uri(baseUri, "../about.html");

A relative URI is resolved against a base URI.

Console.WriteLine(Uri.TryCreate("not a url", UriKind.Absolute, out _));

The TryCreate method validates a string without throwing an exception.

$ dotnet run
Scheme: https
UserInfo: user:pass
Host: example.com
Port: 8443
Authority: example.com:8443
AbsolutePath: /users/index.html
Query: ?name=Peter&age=23
PathAndQuery: /users/index.html?name=Peter&age=23
Fragment: #info
Segments: /, users/, index.html
Default port: 80, IsDefaultPort: True
Combined: https://example.com/about.html
False

C# UriBuilder

UriBuilder provides a convenient way to build a URI or modify the contents of a Uri instance without creating a new Uri instance for each modification.

Program.cs
var builder = new UriBuilder
{
    Scheme = "https",
    Host = "example.com",
    Path = "users",
    Query = "name=Peter Novak&age=23"
};

Uri uri = builder.Uri;
Console.WriteLine(uri);
Console.WriteLine(uri.AbsoluteUri);

builder.Path = "products";
builder.Query = $"q={Uri.EscapeDataString("C# & .NET")}";
Console.WriteLine(builder.Uri.AbsoluteUri);

The example builds URIs with UriBuilder.

var builder = new UriBuilder
{
    Scheme = "https",
    Host = "example.com",
    Path = "users",
    Query = "name=Peter Novak&age=23"
};

We set the parts of the URI with an object initializer.

Uri uri = builder.Uri;
Console.WriteLine(uri);
Console.WriteLine(uri.AbsoluteUri);

The Uri property returns the built Uri. The ToString method returns an unescaped, human-readable form, while AbsoluteUri returns the escaped form, which is suitable for sending over the network.

builder.Path = "products";
builder.Query = $"q={Uri.EscapeDataString("C# & .NET")}";

We modify the builder. The value of a query parameter must be escaped with Uri.EscapeDataString; otherwise the # and & characters would be interpreted as a fragment and a parameter separator.

$ dotnet run
https://example.com/users?name=Peter Novak&age=23
https://example.com/users?name=Peter%20Novak&age=23
https://example.com/products?q=C%23%20%26%20.NET

C# hostname

The Dns.GetHostName method gets the host name of the local computer.

Program.cs
using System.Net;

var hostName = Dns.GetHostName();
Console.WriteLine($"Hostname: {hostName}");

The program prints the hostname of our local computer.

$ dotnet run
Hostname: universum

C# Dns.GetHostAddresses

The Dns.GetHostAddresses method returns an array of IPAddress objects that holds the IP addresses for the host. We use its asynchronous version, GetHostAddressesAsync.

Program.cs
using System.Net;

var hostname = "wikipedia.org";

IPAddress[] addresses = await Dns.GetHostAddressesAsync(hostname);

foreach (IPAddress address in addresses)
{
    Console.WriteLine($"{address} {address.AddressFamily}");
}

The example prints all IP addresses of a website together with their address family.

$ dotnet run
2a02:ec80:300:ed1a::1 InterNetworkV6
185.15.59.224 InterNetwork

The output consists of an IPv6 and an IPv4 address. The actual addresses depend on the DNS server and the location.

C# Dns.GetHostEntry

The Dns.GetHostEntry method resolves a host name or an IP address to an IPHostEntry instance. Besides the addresses, the entry contains the host name and its aliases.

Program.cs
using System.Net;

var name = "wikipedia.org";
IPHostEntry host = await Dns.GetHostEntryAsync(name);

Console.WriteLine($"Host name: {host.HostName}");

foreach (var address in host.AddressList)
{
    Console.WriteLine(address);
}

The example prints the host name and the IP addresses of the wikipedia.org website.

foreach (var address in host.AddressList)
{
    Console.WriteLine(address);
}

The AddressList property contains a list of IP addresses that are associated with a host.

$ dotnet run
Host name: wikipedia.org
2a02:ec80:300:ed1a::1
185.15.59.224

C# Ping

Ping is a network administration utility used to test the availability of a host on an Internet Protocol (IP) network. Ping works by sending Internet Control Message Protocol (ICMP) echo request packets to the target host and waiting for an ICMP echo reply.

.NET contains the Ping class for sending ping requests. The Ping class uses instances of the PingReply class to return information about the operation and receive the reply.

Program.cs
using System.Net.NetworkInformation;

using var ping = new Ping();

PingReply reply = await ping.SendPingAsync("example.com", TimeSpan.FromSeconds(2));

if (reply.Status == IPStatus.Success)
{
    Console.WriteLine($"Status: {reply.Status}");
    Console.WriteLine($"IP address: {reply.Address}");
    Console.WriteLine($"Time: {reply.RoundtripTime} ms");
}
else
{
    Console.WriteLine(reply.Status);
}

We send a ping request to the example.com host.

PingReply reply = await ping.SendPingAsync("example.com", TimeSpan.FromSeconds(2));

The SendPingAsync method attempts to send an ICMP echo message. The second parameter is the timeout. If no reply arrives in time, the status is TimedOut.

$ dotnet run
Status: Success
IP address: 2606:4700:10::6814:179a
Time: 27 ms

See the C# Ping article for more examples.

C# Socket

In programming, a socket is an endpoint of a communication between two programs running on a network. Sockets are used to create a connection between a client program and a server program.

The System.Net.Sockets.Socket class implements the Berkeley sockets interface.

Program.cs
using System.Net.Sockets;
using System.Text;

string server = "example.com";
int port = 80;

var request = $"GET / HTTP/1.1\r\nHost: {server}\r\nConnection: close\r\n\r\n";
byte[] requestBytes = Encoding.ASCII.GetBytes(request);

using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect(server, port);

Console.WriteLine($"Connected to {socket.RemoteEndPoint}");

socket.Send(requestBytes);

var buffer = new byte[1024];
var sb = new StringBuilder();
int n;

while ((n = socket.Receive(buffer)) > 0)
{
    sb.Append(Encoding.UTF8.GetString(buffer, 0, n));
}

Console.WriteLine(sb);

The example uses the Socket class to send an HTTP request to a web server and receive the response. Note that socket programming is low-level. Classes such as HttpClient abstract away these low-level details.

var request = $"GET / HTTP/1.1\r\nHost: {server}\r\nConnection: close\r\n\r\n";

We define a GET request. A request line consists of the method, the resource path, and the HTTP version. In HTTP/1.1, the Host header is mandatory. Each line ends with the \r\n characters and an empty line ends the headers. The Connection: close header tells the server to close the connection after sending the response. The details are described in the RFC 9112 document.

byte[] requestBytes = Encoding.ASCII.GetBytes(request);

We transform the text data of our request into bytes.

using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect(server, port);

A new TCP socket is created. The SocketType.Stream provides reliable, two-way, connection-based byte streams. This constructor creates a dual-mode socket, which works with both IPv4 and IPv6 addresses. The Connect method resolves the host name and connects to the server.

socket.Send(requestBytes);

We send the request to the server with the Send method.

while ((n = socket.Receive(buffer)) > 0)
{
    sb.Append(Encoding.UTF8.GetString(buffer, 0, n));
}

We read the data from the socket with the Receive method until the server closes the connection, which is signaled by zero bytes read. We transform the bytes into text with the GetString method and add the text into a StringBuilder.

$ dotnet run
Connected to [2606:4700:10::6814:179a]:80
HTTP/1.1 200 OK
Date: Sat, 17 Oct 2026 16:52:52 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: close
Server: cloudflare
...

See the C# Socket article for more examples.

C# UdpClient

The UdpClient provides User Datagram Protocol (UDP) network services. It contains methods for sending and receiving connectionless UDP datagrams. We do not need to establish a connection with the remote host before sending and receiving data, since UDP is a connectionless transport protocol.

In the following example, we create a simple UDP echo server and a client in one program. The server sends every datagram back to its sender.

Program.cs
using System.Net;
using System.Net.Sockets;
using System.Text;

var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 5005);

using var server = new UdpClient(serverEndPoint);
var serverTask = RunEchoServer(server);

using var client = new UdpClient();

foreach (var msg in new[] { "Hello there", "How are you?", "Bye" })
{
    byte[] data = Encoding.UTF8.GetBytes(msg);
    await client.SendAsync(data, serverEndPoint);

    UdpReceiveResult res = await client.ReceiveAsync();
    Console.WriteLine($"client received: {Encoding.UTF8.GetString(res.Buffer)}");
}

await serverTask;

static async Task RunEchoServer(UdpClient server)
{
    for (int i = 0; i < 3; i++)
    {
        UdpReceiveResult res = await server.ReceiveAsync();
        Console.WriteLine($"server received {res.Buffer.Length} bytes from {res.RemoteEndPoint}");

        await server.SendAsync(res.Buffer, res.RemoteEndPoint);
    }
}

The client sends three messages and prints the echoed replies.

var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 5005);

using var server = new UdpClient(serverEndPoint);
var serverTask = RunEchoServer(server);

The server UdpClient is bound to port 5005 on the loopback interface. We start the server and keep its task.

UdpReceiveResult res = await server.ReceiveAsync();
...
await server.SendAsync(res.Buffer, res.RemoteEndPoint);

The ReceiveAsync method returns the datagram together with the endpoint of the sender. The server sends the same bytes back to it.

using var client = new UdpClient();
...
await client.SendAsync(data, serverEndPoint);

UdpReceiveResult res = await client.ReceiveAsync();

The client is bound to a random free port when it sends the first datagram.

$ dotnet run
server received 11 bytes from 127.0.0.1:33011
client received: Hello there
server received 12 bytes from 127.0.0.1:33011
client received: How are you?
server received 3 bytes from 127.0.0.1:33011
client received: Bye

The messages that we send are echoed back to us.

C# TcpClient

The TcpClient class provides client connections for TCP network services. It abstracts the low-level details of creating a Socket for requesting and receiving data over TCP.

Since the connection to the remote device is represented as a stream, data can be read and written with the .NET stream-handling techniques.

Program.cs
using System.Net.Sockets;
using System.Text;

using var client = new TcpClient();

var hostname = "example.com";
await client.ConnectAsync(hostname, 80);

using NetworkStream networkStream = client.GetStream();
networkStream.ReadTimeout = 2000;

var message = """
    GET / HTTP/1.1
    Host: example.com
    Accept: text/html
    User-Agent: C# console app
    Connection: close


    """.ReplaceLineEndings("\r\n");

byte[] bytes = Encoding.ASCII.GetBytes(message);
await networkStream.WriteAsync(bytes);

using var reader = new StreamReader(networkStream, Encoding.UTF8);
Console.WriteLine(await reader.ReadToEndAsync());

The example creates a GET request with the TcpClient.

using var client = new TcpClient();

var hostname = "example.com";
await client.ConnectAsync(hostname, 80);

A TCP client is created. We connect to a remote host with the ConnectAsync method.

using NetworkStream networkStream = client.GetStream();
networkStream.ReadTimeout = 2000;

With the GetStream method we obtain a network stream. We set the read timeout to two seconds.

var message = """
    GET / HTTP/1.1
    Host: example.com
    Accept: text/html
    User-Agent: C# console app
    Connection: close


    """.ReplaceLineEndings("\r\n");

We build up the GET request message with a raw string literal. The two empty lines produce the line terminator of the last header and the empty line that ends the headers. HTTP requires the \r\n line endings, so we replace the line endings of the source file with ReplaceLineEndings.

byte[] bytes = Encoding.ASCII.GetBytes(message);
await networkStream.WriteAsync(bytes);

We transform the text into bytes and write them to the network stream.

using var reader = new StreamReader(networkStream, Encoding.UTF8);
Console.WriteLine(await reader.ReadToEndAsync());

We read the whole response with a StreamReader.

$ dotnet run
HTTP/1.1 200 OK
Date: Sat, 17 Oct 2026 16:52:52 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: close
...

241
<!doctype html><html lang=en><head><meta charset=utf-8>...

The body is sent in chunked transfer encoding; the 241 is the size of the chunk in hexadecimal. Decoding such details is one of the reasons to use HttpClient. See the C# TcpClient article for more examples.

C# FTP and SMTP

The FtpWebRequest class, like the whole WebRequest family, is obsolete in modern .NET and produces the SYSLIB0014 warning. For FTP, use a third-party library such as FluentFTP; see the C# FluentFTP article.

The SmtpClient class from System.Net.Mail still works, but Microsoft does not recommend it for new development, since it does not support many modern protocols. The recommended alternative is the MailKit library; see the C# send mail article.

C# HttpClient

HttpClient is a class for sending HTTP requests and receiving HTTP responses from a resource identified by a URI.

Program.cs
using var client = new HttpClient();

using HttpResponseMessage res = await client.GetAsync("https://example.com");
Console.WriteLine($"{(int) res.StatusCode} {res.StatusCode}");
Console.WriteLine(res.Content.Headers.ContentType);

string content = await res.Content.ReadAsStringAsync();
Console.WriteLine($"{content.Length} characters received");

The example creates a GET request to a small website. We get the status code, the content type, and the body of the response.

using var client = new HttpClient();

A new HttpClient is created. An HttpClient is intended to be created once and reused for the lifetime of the application; creating a new instance for each request can exhaust the available sockets.

using HttpResponseMessage res = await client.GetAsync("https://example.com");

The GetAsync method sends a GET request to the specified URI as an asynchronous operation. The await operator suspends the evaluation of the enclosing async method until the asynchronous operation completes. When the asynchronous operation completes, the await operator returns the result of the operation.

string content = await res.Content.ReadAsStringAsync();

We read the body of the response as a string.

$ dotnet run
200 OK
text/html; charset=utf-8
577 characters received

We get the 200 OK status code; the website is up. See the C# HttpClient article for more examples.

C# HttpListener

HttpListener is a simple, programmatically controlled HTTP protocol listener. It is suitable for small tools and tests; for real web applications, use ASP.NET Core.

Program.cs
using System.Net;
using System.Text;

using var listener = new HttpListener();
listener.Prefixes.Add("http://localhost:8001/");

listener.Start();
Console.WriteLine("Listening on port 8001...");

while (true)
{
    HttpListenerContext context = await listener.GetContextAsync();
    HttpListenerRequest req = context.Request;

    Console.WriteLine($"{req.HttpMethod} {req.Url}");
    Console.WriteLine($"User agent: {req.UserAgent}");

    using HttpListenerResponse resp = context.Response;

    byte[] buffer = Encoding.UTF8.GetBytes("Hello there!");

    resp.ContentType = "text/plain; charset=utf-8";
    resp.ContentLength64 = buffer.Length;

    await resp.OutputStream.WriteAsync(buffer);
}

The example creates a simple HTTP server.

using var listener = new HttpListener();
listener.Prefixes.Add("http://localhost:8001/");

listener.Start();

We create a listener which listens on port 8001.

HttpListenerContext context = await listener.GetContextAsync();
HttpListenerRequest req = context.Request;

With GetContextAsync, we wait for an incoming request. The context gives us access to the request and the response objects.

Console.WriteLine($"{req.HttpMethod} {req.Url}");
Console.WriteLine($"User agent: {req.UserAgent}");

The server logs the request to the terminal.

using HttpListenerResponse resp = context.Response;

byte[] buffer = Encoding.UTF8.GetBytes("Hello there!");

resp.ContentType = "text/plain; charset=utf-8";
resp.ContentLength64 = buffer.Length;

await resp.OutputStream.WriteAsync(buffer);

We set the content type and the length of the response and write the data to the output stream. Disposing the response sends it to the client and closes the output stream.

$ dotnet run
Listening on port 8001...
GET http://localhost:8001/greet?name=Jan
User agent: curl/7.81.0

We run the server.

$ curl localhost:8001/greet?name=Jan
Hello there!

We create a request with the curl tool. See the C# HttpListener article for more examples.

C# reverse DNS lookup

A reverse DNS lookup finds the host name for an IP address. We pass an IPAddress to the GetHostEntryAsync method.

Program.cs
using System.Net;
using System.Net.Sockets;

string[] addresses = ["8.8.8.8", "1.1.1.1", "185.15.59.224", "192.0.2.1"];

foreach (var a in addresses)
{
    try
    {
        IPHostEntry entry = await Dns.GetHostEntryAsync(IPAddress.Parse(a));
        Console.WriteLine($"{a,-15} {entry.HostName}");
    }
    catch (SocketException e)
    {
        Console.WriteLine($"{a,-15} {e.SocketErrorCode}");
    }
}

We look up the names of four addresses. The 192.0.2.1 address belongs to a range reserved for documentation and has no name; the lookup throws a SocketException.

$ dotnet run
8.8.8.8         dns.google
1.1.1.1         one.one.one.one
185.15.59.224   text-lb.esams.wikimedia.org
192.0.2.1       HostNotFound

C# TCP port check

To find out whether a service listens on a port, we try to connect to it. The following example checks several ports concurrently, each with a timeout.

Program.cs
using System.Net.Sockets;

var host = "example.com";
int[] ports = [22, 80, 443, 8080];

var tasks = ports.Select(port => CheckPortAsync(host, port, TimeSpan.FromSeconds(2)));
bool[] results = await Task.WhenAll(tasks);

for (int i = 0; i < ports.Length; i++)
{
    Console.WriteLine($"{host}:{ports[i]} {(results[i] ? "open" : "closed or filtered")}");
}

static async Task<bool> CheckPortAsync(string host, int port, TimeSpan timeout)
{
    using var client = new TcpClient();
    using var cts = new CancellationTokenSource(timeout);

    try
    {
        await client.ConnectAsync(host, port, cts.Token);
        return true;
    }
    catch (Exception e) when (e is SocketException or OperationCanceledException)
    {
        return false;
    }
}

A port is open if the connection succeeds.

using var cts = new CancellationTokenSource(timeout);
...
await client.ConnectAsync(host, port, cts.Token);

A firewall often drops connection attempts silently, and the operating system would wait a long time before giving up. The cancellation token source cancels the attempt after two seconds.

var tasks = ports.Select(port => CheckPortAsync(host, port, TimeSpan.FromSeconds(2)));
bool[] results = await Task.WhenAll(tasks);

The checks run concurrently, so the whole program takes about as long as the slowest check.

$ dotnet run
example.com:22 closed or filtered
example.com:80 open
example.com:443 open
example.com:8080 open

Check only hosts that you own or are allowed to test.

Source

Network programming in .NET

In this article we have created some basic networking programs in C#.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.