CSharpStory_Network.html
copyright © James Fawcett
Revised: 05/15/2026
14.0 Prologue
.NET's networking support lives primarily in the System.Net and
System.Net.Sockets namespaces. This chapter covers the main APIs at each
layer of the networking stack: high-level HTTP with HttpClient, reliable
TCP streams with TcpClient / TcpListener, connectionless UDP
with UdpClient, raw socket programming with Socket, full-duplex
messaging with WebSocket, and the supporting address types
IPAddress, IPEndPoint, and Dns.
14.1 HttpClient
HttpClient is the primary class for sending HTTP requests and receiving
responses. It is designed to be instantiated once and reused for the lifetime of the
application (or injected as a singleton via IHttpClientFactory).
using var client = new HttpClient();
client.BaseAddress = new Uri("https://api.example.com/");
client.DefaultRequestHeaders.Add("Accept", "application/json");
// GET
string json = await client.GetStringAsync("items/42");
// POST
var payload = new StringContent(
"""{"name":"widget"}""", Encoding.UTF8, "application/json");
HttpResponseMessage resp = await client.PostAsync("items", payload);
resp.EnsureSuccessStatusCode();
Key members:
- GetAsync / PostAsync / PutAsync / DeleteAsync — verb-specific helpers
- SendAsync(HttpRequestMessage) — full control over method, headers, content
- GetStreamAsync — streams response body without buffering
- GetFromJsonAsync<T> / PostAsJsonAsync (System.Net.Http.Json) — JSON helpers that avoid manual serialization
// streaming download
await using Stream stream = await client.GetStreamAsync("large-file.bin");
await stream.CopyToAsync(File.OpenWrite("output.bin"));
// JSON helper
var item = await client.GetFromJsonAsync<Item>("items/42");
Use IHttpClientFactory in ASP.NET Core to manage HttpClient
lifetimes and avoid socket exhaustion from excessive construction and disposal.
14.2 TcpClient and TcpListener
TcpClient and TcpListener wrap Socket for TCP
streams. They expose a NetworkStream that supports both synchronous and
asynchronous reads and writes.
// --- server ---
var listener = new TcpListener(IPAddress.Any, 5000);
listener.Start();
TcpClient client = await listener.AcceptTcpClientAsync();
NetworkStream ns = client.GetStream();
using var reader = new StreamReader(ns);
using var writer = new StreamWriter(ns) { AutoFlush = true };
string? line = await reader.ReadLineAsync();
await writer.WriteLineAsync("Echo: " + line);
// --- client ---
using var tcp = new TcpClient();
await tcp.ConnectAsync("localhost", 5000);
NetworkStream ns = tcp.GetStream();
using var writer = new StreamWriter(ns) { AutoFlush = true };
using var reader = new StreamReader(ns);
await writer.WriteLineAsync("hello");
Console.WriteLine(await reader.ReadLineAsync());
NetworkStream supports ReadAsync / WriteAsync
with Memory<byte> overloads, making it easy to integrate with
Span<T>-based parsing pipelines.
Wrap the stream in SslStream (providing an
X509Certificate2) for TLS. Call
AuthenticateAsServerAsync / AuthenticateAsClientAsync
before reading or writing.
14.3 UdpClient
UdpClient sends and receives datagrams. Unlike TCP it is connectionless
— each Send / Receive is independent. Useful for
low-latency telemetry, DNS queries, and multicast.
// sender
using var sender = new UdpClient();
byte[] data = Encoding.UTF8.GetBytes("ping");
await sender.SendAsync(data, data.Length, "localhost", 9000);
// receiver
using var recv = new UdpClient(9000);
UdpReceiveResult result = await recv.ReceiveAsync();
string msg = Encoding.UTF8.GetString(result.Buffer);
Console.WriteLine($"From {result.RemoteEndPoint}: {msg}");
For multicast, call JoinMulticastGroup(IPAddress) and bind to the
multicast port. Use DropMulticastGroup to leave.
14.4 Socket
Socket in System.Net.Sockets provides direct access to the
OS socket API. TcpClient and UdpClient are thin wrappers
over it. Use Socket directly when you need control over socket options,
raw protocols, or high-performance I/O.
// async TCP client via Socket
var ep = new IPEndPoint(IPAddress.Loopback, 5000);
using var sock = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await sock.ConnectAsync(ep);
byte[] buf = Encoding.UTF8.GetBytes("hello\n");
await sock.SendAsync(buf, SocketFlags.None);
byte[] recv = new byte[256];
int n = await sock.ReceiveAsync(recv, SocketFlags.None);
Console.WriteLine(Encoding.UTF8.GetString(recv, 0, n));
Useful Socket members:
- AcceptAsync — non-blocking accept on server sockets
- SetSocketOption — control TCP_NODELAY, SO_REUSEADDR, timeouts, etc.
- SendToAsync / ReceiveFromAsync — UDP-style with explicit endpoint
- Poll — check readability / writeability / error state without blocking
For very high throughput, use SocketAsyncEventArgs to avoid per-operation
allocations, or consider the System.Net.Http.SocketsHttpHandler pipeline
that HttpClient uses internally.
14.5 WebSocket
ClientWebSocket (client side) and WebSocket (server side,
obtained from HttpContext.WebSockets.AcceptWebSocketAsync() in ASP.NET
Core) provide full-duplex message framing over a single HTTP upgrade connection.
using var ws = new ClientWebSocket();
await ws.ConnectAsync(new Uri("wss://echo.websocket.org"), CancellationToken.None);
// send
byte[] msg = Encoding.UTF8.GetBytes("hello");
await ws.SendAsync(msg, WebSocketMessageType.Text, endOfMessage: true,
CancellationToken.None);
// receive
byte[] buf = new byte[1024];
WebSocketReceiveResult res =
await ws.ReceiveAsync(buf, CancellationToken.None);
Console.WriteLine(Encoding.UTF8.GetString(buf, 0, res.Count));
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "",
CancellationToken.None);
WebSocketMessageType is either Text or Binary.
Messages larger than the receive buffer arrive across multiple calls — loop until
result.EndOfMessage is true.
14.6 IPAddress, IPEndPoint, and Dns
These supporting types handle address representation and resolution:
// IPAddress — parse or use constants
IPAddress loopback = IPAddress.Loopback; // 127.0.0.1
IPAddress any = IPAddress.Any; // 0.0.0.0
IPAddress ip6 = IPAddress.IPv6Loopback;
IPAddress parsed = IPAddress.Parse("192.168.1.10");
bool ok = IPAddress.TryParse("10.0.0.1", out IPAddress? addr);
// IPEndPoint — address + port
var ep = new IPEndPoint(IPAddress.Loopback, 8080);
Console.WriteLine(ep); // 127.0.0.1:8080
// Dns — async hostname resolution
IPHostEntry entry = await Dns.GetHostEntryAsync("example.com");
foreach (IPAddress a in entry.AddressList)
Console.WriteLine(a);
string host = await Dns.GetHostNameAsync(); // local machine name
IPAddress.AddressFamily distinguishes IPv4
(AddressFamily.InterNetwork) from IPv6
(AddressFamily.InterNetworkV6). Pass the right family to
Socket or TcpClient constructors.
14.7 Epilogue
.NET's networking stack scales from single HTTP calls with HttpClient
to raw socket I/O with Socket. All APIs have async overloads that integrate
cleanly with async / await and CancellationToken.
For production services, prefer IHttpClientFactory for HTTP and
System.IO.Pipelines for high-throughput socket work.
14.8 References
HttpClient — Microsoft docs
TcpClient — Microsoft docs
UdpClient — Microsoft docs
Socket — Microsoft docs
ClientWebSocket — Microsoft docs
Dns — Microsoft docs