Networking in C# — Complete Guide
In this tutorial, you will learn about Networking in C#. We cover key concepts, practical examples, and best practices to help you master this topic.
Hook
Modern applications communicate over networks. C# provides comprehensive networking APIs ranging from high-level HttpClient for REST calls to low-level sockets for custom protocols. Whether you are building Microservices, real-time applications, or IoT solutions, mastering networking in .NET is essential.
Learning Path
graph LR A[Networking] --> B[HttpClient] A --> C[gRPC] A --> D[SignalR] B --> E[Best Practices] C --> F[Protocol Buffers] D --> G[Real-time] style A fill:#4a90d9,color:#fff style B fill:#4a90d9,color:#fff style C fill:#4a90d9,color:#fff style D fill:#4a90d9,color:#fff style E fill:#4a90d9,color:#fff style F fill:#4a90d9,color:#fff style G fill:#4a90d9,color:#fff
HttpClient
HttpClient is the primary class for making HTTP requests.
using System;
using System.Net.Http;
using System.Net.Http.Json;
public class ApiClient
{
private readonly HttpClient _httpClient;
public ApiClient()
{
// Always reuse HttpClient instances
_httpClient = new HttpClient
{
BaseAddress = new Uri("https://api.github.com"),
Timeout = TimeSpan.FromSeconds(30),
DefaultRequestHeaders =
{
{ "User-Agent", "MyApp/1.0" },
{ "Accept", "application/json" }
}
};
}
public async Task<T?> GetAsync<T>(string endpoint)
{
var response = await _httpClient.GetAsync(endpoint);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>();
}
public async Task<T?> PostAsync<T>(string endpoint, object data)
{
var response = await _httpClient.PostAsJsonAsync(endpoint, data);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>();
}
// Streaming large responses
public async Task ProcessStreamAsync(string endpoint)
{
var response = await _httpClient.GetAsync(endpoint,
HttpCompletionOption.ResponseHeadersRead);
using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
Console.WriteLine($"Received: {line}");
}
}
}
public record GitHubUser(string Login, int Id, string? Name);
HttpClientFactory
Use IHttpClientFactory for resilient HTTP clients in production.
// In Program.cs
builder.Services.AddHttpClient("GitHub", client =>
{
client.BaseAddress = new Uri("https://api.github.com");
client.DefaultRequestHeaders.UserAgent.ParseAdd("MyApp/1.0");
})
.AddTransientHttpErrorPolicy(policy =>
policy.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))))
.AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10)));
// In service
public class GitHubService
{
private readonly HttpClient _httpClient;
public GitHubService(IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient("GitHub");
}
public async Task<GitHubUser?> GetUserAsync(string username)
{
return await _httpClient.GetFromJsonAsync<GitHubUser>($"users/{username}");
}
}
gRPC
gRPC provides high-performance remote procedure calls using Protocol Buffers.
// greet.proto
syntax = "proto3";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
// Server
public class GreeterService : Greeter.GreeterBase
{
public override Task<HelloReply> SayHello(HelloRequest request,
ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = $"Hello {request.Name}"
});
}
}
// Client
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "World" });
Console.WriteLine(reply.Message);
SignalR
SignalR enables real-time web functionality.
// Hub
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
Console.WriteLine($"Client connected: {Context.ConnectionId}");
}
}
// Server setup
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSignalR();
var app = builder.Build();
app.MapHub<ChatHub>("/chat");
app.Run();
// Client (C#)
var connection = new HubConnectionBuilder()
.WithUrl("https://localhost:5001/chat")
.Build();
connection.On<string, string>("ReceiveMessage", (user, message) =>
{
Console.WriteLine($"{user}: {message}");
});
await connection.StartAsync();
await connection.InvokeAsync("SendMessage", "C# Client", "Hello SignalR!");
Raw Sockets
For custom protocols, use TcpClient and UdpClient.
// TCP Server
var listener = new TcpListener(IPAddress.Any, 5000);
listener.Start();
Console.WriteLine("Server started on port 5000");
while (true)
{
using var client = await listener.AcceptTcpClientAsync();
using var stream = client.GetStream();
using var reader = new StreamReader(stream);
using var writer = new StreamWriter(stream);
string? data = await reader.ReadLineAsync();
Console.WriteLine($"Received: {data}");
await writer.WriteLineAsync($"Echo: {data}");
}
// TCP Client
using var tcpClient = new TcpClient();
await tcpClient.ConnectAsync("localhost", 5000);
using var stream = tcpClient.GetStream();
using var writer = new StreamWriter(stream) { AutoFlush = true };
using var reader = new StreamReader(stream);
await writer.WriteLineAsync("Hello TCP Server");
string? response = await reader.ReadLineAsync();
Console.WriteLine(response);
DNS and IP
Resolve hostnames to IP addresses.
var host = await Dns.GetHostEntryAsync("google.com");
Console.WriteLine($"Host: {host.HostName}");
foreach (var ip in host.AddressList)
Console.WriteLine($" IP: {ip}");
var localIp = Dns.GetHostEntry(Dns.GetHostName())
.AddressList
.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork);
Console.WriteLine($"Local IP: {localIp}");
Common Mistakes
Not reusing HttpClient: Creating a new
HttpClientfor each request exhausts socket ports. UseIHttpClientFactoryor a single static instance.Ignoring cancellation: Always pass
CancellationTokento network calls to support graceful shutdown.Not handling transient failures: Network calls fail. Use Polly for retry policies with exponential backoff.
Blocking on async calls: Using
.Resultor.Wait()on Task causes thread pool starvation and potential deadlocks.Forgetting to dispose network resources: TcpClient, NetworkStream, and HttpResponseMessage should be disposed properly.
Practice Questions
Write a download manager that downloads multiple files concurrently using HttpClient and reports progress.
Implement a chat application using SignalR with group support for private messaging.
Create a gRPC service for a product catalog with CRUD operations.
Challenge: Build a port scanner using TcpClient that scans a range of IP addresses and ports asynchronously.
FAQ
Mini Project: Real-Time Stock Ticker
Build a SignalR hub that broadcasts simulated stock prices to connected clients.
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Concurrent;
public class Stock
{
public string Symbol { get; set; } = "";
public decimal Price { get; set; }
public decimal Change { get; set; }
}
public class StockTickerHub : Hub
{
private static readonly Random Rng = new();
private static readonly ConcurrentDictionary<string, decimal> Stocks = new()
{
["AAPL"] = 150.00m,
["GOOGL"] = 2800.00m,
["MSFT"] = 310.00m,
["AMZN"] = 3400.00m
};
public override async Task OnConnectedAsync()
{
await Clients.Caller.SendAsync("StocksInitial", GetStocks());
await base.OnConnectedAsync();
}
private List<Stock> GetStocks()
{
return Stocks.Select(kvp => new Stock
{
Symbol = kvp.Key,
Price = kvp.Value,
Change = 0
}).ToList();
}
public async Task StartTicker()
{
while (true)
{
await Task.Delay(1000);
foreach (var symbol in Stocks.Keys)
{
var change = (decimal)(Rng.NextDouble() - 0.5) * 5;
Stocks[symbol] = Math.Max(1, Stocks[symbol] + change);
}
var updates = Stocks.Select(kvp => new Stock
{
Symbol = kvp.Key,
Price = Math.Round(kvp.Value, 2),
Change = Math.Round(kvp.Value - 150, 2)
}).ToList();
await Clients.All.SendAsync("StocksUpdated", updates);
}
}
}
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSignalR();
var app = builder.Build();
app.MapHub<StockTickerHub>("/stockticker");
app.Run();
Networking in C# spans from simple HTTP calls to complex real-time systems. The .NET ecosystem provides battle-tested libraries for every networking scenario, enabling you to build Distributed Systems with confidence.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro