C# 高级教程 - 14 网络编程

网络编程是后端开发的核心能力。C# 提供了从高层级的 HttpClient 到低层级的 Socket 的完整网络编程栈。本文将深入探讨 HTTP 请求调优、gRPC 通信、Socket 编程、以及 ASP.NET Core 中的网络性能优化。

HttpClient 深入调优

正确使用 HttpClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 错误:每次创建新实例(Socket 耗尽!)
public class BadService
{
public async Task CallApi()
{
using var client = new HttpClient(); // 每次创建新连接
return await client.GetStringAsync("https://api.example.com");
}
}

// 正确:IHttpClientFactory 管理连接池
public class GoodService
{
private readonly HttpClient _httpClient;

public GoodService(IHttpClientFactory factory)
{
_httpClient = factory.CreateClient("Api");
}

public async Task<string> CallApi()
{
return await _httpClient.GetStringAsync("/data");
}
}

// 配置客户端
services.AddHttpClient("Api", client =>
{
client.BaseAddress = new Uri("https://api.example.com");
client.Timeout = TimeSpan.FromSeconds(30);
client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
MaxConnectionsPerServer = 10,
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
EnableMultipleHttp2Connections = true,
});

高级请求技术

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 带重试策略(使用 Polly)
using Polly;

var retryPolicy = HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromMilliseconds(100 * Math.Pow(2, retryAttempt)));

var response = await retryPolicy.ExecuteAsync(() =>
_httpClient.GetAsync("/unreliable-endpoint"));

// 流式响应(避免大响应体内存占用)
using var response = await _httpClient.GetAsync(
"/large-file", HttpCompletionOption.ResponseHeadersRead);
using var stream = await response.Content.ReadAsStreamAsync();
await stream.CopyToAsync(File.OpenWrite("large.bin"));

// 取消请求
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
var result = await _httpClient.GetAsync("/slow-api", cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("请求超时");
}

gRPC 通信

定义服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// proto/order.proto
service OrderService {
rpc GetOrder (OrderRequest) returns (OrderResponse);
rpc ListOrders (ListRequest) returns (stream OrderResponse);
rpc PlaceOrder (stream OrderRequest) returns (OrderSummary);
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}

message OrderRequest {
int32 id = 1;
}

message OrderResponse {
int32 id = 1;
string customer = 2;
double amount = 3;
}

服务端实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class OrderServiceImpl : OrderService.OrderServiceBase
{
// 一元调用
public override Task<OrderResponse> GetOrder(
OrderRequest request, ServerCallContext context)
{
return Task.FromResult(new OrderResponse
{
Id = request.Id,
Customer = "张三",
Amount = 99.9
});
}

// 服务端流
public override async Task ListOrders(
ListRequest request,
IServerStreamWriter<OrderResponse> responseStream,
ServerCallContext context)
{
foreach (var order in GetOrders())
{
await responseStream.WriteAsync(order);
await Task.Delay(100);
}
}

// 双向流
public override async Task Chat(
IAsyncStreamReader<ChatMessage> requestStream,
IServerStreamWriter<ChatMessage> responseStream,
ServerCallContext context)
{
await foreach (var msg in requestStream.ReadAllAsync())
{
await responseStream.WriteAsync(new ChatMessage
{
Content = $"收到: {msg.Content}"
});
}
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new OrderService.OrderServiceClient(channel);

// 一元调用
var response = await client.GetOrderAsync(new OrderRequest { Id = 1 });

// 服务端流
using var listCall = client.ListOrders(new ListRequest());
await foreach (var order in listCall.ResponseStream.ReadAllAsync())
{
Console.WriteLine(order.Customer);
}

// 双向流
using var chatCall = client.Chat();
_ = Task.Run(async () =>
{
await foreach (var reply in chatCall.ResponseStream.ReadAllAsync())
Console.WriteLine(reply.Content);
});

await chatCall.RequestStream.WriteAsync(
new ChatMessage { Content = "你好" });
await chatCall.RequestStream.CompleteAsync();

Socket 编程

TCP 客户端/服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// TCP 服务器
var listener = new TcpListener(IPAddress.Any, 8888);
listener.Start();

_ = Task.Run(async () =>
{
while (true)
{
var client = await listener.AcceptTcpClientAsync();
_ = HandleClientAsync(client);
}
});

async Task HandleClientAsync(TcpClient tcpClient)
{
using (tcpClient)
using (var stream = tcpClient.GetStream())
{
byte[] buffer = new byte[1024];
int bytesRead = await stream.ReadAsync(buffer);
string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);

byte[] response = Encoding.UTF8.GetBytes($"已收到: {message}");
await stream.WriteAsync(response);
}
}

// TCP 客户端
using var client = new TcpClient();
await client.ConnectAsync("localhost", 8888);
using var stream = client.GetStream();

byte[] data = Encoding.UTF8.GetBytes("你好");
await stream.WriteAsync(data);

byte[] buffer = new byte[1024];
int read = await stream.ReadAsync(buffer);
Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, read));

UDP 广播

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using var udpClient = new UdpClient();

// 发送广播
byte[] message = Encoding.UTF8.GetBytes("发现服务");
await udpClient.SendAsync(message, message.Length, "255.255.255.255", 9999);

// 接收
using var listener = new UdpClient(9999);
while (true)
{
var result = await listener.ReceiveAsync();
string text = Encoding.UTF8.GetString(result.Buffer);
Console.WriteLine($"来自 {result.RemoteEndPoint}: {text}");
}

Socket 多路复用(Select/Poll)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用 SocketAsyncEventArgs 实现高并发
public class AsyncSocketServer
{
private readonly Socket _listenSocket;
private readonly BufferManager _bufferManager;
private readonly SemaphoreSlim _maxClient;

public async Task StartAsync(int port, int maxClients = 1000)
{
_listenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
_listenSocket.Bind(new IPEndPoint(IPAddress.Any, port));
_listenSocket.Listen(maxClients);

while (true)
{
var socket = await _listenSocket.AcceptAsync();
_ = ProcessClientAsync(socket);
}
}
}

ASP.NET Core 网络性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 1. 使用 Kestrel 作为边缘服务器
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxConcurrentConnections = 10_000;
options.Limits.MaxRequestBodySize = 10 * 1024 * 1024;
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
});

// 2. 响应压缩
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
});

// 3. HTTP/2 多路复用
builder.WebHost.ConfigureKestrel(options =>
{
options.Listen(IPAddress.Any, 5001, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
});
});

// 4. 连接池
builder.Services.AddSingleton<ConnectionMultiplexer>(
_ => ConnectionMultiplexer.Connect("localhost:6379"));

// 5. 管道式中间件 —— 避免阻塞
app.Use(async (context, next) =>
{
// 异步操作不要阻塞
await next(); // ✅
// next().Wait(); ❌
});

网络编程常见陷阱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 陷阱 1: DNS 解析缓存
var handler = new HttpClientHandler();
// 默认 DNS 缓存无限期 —— 容器环境中可能有问题
// 使用 SocketsHttpHandler.PooledConnectionLifetime 控制连接生命周期

// 陷阱 2: 未处理的连接异常
try
{
await client.GetAsync("https://example.com");
}
catch (HttpRequestException ex)
{
// 处理网络错误
}

// 陷阱 3: 线程饥饿 —— 同步上下文死锁
public async Task<string> BadPattern()
{
// ASP.NET Core 无同步上下文,但在 WinForms/WPF 中:
var result = await client.GetStringAsync("url").ConfigureAwait(false);
return result;
}

总结:现代 C# 网络编程主要围绕 HttpClient(HTTP/REST)、gRPC(高性能 RPC)和原生 Socket(自定义协议)。使用 IHttpClientFactory 管理连接池、利用 HTTP/2 多路复用、引入重试策略和连接生命周期管理,是构建健壮网络服务的基础。gRPC 基于 HTTP/2 的双向流和 Protobuf 序列化,使其成为微服务间通信的首选。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: