VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • C#网络编程这异步TCP/UDP编程(TcpClientAsync、UdpClientAsync)

异步TCP/UDP编程:TcpClientAsync、UdpClientAsync深度解析
18.8.1 引言:网络编程的异步需求
在传统同步网络编程中,线程会被阻塞在Accept()、Read()、Write()等操作上,导致系统无法处理大量并发连接(如1000个客户端连接需要1000个线程,内存和CPU开销巨大)。异步网络编程vb.net教程C#教程python教程SQL教程access 2010教程通过非阻塞IO和回调/状态机机制,让单个线程可以处理数千甚至数万个并发连接,是构建高性能服务器的核心技术。
.NET提供了两套异步网络编程API:
1.基于TcpClient/UdpClient的异步方法(如ConnectAsync、ReadAsync);
2.基于Socket的底层异步API(如BeginAccept/EndAccept、ReceiveAsync)。
本章重点讲解前者(更易用,适合大多数场景),同时拓展底层原理和最佳实践。
18.8.2 异步TCP编程:TcpClient与TcpListener
TCP是面向连接、可靠的传输协议,适用于数据完整性要求高的场景(如文件传输、聊天系统)。.NET中TcpClient用于客户端,TcpListener用于服务器端,两者均提供异步方法。

  1. 异步TCP客户端:TcpClientAsync
    核心异步方法
    ConnectAsync(string host, int port):异步连接服务器;
    GetStreamAsync():异步获取网络流(用于读写数据);
    NetworkStream.ReadAsync(byte[] buffer, int offset, int count):异步读取数据;
    NetworkStream.WriteAsync(byte[] buffer, int offset, int count):异步写入数据。
    代码示例:异步TCP客户端(逐行讲解)
    csharp
	using System; 
	using System.Net.Sockets; 
	using System.Text; 
	using System.Threading.Tasks; 
	
	class AsyncTcpClient 
	{ 
	// 服务器地址和端口 
	private const string ServerIp = "127.0.0.1"; 
	private const int ServerPort = 8888; 
	
	static async Task Main() 
	{ 
	// 1. 创建TcpClient实例,using语句确保自动释放资源(关闭连接) 
	using TcpClient client = new TcpClient(); 
	Console.WriteLine("正在连接服务器..."); 
	
	try 
	{ 
	// 2. 异步连接服务器:线程不阻塞,连接成功后继续执行 
	await client.ConnectAsync(ServerIp, ServerPort); 
	Console.WriteLine("连接服务器成功!"); 
	
	// 3. 获取网络流:TcpClient的读写通道,支持异步操作 
	NetworkStream stream = client.GetStream(); 
	
	// 4. 异步发送数据:将字符串转为字节数组,通过网络发送到服务器 
	string message = "Hello, Async TCP Server!"; 
	byte[] sendBuffer = Encoding.UTF8.GetBytes(message); 
	await stream.WriteAsync(sendBuffer, 0, sendBuffer.Length); 
	Console.WriteLine($"发送数据:{message}"); 
	
	// 5. 异步接收服务器响应:读取服务器返回的字节数据 
	byte[] receiveBuffer = new byte[1024]; 
	int bytesRead = await stream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length); 
	string response = Encoding.UTF8.GetString(receiveBuffer, 0, bytesRead); 
	Console.WriteLine($"收到服务器响应:{response}"); 
	} 
	catch (SocketException ex) 
	{ 
	// 6. 捕获网络异常:如服务器未启动、连接超时等 
	Console.WriteLine($"连接失败:{ex.Message}"); 
	} 
	// 7. using语句自动调用client.Dispose(),关闭Socket连接 
	} 
	}

逐行讲解:
1.using TcpClient client = new TcpClient();:using语句确保TcpClient在使用后自动释放资源(关闭Socket连接),避免手动调用Close();
2.await client.ConnectAsync(ServerIp, ServerPort);:异步连接服务器,线程不会阻塞,若连接成功则继续执行,失败则抛出SocketException(如服务器未启动、端口被占用);
3.NetworkStream stream = client.GetStream();:NetworkStream是TcpClient的读写通道,封装了Socket的底层操作,支持异步读写;
4.await stream.WriteAsync(sendBuffer, 0, sendBuffer.Length);:异步发送数据,数据通过网络发送到服务器,线程不阻塞,可同时处理其他任务;
5.int bytesRead = await stream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length);:异步接收服务器响应,bytesRead表示实际读取的字节数(TCP是流协议,需处理粘包问题);
6.Encoding.UTF8.GetString(receiveBuffer, 0, bytesRead);:将字节数组转换为字符串,注意只转换实际读取的字节数(避免末尾的空字符);
7.catch (SocketException ex):捕获网络异常,常见错误码如10061(服务器拒绝连接)、10054(客户端强制关闭连接)。
2. 异步TCP服务器:TcpListenerAsync
核心异步方法
Start():启动服务器监听;
AcceptTcpClientAsync():异步接受客户端连接;
Stop():停止服务器监听。
代码示例:异步TCP服务器(支持多客户端)
csharp

	using System; 
	using System.Net; 
	using System.Net.Sockets; 
	using System.Text; 
	using System.Threading.Tasks; 
	
	class AsyncTcpServer 
	{ 
	private const int Port = 8888; 
	
	static async Task Main() 
	{ 
	// 1. 创建TcpListener实例,绑定到所有网卡的指定端口 
	TcpListener listener = new TcpListener(IPAddress.Any, Port); 
	listener.Start(); 
	Console.WriteLine($"服务器启动,监听端口:{Port}"); 
	
	try 
	{ 
	// 2. 循环接受客户端连接:异步非阻塞,每个连接启动独立任务处理 
	while (true) 
	{ 
	// 异步等待客户端连接,连接成功后返回TcpClient实例 
	TcpClient client = await listener.AcceptTcpClientAsync(); 
	Console.WriteLine($"新客户端连接:{client.Client.RemoteEndPoint}"); 
	
	// 3. 启动异步任务处理客户端通信:避免阻塞主线程的Accept操作 
	_ = HandleClientAsync(client); // 下划线表示忽略返回的Task(无需等待) 
	} 
	} 
	finally 
	{ 
	// 4. 停止服务器监听:实际中需处理优雅关闭(通知所有客户端断开) 
	listener.Stop(); 
	} 
	} 
	
	// 处理单个客户端的通信逻辑(独立异步任务) 
	private static async Task HandleClientAsync(TcpClient client) 
	{ 
	// using语句确保客户端连接自动释放(断开时关闭Socket) 
	using (client) 
	using (NetworkStream stream = client.GetStream()) 
	{ 
	byte[] buffer = new byte[1024]; 
	int bytesRead; 
	
	// 循环接收客户端数据:直到客户端断开连接(bytesRead=0) 
	while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) 
	{ 
	string message = Encoding.UTF8.GetString(buffer, 0, bytesRead); 
	Console.WriteLine($"收到客户端[{client.Client.RemoteEndPoint}]消息:{message}"); 
	
	// 异步发送响应给客户端:将消息原样返回(回声服务器) 
	byte[] responseBuffer = Encoding.UTF8.GetBytes($"服务器已收到:{message}"); 
	await stream.WriteAsync(responseBuffer, 0, responseBuffer.Length); 
	} 
	
	Console.WriteLine($"客户端[{client.Client.RemoteEndPoint}]断开连接"); 
	} 
	} 
	}

关键特性讲解:
1.多客户端支持:通过while (true)循环调用AcceptTcpClientAsync(),每个客户端连接启动一个独立的HandleClientAsync任务,实现并发处理;
2.资源管理:using (client)和using (NetworkStream stream)确保客户端断开连接时自动释放Socket和流资源;
3.TCP粘包处理:上述代码未处理粘包问题(若客户端连续发送数据,服务器可能将多条数据合并为一次读取),实际生产中需通过消息头+消息体的方式解决(如先读取4字节表示消息长度,再读取对应长度的消息体)。
粘包问题解决方案示例
csharp

	// 改进版:读取完整消息(假设消息头为4字节整数,表示消息长度) 
	private static async Task<byte[]> ReadFullMessageAsync(NetworkStream stream) 
	{ 
	// 1. 读取消息头(4字节,BigEndian格式) 
	byte[] headerBuffer = new byte[4]; 
	int headerBytesRead = await stream.ReadAsync(headerBuffer, 0, 4); 
	if (headerBytesRead != 4) 
	{ 
	throw new InvalidOperationException("客户端断开连接或消息格式错误"); 
	} 
	int messageLength = BitConverter.ToInt32(headerBuffer, 0); 
	if (BitConverter.IsLittleEndian) 
	{ 
	// 转换为网络字节序(BigEndian):确保跨平台兼容性 
	messageLength = IPAddress.NetworkToHostOrder(messageLength); 
	} 
	
	// 2. 读取消息体(指定长度):处理TCP流的分段问题 
	byte[] messageBuffer = new byte[messageLength]; 
	int totalBytesRead = 0; 
	while (totalBytesRead < messageLength) 
	{ 
	int bytesRead = await stream.ReadAsync(messageBuffer, totalBytesRead, messageLength - totalBytesRead); 
	if (bytesRead == 0) 
	{ 
	throw new InvalidOperationException("客户端断开连接"); 
	} 
	totalBytesRead += bytesRead; 
	} 
	return messageBuffer; 
	}

18.8.3 异步UDP编程:UdpClientAsync
UDP是无连接、不可靠的传输协议,适用于对延迟敏感、可容忍少量丢包的场景(如视频通话、实时游戏)。.NET中UdpClient提供了异步发送和接收方法。

  1. 核心异步方法
    ConnectAsync(string host, int port):异步连接到指定服务器(可选,用于简化后续发送);
    SendAsync(byte[] datagram, int bytes, string host, int port):异步发送数据报到指定地址;
    ReceiveAsync():异步接收数据报,返回UdpReceiveResult(包含数据和发送方地址)。
  2. 代码示例:异步UDP客户端与服务器
    异步UDP客户端
    csharp
	using System; 
	using System.Net; 
	using System.Net.Sockets; 
	using System.Text; 
	using System.Threading.Tasks; 
	
	class AsyncUdpClient 
	{ 
	private const string ServerIp = "127.0.0.1"; 
	private const int ServerPort = 9999; 
	
	static async Task Main() 
	{ 
	using UdpClient client = new UdpClient(); 
	Console.WriteLine("UDP客户端启动,输入消息发送到服务器(输入exit退出)"); 
	
	while (true) 
	{ 
	Console.Write("请输入消息:"); 
	string message = Console.ReadLine(); 
	if (message.Equals("exit", StringComparison.OrdinalIgnoreCase)) 
	{ 
	break; 
	} 
	
	// 1. 异步发送数据报到服务器:无需提前连接 
	byte[] sendBuffer = Encoding.UTF8.GetBytes(message); 
	await client.SendAsync(sendBuffer, sendBuffer.Length, ServerIp, ServerPort); 
	Console.WriteLine($"发送数据:{message}"); 
	
	// 2. 异步接收服务器响应:包含发送方地址 
	UdpReceiveResult result = await client.ReceiveAsync(); 
	string response = Encoding.UTF8.GetString(result.Buffer); 
	Console.WriteLine($"收到服务器[{result.RemoteEndPoint}]响应:{response}"); 
	} 
	} 
	}

异步UDP服务器
csharp

	using System; 
	using System.Net; 
	using System.Net.Sockets; 
	using System.Text; 
	using System.Threading.Tasks; 
	
	class AsyncUdpServer 
	{ 
	private const int Port = 9999; 
	
	static async Task Main() 
	{ 
	// 绑定到指定端口,监听所有网卡 
	using UdpClient server = new UdpClient(Port); 
	Console.WriteLine($"UDP服务器启动,监听端口:{Port}"); 
	
	try 
	{ 
	while (true) 
	{ 
	// 1. 异步接收数据报:包含发送方的IP和端口 
	UdpReceiveResult result = await server.ReceiveAsync(); 
	string message = Encoding.UTF8.GetString(result.Buffer); 
	Console.WriteLine($"收到客户端[{result.RemoteEndPoint}]消息:{message}"); 
	
	// 2. 异步发送响应给客户端:直接回复到发送方地址 
	string response = $"服务器已收到:{message}"; 
	byte[] responseBuffer = Encoding.UTF8.GetBytes(response); 
	await server.SendAsync(responseBuffer, responseBuffer.Length, result.RemoteEndPoint); 
	} 
	} 
	finally 
	{ 
	server.Close(); 
	} 
	} 
	}

UDP与TCP的核心区别:
1.无连接:UDP客户端无需提前连接服务器,直接发送数据报;
2.不可靠:UDP不保证数据到达顺序和完整性,可能丢包,需应用层实现重传机制;
3.数据报边界:UDP保留数据报边界,服务器每次ReceiveAsync()读取一个完整的数据报(无需处理粘包问题);
4.广播/多播:UDP支持广播(发送到同一网络的所有设备)和多播(发送到指定组的设备),而TCP不支持。
18.8.4 底层原理:IOCP(输入/输出完成端口)
.NET异步网络编程的底层依赖于Windows的IOCP(Input/Output Completion Port)机制(Linux/macOS下依赖epoll/kqueue),这是一种高效的异步IO模型,核心思想是:
1.线程池与IO操作分离:当发起异步IO操作(如ReadAsync)时,操作系统负责处理IO,线程返回继续执行其他任务;
2.IO完成通知:当IO操作完成时,操作系统将结果放入完成端口,线程池中的工作线程从完成端口获取结果并处理后续逻辑;
3.高效并发:单个IOCP端口可以处理数千甚至数万个并发IO操作,线程数量远小于并发连接数(通常为CPU核心数×2)。
IOCP的优势:
避免线程阻塞等待IO,提高线程利用率;
减少线程上下文切换开销,适合高并发场景;
操作系统级别的异步支持,性能优于用户态的异步模型(如轮询)。
18.8.5 性能优化与最佳实践

  1. 避免同步阻塞异步代码
    禁止在异步网络代码中使用Thread.Sleep()、Task.Wait()等同步阻塞方法,应使用Task.Delay()或异步等待;
    服务器端处理客户端连接时,必须使用独立的异步任务(如HandleClientAsync),避免阻塞AcceptTcpClientAsync()。
  2. 资源复用与池化
    Socket池:对于频繁创建和销毁的客户端连接,可使用Socket池复用Socket资源(减少Socket创建和关闭的开销);
    缓冲区池:对于大量读写操作,可使用ArrayPool复用字节数组(减少GC压力):
    csharp
	// 使用ArrayPool<byte>复用缓冲区:减少内存分配和GC开销 
	byte[] buffer = ArrayPool<byte>.Shared.Rent(1024); 
	try 
	{ 
	int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); 
	// 处理数据 
	} 
	finally 
	{ 
	ArrayPool<byte>.Shared.Return(buffer); // 归还缓冲区到池 
	}
  1. 优雅关闭与资源释放
    TCP服务器:停止监听前,需通知所有客户端断开连接,并等待正在处理的任务完成;
    客户端:使用using语句确保TcpClient/UdpClient在使用后自动关闭Socket,避免资源泄漏。
  2. 异常处理与重试机制
    网络异常:捕获SocketException、IOException等网络异常,根据错误码处理(如连接超时、端口占用);
    UDP重传:对于需要可靠传输的UDP场景,需在应用层实现重传机制(如基于序列号的确认和重传)。
    18.8.6 总结:异步网络编程的核心价值
    异步TCP/UDP编程是构建高性能网络应用的基础,其核心价值在于:
    高并发支持:单个服务器可处理数千甚至数万个并发连接;
    低延迟:线程不阻塞等待IO,响应速度更快;
    资源高效利用:减少线程数量,降低内存和CPU开销。
    掌握异步网络编程,结合async/await语法和IOCP底层机制,可开发出稳定、高效的网络应用(如即时通讯系统、游戏服务器、微服务网关等)。
    18.8.7 思考题
    1.对比TCP和UDP的适用场景,举例说明什么情况下应使用UDP?
    2.解释TCP粘包问题的产生原因,如何通过消息头+消息体的方式解决?
    3.异步TCP服务器中,为什么要为每个客户端连接启动独立的异步任务?若直接在主线程处理会有什么问题?
    4.如何实现TCP服务器的优雅关闭(停止监听并等待所有客户端连接处理完成)?
    5.设计一个基于UDP的实时聊天系统,支持多客户端通信,需处理消息丢失和乱序问题。

 本站原创,转载请注明出处:


相关教程