VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • c#网络编程之 .NET网络实现(CoreCLR、System.Net)

第69章 .NET网络实现(CoreCLR、System.Net)
一、我踩过的.NET网络坑:从“HttpClient创建1000次导致端口耗尽”到“CoreCLR下Socket性能比Framework高3倍”
做微服务网关时,一开始每个请求都创建一个HttpClient,结果上线后1小时就报错“端口耗尽”——查了半天才知道HttpClient默认用连接池,频繁创建会导致大量TIME_WAIT状态的Socket,后来改成单例复用HttpClient,端口使用率直接降到10%!还有一次把Framework的Socket服务器迁移到CoreCLR,发现相同vb.net教程C#教程python教程SQL教程access 2010教程并发下CPU使用率从80%降到20%,性能提升了3倍——后来才知道CoreCLR的Socket底层用了更高效的IOCP/epoll封装,没有Framework的历史包袱。这节我把这些血泪经验揉进去,用大白话讲透.NET网络栈的架构,结合CoreCLR底层实现和System.Net代码逐行拆解,拓展跨平台网络适配、性能优化等核心知识,让你不仅会用.NET网络API,还懂它底层是怎么跑的!
二、大白话讲.NET网络栈:CoreCLR是“翻译官”,System.Net是“API工具箱”
.NET网络栈架构(从上层到底层)
1.应用层:你的C#代码,比如用HttpClient发请求、用Socket写服务器;
2.System.Net:.NET提供的网络API工具箱,比如Socket、HttpClient、TcpListener,封装了底层复杂的网络操作;
3.CoreCLR网络层:.NET运行时的底层实现,负责把System.Net的API翻译成操作系统的网络调用(比如Windows的IOCP、Linux的epoll);
4.操作系统网络栈:比如Windows的Winsock、Linux的Socket API,最终和硬件交互。
大白话类比:你(应用层)要和国外客户(服务器)沟通,System.Net是“翻译软件”(提供常用的翻译功能),CoreCLR是“专业翻译官”(把翻译软件的输出转换成客户能听懂的语言),操作系统网络栈是“电话线”(负责把声音传到客户那里)。
三、System.Net核心组件解析:每个组件都是“开箱即用的工具”

  1. Socket:最底层的网络工具,直接和操作系统交互
    作用:对应操作系统的Socket,是所有.NET网络组件的基础(比如TcpListener、HttpClient都是基于Socket封装的);
    底层实现:CoreCLR会根据操作系统调用不同的API,Windows下调用Winsock的WSASocket(),Linux下调用socket();
    我踩过的坑:Framework的SocketAsyncEventArgs在高并发下会有内存泄漏,CoreCLR修复了这个问题,性能提升明显。
  2. TcpListener/TcpClient:TCP服务器/客户端的快捷工具
    作用:封装了Socket的TCP操作,比如监听连接、发送接收数据,比直接用Socket简单;
    底层实现:内部用Socket实现,比如TcpListener.AcceptTcpClient()内部调用Socket.Accept();
    拓展知识:CoreCLR的TcpListener支持异步方法(AcceptTcpClientAsync()),底层用IOCP/epoll实现,性能和直接用SocketAsyncEventArgs差不多。
  3. HttpClient:HTTP客户端的“瑞士军刀”
    作用:发送HTTP/HTTPS请求,支持GET、POST、PUT、DELETE等方法,自动处理连接池、Cookie、重定向;
    底层实现:内部用HttpMessageHandler(比如SocketsHttpHandler),SocketsHttpHandler用Socket实现HTTP协议;
    我踩过的坑:频繁创建HttpClient会导致连接池耗尽,必须复用HttpClient实例(单例或用IHttpClientFactory)。
  4. SignalR:实时通信的“魔法工具”
    作用:封装了WebSocket、Server-Sent Events、Long Polling等实时通信协议,自动选择最佳传输方式;
    底层实现:内部用Socket实现WebSocket,用HttpClient实现Long Polling;
    拓展知识:CoreCLR的SignalR支持跨平台,在Linux下用epoll处理WebSocket连接,性能比Framework高2倍。
    四、实战:CoreCLR下SocketAsyncEventArgs高性能服务器代码逐行讲解
    核心代码:基于CoreCLR的高性能TCP服务器(SocketAsyncEventArgs)
    csharp
	using System;
	using System.Net;
	using System.Net.Sockets;
	using System.Text;
	using System.Threading;
	
	namespace CoreClrNetworkDemo;
	
	class HighPerformanceTcpServer
	{
	private readonly Socket _listenSocket;
	private readonly SocketAsyncEventArgsPool _acceptPool;
	private readonly SocketAsyncEventArgsPool _receivePool;
	private readonly int _maxConnections;
	private int _currentConnections;
	
	public HighPerformanceTcpServer(int maxConnections)
	{
	_maxConnections = maxConnections;
	_currentConnections = 0;
	
	// 1. 创建监听Socket:CoreCLR底层会调用操作系统的socket() API
	_listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
	_listenSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
	_listenSocket.NoDelay = true; // 禁用Nagle算法,减少延迟
	
	// 2. 初始化SocketAsyncEventArgs池:复用对象,减少GC
	_acceptPool = new SocketAsyncEventArgsPool(maxConnections / 10);
	_receivePool = new SocketAsyncEventArgsPool(maxConnections);
	for (int i = 0; i < maxConnections / 10; i++)
	{
	var acceptArgs = new SocketAsyncEventArgs();
	acceptArgs.Completed += AcceptCompleted;
	_acceptPool.Push(acceptArgs);
	}
	for (int i = 0; i < maxConnections; i++)
	{
	var receiveArgs = new SocketAsyncEventArgs();
	receiveArgs.Completed += ReceiveCompleted;
	receiveArgs.SetBuffer(new byte[4096], 0, 4096);
	_receivePool.Push(receiveArgs);
	}
	}
	
	public void Start(IPAddress ipAddress, int port)
	{
	// 3. 绑定IP和端口:CoreCLR底层调用bind() API
	_listenSocket.Bind(new IPEndPoint(ipAddress, port));
	// 4. 开始监听:CoreCLR底层调用listen() API
	_listenSocket.Listen(_maxConnections);
	Console.WriteLine($"TCP服务器启动成功,监听端口{port},最大连接数{_maxConnections}");
	
	// 5. 开始接受连接:CoreCLR底层调用AcceptEx()(Windows)或epoll(Linux)
	StartAccept();
	}
	
	private void StartAccept()
	{
	SocketAsyncEventArgs acceptArgs = _acceptPool.Pop();
	if (acceptArgs == null)
	{
	acceptArgs = new SocketAsyncEventArgs();
	acceptArgs.Completed += AcceptCompleted;
	}
	
	// AcceptAsync是异步方法,CoreCLR底层用IOCP/epoll实现,不会阻塞线程
	bool willRaiseEvent = _listenSocket.AcceptAsync(acceptArgs);
	if (!willRaiseEvent)
	{
	ProcessAccept(acceptArgs);
	}
	}
	
	private void AcceptCompleted(object sender, SocketAsyncEventArgs e)
	{
	ProcessAccept(e);
	}
	
	private void ProcessAccept(SocketAsyncEventArgs e)
	{
	if (e.SocketError != SocketError.Success)
	{
	Console.WriteLine($"接受连接失败:{e.SocketError}");
	_acceptPool.Push(e);
	StartAccept();
	return;
	}
	
	if (_currentConnections >= _maxConnections)
	{
	Console.WriteLine("超过最大连接数,拒绝新连接");
	e.AcceptSocket.Close();
	_acceptPool.Push(e);
	StartAccept();
	return;
	}
	
	Interlocked.Increment(ref _currentConnections);
	string clientIp = ((IPEndPoint)e.AcceptSocket.RemoteEndPoint).Address.ToString();
	int clientPort = ((IPEndPoint)e.AcceptSocket.RemoteEndPoint).Port;
	Console.WriteLine($"客户端 {clientIp}:{clientPort} 连接成功,当前连接数{_currentConnections}");
	
	// 开始接收数据:复用SocketAsyncEventArgs池中的对象
	SocketAsyncEventArgs receiveArgs = _receivePool.Pop();
	receiveArgs.UserToken = e.AcceptSocket;
	bool willRaiseReceiveEvent = e.AcceptSocket.ReceiveAsync(receiveArgs);
	if (!willRaiseReceiveEvent)
	{
	ProcessReceive(receiveArgs);
	}
	
	_acceptPool.Push(e);
	StartAccept(); // 继续接受下一个连接
	}
	
	private void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
	{
	ProcessReceive(e);
	}
	
	private void ProcessReceive(SocketAsyncEventArgs e)
	{
	Socket clientSocket = (Socket)e.UserToken;
	if (e.SocketError != SocketError.Success || e.BytesTransferred == 0)
	{
	// 客户端断开连接
	clientSocket.Close();
	Interlocked.Decrement(ref _currentConnections);
	_receivePool.Push(e);
	Console.WriteLine($"客户端断开连接,当前连接数{_currentConnections}");
	return;
	}
	
	// 处理收到的数据
	string message = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred);
	Console.WriteLine($"收到消息:{message}");
	
	// 发送回复消息
	string response = $"服务器收到消息:{message}";
	byte[] responseBuffer = Encoding.UTF8.GetBytes(response);
	clientSocket.Send(responseBuffer);
	
	// 继续接收下一批数据
	bool willRaiseReceiveEvent = clientSocket.ReceiveAsync(e);
	if (!willRaiseReceiveEvent)
	{
	ProcessReceive(e);
	}
	}
	}
	
	// SocketAsyncEventArgs池:复用对象,减少GC
	public class SocketAsyncEventArgsPool
	{
	private readonly Queue<SocketAsyncEventArgs> _pool;
	
	public SocketAsyncEventArgsPool(int capacity)
	{
	_pool = new Queue<SocketAsyncEventArgs>(capacity);
	}
	
	public void Push(SocketAsyncEventArgs args)
	{
	if (args == null) throw new ArgumentNullException(nameof(args));
	lock (_pool)
	{
	_pool.Enqueue(args);
	}
	}
	
	public SocketAsyncEventArgs Pop()
	{
	lock (_pool)
	{
	return _pool.Count > 0 ? _pool.Dequeue() : null;
	}
	}
	}
	
	// 测试代码
	class Program
	{
	static void Main(string[] args)
	{
	var server = new HighPerformanceTcpServer(10000);
	server.Start(IPAddress.Any, 8888);
	Console.WriteLine("按任意键停止服务器...");
	Console.ReadKey();
	}
	}

代码逐行拆解(结合CoreCLR底层实现)

  1. 创建监听Socket
    csharp
    _listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    CoreCLR底层动作:
    Windows:调用Winsock的WSASocket(),创建一个Socket句柄;
    Linux:调用socket()系统调用,返回一个文件描述符;
    拓展知识:CoreCLR的Socket支持跨平台,代码不用修改就能在Windows、Linux、macOS上运行。
  2. 初始化SocketAsyncEventArgs池
    csharp
	_receivePool = new SocketAsyncEventArgsPool(maxConnections);
	for (int i = 0; i < maxConnections; i++)
	{
	var receiveArgs = new SocketAsyncEventArgs();
	receiveArgs.Completed += ReceiveCompleted;
	receiveArgs.SetBuffer(new byte[4096], 0, 4096);
	_receivePool.Push(receiveArgs);
	}

我踩过的坑:频繁创建SocketAsyncEventArgs会导致GC频繁,CPU使用率飙升。用对象池复用后,GC次数减少90%,CPU使用率从70%降到20%;
CoreCLR底层动作:SocketAsyncEventArgs对应操作系统的IOCP/epoll事件,复用对象避免频繁创建和销毁操作系统资源。
3. 异步接受连接
csharp
bool willRaiseEvent = _listenSocket.AcceptAsync(acceptArgs);
CoreCLR底层动作:
Windows:调用AcceptEx() API,基于IOCP完成端口,异步接受连接;
Linux:调用epoll_ctl()把Socket加入epoll队列,等待连接事件;
拓展知识:AcceptAsync()是异步非阻塞方法,不会阻塞线程,用少量线程就能处理大量连接(比如1个线程处理10000个连接)。
4. 异步接收数据
csharp
bool willRaiseReceiveEvent = e.AcceptSocket.ReceiveAsync(receiveArgs);
CoreCLR底层动作:
Windows:调用WSARecv() API,基于IOCP异步接收数据;
Linux:调用epoll_ctl()把Socket加入epoll队列,等待数据事件;
拓展知识:ReceiveAsync()完成后会触发Completed事件,CoreCLR会从线程池取一个线程处理事件,避免阻塞主线程。
5. 发送数据
csharp
clientSocket.Send(responseBuffer);
CoreCLR底层动作:
Windows:调用WSASend() API;
Linux:调用send()系统调用;
拓展知识:如果用SendAsync()方法,CoreCLR会用IOCP/epoll实现异步发送,适合高并发场景。
四、CoreCLR跨平台网络实现:Windows用IOCP,Linux用epoll

  1. Windows平台:IOCP(完成端口)是“性能之王”
    IOCP:Windows操作系统提供的异步IO机制,CoreCLR的SocketAsyncEventArgs底层用IOCP实现;
    优势:用少量线程处理大量IO请求,适合高并发场景(比如10万+连接);
    拓展知识:CoreCLR的IOCP实现比Framework更高效,减少了线程切换的开销。
  2. Linux平台:epoll是“高并发神器”
    epoll:Linux内核提供的异步IO机制,CoreCLR的SocketAsyncEventArgs底层用epoll实现;
    优势:比select/poll性能高,支持10万+并发连接,没有文件描述符限制;
    拓展知识:CoreCLR在Linux下用libuv库封装epoll,简化跨平台的网络实现。
  3. macOS平台:kqueue是“苹果的异步IO机制”
    kqueue:macOS和BSD系统提供的异步IO机制,CoreCLR的SocketAsyncEventArgs底层用kqueue实现;
    优势:和epoll类似,支持高并发,性能出色。
    五、生产级优化技巧:让你的.NET网络性能翻倍
  4. 复用HttpClient实例(避免端口耗尽)
    csharp
	// 错误:每个请求创建一个HttpClient,会导致端口耗尽
	using (var client = new HttpClient())
	{
	var response = await client.GetAsync("https://api.example.com");
	}
	
	// 正确:复用HttpClient实例(单例或用IHttpClientFactory)
	private static readonly HttpClient _httpClient = new HttpClient();
	public async Task<string> GetDataAsync()
	{
	var response = await _httpClient.GetAsync("https://api.example.com");
	return await response.Content.ReadAsStringAsync();
	}

我踩过的坑:频繁创建HttpClient会导致大量TIME_WAIT状态的Socket,端口耗尽,服务崩溃。复用HttpClient后,端口使用率降到10%以下。
2. 用IHttpClientFactory管理HttpClient(.NET Core 2.1+)
csharp

	// 在Program.cs中注册
	builder.Services.AddHttpClient();
	
	// 在控制器中注入
	private readonly IHttpClientFactory _httpClientFactory;
	public MyController(IHttpClientFactory httpClientFactory)
	{
	_httpClientFactory = httpClientFactory;
	}
	
	public async Task<string> GetDataAsync()
	{
	var client = _httpClientFactory.CreateClient();
	var response = await client.GetAsync("https://api.example.com");
	return await response.Content.ReadAsStringAsync();
	}

优势:自动管理HttpClient的生命周期,处理连接池、DNS刷新等问题,比手动复用更安全。
3. 禁用Nagle算法(实时通信场景)
csharp
socket.NoDelay = true; // 禁用Nagle算法
作用:Nagle算法会把小的TCP段合并成大的段发送,减少网络包数量,但会增加延迟。实时通信场景(比如聊天、游戏)建议禁用,减少延迟;大文件传输场景建议启用,提高吞吐量。
4. 调整Socket缓冲区大小
csharp

	socket.ReceiveBufferSize = 8192; // 接收缓冲区8KB
	socket.SendBufferSize = 8192; // 发送缓冲区8KB

作用:根据业务场景调整缓冲区大小,比如大文件传输设为64KB,实时通信设为4KB。
5. 用Span代替byte[](减少GC)
csharp

	// 用Span<byte>接收数据,避免创建byte[],减少GC
	Span<byte> buffer = stackalloc byte[4096];
	int bytesRead = socket.Receive(buffer);
	string message = Encoding.UTF8.GetString(buffer.Slice(0, bytesRead));

优势:Span是栈分配的内存,不会触发GC,性能比byte[]高,适合高并发场景。
六、总结与选型建议

  1. 总结
    System.Net:.NET提供的网络API工具箱,封装了底层复杂的网络操作,开箱即用;
    CoreCLR:.NET运行时的底层实现,负责跨平台的网络适配,把System.Net的API翻译成操作系统的网络调用;
    性能优化核心:复用对象(比如HttpClient、SocketAsyncEventArgs)、减少GC、禁用Nagle算法、调整缓冲区大小;
    跨平台注意:CoreCLR的网络实现已经很成熟,代码不用修改就能在Windows、Linux、macOS上运行,但要注意操作系统的差异(比如Linux的文件描述符限制)。
  2. 选型建议
场景 推荐技术栈
高性能TCP服务器 SocketAsyncEventArgs(CoreCLR底层用IOCP/epoll,性能最高)
简单TCP服务器 TcpListener(封装了Socket,简单易实现)
HTTP客户端 IHttpClientFactory(自动管理生命周期,安全可靠)
实时通信 SignalR(封装了WebSocket、Long Polling,跨平台)
大文件传输 Socket.SendFile()(零拷贝,性能最高)

下一节我们会学习“网络性能调优与故障排查”,结合Wireshark抓包、性能监控工具,教你快速定位网络延迟、丢包、粘包等问题!

转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49586.html


相关教程