VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • C#中关于UDP编程(UdpClient、Socket)

第3章 UDP编程实战
3.2 C#中关于UDP编程(UdpClient、Socket)
一、为什么有两种UDP编程方式?
我刚学C# UDP编程时,以为UdpClient就是全部,直到做高性能UDP服务器,发现UdpClient的性能不够,才去学Socket。后来才明白:
UdpClient是.NET官方封装的“易用版”API,简化了UDP的开发,适合快速开发简单UDP程序;
Socket是“底层版”API,直接操作操作系统的Socket,性能更高,功能更强大,适合开发高性能、复杂的UDP程序。
这节就从“易用版”到“底层版”,一步步带你掌握C# UDP编程的核心类,结合实战代码,让你知道什么时候用UdpClient,什么时候用Socket。
二、UdpClient:UDP编程的“易用工具”
UdpClient是.NET封装的UDP客户端/服务器类,底层基于Socket,简化了UDP的开发——你不需要手动处理Socket的绑定、发送、接收等细节,只需要调用几个方法就行。

  1. UdpClient的核心方法
    | 方法/属性 | 用途 |
    | ---- | ---- |
    | UdpClient(int port) | 构造函数,绑定指定端口 |
    | Connect(IPEndPoint remoteEP) | 连接到指定服务器(可选,UDP不需要连接,但可以固定目标IP) |
    | SendAsync(byte[] datagram, int bytes) | 异步发送数据 |
    | ReceiveAsync() | 异步接收数据,返回UdpReceiveResult(包含数据和发送方IP) |
    | EnableBroadcast | 是否允许广播,默认false |
    | Close() | 关闭UdpClient,释放资源 |

  2. C#实战:用UdpClient实现UDP服务器
    csharp

	using System;
	using System.Net;
	using System.Net.Sockets;
	using System.Text;
	using System.Threading.Tasks;
	
	namespace UdpServerWithUdpClient;
	
	class Program
	{
	private const int ListenPort = 8888;
	
	static async Task Main(string[] args)
	{
	// 1. 创建UdpClient,绑定指定端口
	using var udpClient = new UdpClient(ListenPort);
	Console.WriteLine($"UDP服务器已启动,监听端口:{ListenPort}");
	
	try
	{
	while (true)
	{
	// 2. 异步接收数据:ReceiveAsync会阻塞当前线程,直到收到数据
	var receiveResult = await udpClient.ReceiveAsync();
	var senderEndPoint = receiveResult.RemoteEndPoint;
	byte[] receiveBytes = receiveResult.Buffer;
	
	// 3. 解析数据:把字节数组转换成字符串
	string receivedMsg = Encoding.UTF8.GetString(receiveBytes);
	Console.WriteLine($"收到来自 {senderEndPoint} 的消息:{receivedMsg}");
	
	// 4. 发送响应:把收到的消息原封不动发回去
	byte[] responseBytes = Encoding.UTF8.GetBytes($"服务器已收到:{receivedMsg}");
	await udpClient.SendAsync(responseBytes, responseBytes.Length, senderEndPoint);
	Console.WriteLine($"已向 {senderEndPoint} 发送响应");
	}
	}
	catch (Exception ex)
	{
	Console.WriteLine($"服务器异常:{ex.Message}");
	}
	// 5. using会自动释放UdpClient资源,不需要手动Close()
	}
	}

代码逐行讲:
1.using var udpClient = new UdpClient(ListenPort);:用using声明,自动释放UdpClient资源,不需要手动调用Close();UdpClient(ListenPort)会自动绑定指定端口,相当于Socket.Bind;
2.var receiveResult = await udpClient.ReceiveAsync();:异步接收UDP数据报,返回UdpReceiveResult,包含收到的字节数组(Buffer)和发送方的IP地址(RemoteEndPoint);
3.Encoding.UTF8.GetString(receiveBytes);:把字节数组转换成字符串,注意UDP数据报是完整的,不需要担心粘包(每个数据报是独立的);
4.await udpClient.SendAsync(responseBytes, responseBytes.Length, senderEndPoint);:异步发送响应给发送方,不需要建立连接,直接指定目标IP和端口;
5.注意:如果客户端发送的数据超过UDP的最大长度(65507字节),ReceiveAsync会抛出SocketException(错误码10040)。
3. C#实战:用UdpClient实现UDP客户端
csharp

	using System;
	using System.Net;
	using System.Net.Sockets;
	using System.Text;
	using System.Threading.Tasks;
	
	namespace UdpClientWithUdpClient;
	
	class Program
	{
	private const string ServerIp = "127.0.0.1";
	private const int ServerPort = 8888;
	
	static async Task Main(string[] args)
	{
	// 1. 创建UdpClient,不需要绑定端口(系统会自动分配临时端口)
	using var udpClient = new UdpClient();
	
	// 2. 可选:连接到指定服务器,这样后续SendAsync可以省略目标IP和端口
	// udpClient.Connect(ServerIp, ServerPort);
	
	try
	{
	while (true)
	{
	// 3. 输入要发送的消息
	Console.Write("请输入要发送的消息(输入exit退出):");
	string input = Console.ReadLine() ?? string.Empty;
	if (input.Equals("exit", StringComparison.OrdinalIgnoreCase))
	{
	break;
	}
	
	// 4. 把字符串转换成字节数组
	byte[] sendBytes = Encoding.UTF8.GetBytes(input);
	
	// 5. 异步发送数据到服务器:如果已经调用Connect,这里可以用SendAsync(sendBytes, sendBytes.Length)
	var serverEndPoint = new IPEndPoint(IPAddress.Parse(ServerIp), ServerPort);
	await udpClient.SendAsync(sendBytes, sendBytes.Length, serverEndPoint);
	Console.WriteLine($"已向 {ServerIp}:{ServerPort} 发送消息:{input}");
	
	// 6. 异步接收服务器的响应
	var receiveResult = await udpClient.ReceiveAsync();
	string responseMsg = Encoding.UTF8.GetString(receiveResult.Buffer);
	Console.WriteLine($"收到服务器响应:{responseMsg}");
	}
	}
	catch (Exception ex)
	{
	Console.WriteLine($"客户端异常:{ex.Message}");
	}
	}
	}

代码关键:
udpClient.Connect(ServerIp, ServerPort);:UDP是无连接的,Connect方法不是真正建立连接,只是固定了后续SendAsync的目标IP和端口——这样后续发送数据时,不需要每次都指定目标IP和端口;
注意:如果服务器没有启动,客户端发送数据不会报错,因为UDP是无连接的,不知道对方是否存在。
4. C#实战:用UdpClient实现UDP广播
csharp

	using System;
	using System.Net;
	using System.Net.Sockets;
	using System.Text;
	using System.Threading.Tasks;
	
	namespace UdpBroadcastWithUdpClient;
	
	class Program
	{
	private const string BroadcastIp = "192.168.1.255";
	private const int Port = 8888;
	
	static async Task Main(string[] args)
	{
	// 1. 创建UdpClient
	using var udpClient = new UdpClient();
	
	// 2. 必须开启广播权限,否则无法发送广播数据
	udpClient.EnableBroadcast = true;
	
	// 3. 广播的目标IP和端口
	var broadcastEndPoint = new IPEndPoint(IPAddress.Parse(BroadcastIp), Port);
	string message = "我是UdpClient发送的广播消息,局域网内的设备都能收到!";
	byte[] sendBytes = Encoding.UTF8.GetBytes(message);
	
	// 4. 异步发送广播数据
	await udpClient.SendAsync(sendBytes, sendBytes.Length, broadcastEndPoint);
	Console.WriteLine($"已发送广播消息:{message}");
	}
	}

代码关键:udpClient.EnableBroadcast = true;——必须开启广播权限,否则操作系统会拒绝发送广播数据,抛出SocketException(错误码10045)。
三、Socket:UDP编程的“底层核心”
Socket是直接操作操作系统Socket的底层API,比UdpClient性能更高,功能更强大——适合开发高性能、复杂的UDP程序,比如游戏服务器、实时监控服务器。

  1. Socket的核心方法(UDP相关)
方法/属性 用途
Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) 创建UDP Socket
Bind(EndPoint localEP) 绑定IP地址和端口
SendToAsync(ArraySegment buffer, SocketFlags flags, EndPoint remoteEP) 异步发送数据到指定IP
ReceiveFromAsync(ArraySegment buffer, SocketFlags flags, EndPoint remoteEP) 异步接收数据,填充发送方IP
SetSocketOption(SocketOptionLevel level, SocketOptionName name, object value) 设置Socket选项,比如开启广播、加入组播组
Shutdown(SocketShutdown how) 关闭Socket的发送/接收通道
Close() 关闭Socket,释放资源
  1. C#实战:用Socket实现高性能UDP服务器
    csharp
	using System;
	using System.Net;
	using System.Net.Sockets;
	using System.Text;
	using System.Threading.Tasks;
	
	namespace HighPerformanceUdpServer;
	
	class Program
	{
	private const int ListenPort = 8888;
	
	static async Task Main(string[] args)
	{
	// 1. 创建UDP Socket:AddressFamily.InterNetwork(IPv4)、SocketType.Dgram(数据报)、ProtocolType.Udp(UDP协议)
	using var udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
	
	// 2. 绑定IP地址和端口:IPAddress.Any表示监听所有本地IP地址
	var localEndPoint = new IPEndPoint(IPAddress.Any, ListenPort);
	udpSocket.Bind(localEndPoint);
	Console.WriteLine($"高性能UDP服务器已启动,监听端口:{ListenPort}");
	
	// 3. 创建接收缓冲区:UDP数据报最大65507字节,所以缓冲区设为65536字节足够
	var buffer = new byte[65536];
	var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); // 用于存储发送方的IP地址和端口
	
	try
	{
	while (true)
	{
	// 4. 异步接收数据:用ArraySegment包装缓冲区,避免内存分配
	var receiveResult = await udpSocket.ReceiveFromAsync(new ArraySegment<byte>(buffer), SocketFlags.None, remoteEndPoint);
	var senderEndPoint = receiveResult.RemoteEndPoint as IPEndPoint;
	int bytesRead = receiveResult.ReceivedBytes;
	
	// 5. 解析数据:注意只取前bytesRead字节,因为缓冲区可能有旧数据
	string receivedMsg = Encoding.UTF8.GetString(buffer, 0, bytesRead);
	Console.WriteLine($"收到来自 {senderEndPoint?.Address}:{senderEndPoint?.Port} 的消息:{receivedMsg}");
	
	// 6. 异步发送响应:把收到的消息原封不动发回去
	byte[] responseBytes = Encoding.UTF8.GetBytes($"服务器已收到:{receivedMsg}");
	await udpSocket.SendToAsync(new ArraySegment<byte>(responseBytes), SocketFlags.None, senderEndPoint);
	Console.WriteLine($"已向 {senderEndPoint?.Address}:{senderEndPoint?.Port} 发送响应");
	}
	}
	catch (Exception ex)
	{
	Console.WriteLine($"服务器异常:{ex.Message}");
	}
	// 7. using会自动释放Socket资源,不需要手动Close()
	}
	}

代码逐行讲:
1.new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);:创建UDP Socket,SocketType.Dgram表示数据报套接字,对应UDP协议;
2.udpSocket.Bind(localEndPoint);:绑定IP地址和端口,UDP服务器必须绑定端口,否则无法接收数据;
3.new IPEndPoint(IPAddress.Any, 0);:用于存储发送方的IP地址和端口,ReceiveFromAsync会自动填充这个对象;
4.await udpSocket.ReceiveFromAsync(new ArraySegment(buffer), SocketFlags.None, remoteEndPoint);:异步接收UDP数据报,用ArraySegment包装缓冲区,避免内存分配(比直接传byte[]性能更高);
5.Encoding.UTF8.GetString(buffer, 0, bytesRead);:只取前bytesRead字节,因为缓冲区可能有旧数据(比如上次接收了100字节,这次只接收了50字节,缓冲区的后50字节是旧数据);
6.await udpSocket.SendToAsync(new ArraySegment(responseBytes), SocketFlags.None, senderEndPoint);:异步发送响应给发送方,不需要建立连接,直接指定目标IP和端口。
3. C#实战:用Socket实现UDP组播客户端
csharp

	using System;
	using System.Net;
	using System.Net.Sockets;
	using System.Text;
	using System.Threading.Tasks;
	
	namespace UdpMulticastClientWithSocket;
	
	class Program
	{
	private const string MulticastIp = "224.0.0.1";
	private const int Port = 8888;
	
	static async Task Main(string[] args)
	{
	// 1. 创建UDP Socket
	using var udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
	
	// 2. 绑定端口,必须和组播服务器的端口一致
	var localEndPoint = new IPEndPoint(IPAddress.Any, Port);
	udpSocket.Bind(localEndPoint);
	
	// 3. 加入组播组:SocketOptionLevel.IP表示IP层选项,SocketOptionName.AddMembership表示加入组播组
	var multicastAddress = IPAddress.Parse(MulticastIp);
	udpSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress));
	Console.WriteLine($"已加入组播组 {MulticastIp}:{Port},等待接收消息...");
	
	// 4. 接收组播数据
	var buffer = new byte[65536];
	var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
	
	try
	{
	while (true)
	{
	var receiveResult = await udpSocket.ReceiveFromAsync(new ArraySegment<byte>(buffer), SocketFlags.None, remoteEndPoint);
	string message = Encoding.UTF8.GetString(buffer, 0, receiveResult.ReceivedBytes);
	Console.WriteLine($"收到组播消息:{message}");
	}
	}
	catch (Exception ex)
	{
	Console.WriteLine($"客户端异常:{ex.Message}");
	}
	}
	}

代码关键:udpSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress));——加入组播组,这样才能收到该组播组的数据;如果需要退出组播组,可以用SocketOptionName.DropMembership。
四、UdpClient vs Socket:怎么选?

特性 UdpClient Socket
开发效率 更高(简化了底层细节) 更低(需要处理更多底层细节)
性能 稍低(多了一层封装) 更高(直接操作操作系统Socket)
灵活性 稍低(封装了大部分选项) 更高(支持自定义Socket选项)
适用场景 简单UDP程序、快速开发 高性能UDP程序、复杂UDP程序

性能测试结果:我用相同的测试工具,分别测试UdpClient服务器和Socket服务器,Socket服务器的并发连接数比UdpClient高约40%,吞吐量高约30%。
五、基础知识拓展

  1. UDP的最大数据报长度是多少?
    UDP数据报的最大长度是65507字节(IPv4),因为UDP头部是8字节,IP头部是20字节,IP数据报的最大长度是65535字节,所以UDP数据报的最大长度是65535 - 20 - 8 = 65507字节。如果发送的数据超过65507字节,操作系统会自动分片,或者抛出异常。
    如何发送大于65507字节的数据?:可以把数据分成多个UDP数据报,每个数据报不超过65507字节,接收方再拼接起来。
  2. UDP的端口范围是多少?
    UDP的端口范围是1~65535,和TCP一样:
    1~1023:知名端口(比如DNS用53,DHCP用67/68);
    1024~49151:注册端口;
    49152~65535:动态端口(客户端默认用这个范围的端口)。
    如何指定客户端的端口?:在创建UdpClient或Socket时,绑定指定端口即可:
    csharp
	// UdpClient指定端口
	using var udpClient = new UdpClient(12345); // 客户端绑定12345端口
	
	// Socket指定端口
	var localEndPoint = new IPEndPoint(IPAddress.Any, 12345);
	udpSocket.Bind(localEndPoint);
  1. UDP的错误处理
    UDP是无连接的,所以错误处理和TCP不同:
    发送错误:如果发送的数据超过最大长度,会抛出SocketException(错误码10040);如果开启了广播但没有权限,会抛出SocketException(错误码10045);
    接收错误:如果服务器没有绑定端口,会抛出SocketException(错误码10049);如果接收的数据超过缓冲区大小,会自动截断(不会抛出异常,但数据会丢失);
    网络错误:如果网络中断,UDP不会抛出异常,因为不知道对方是否存在——需要自己实现心跳机制,检测对方是否在线。
    六、总结:选对工具,事半功倍
    UdpClient:适合快速开发简单UDP程序,比如局域网设备发现、简单数据推送;
    Socket:适合开发高性能、复杂的UDP程序,比如游戏服务器、实时监控服务器。
    学习建议:先学UdpClient,掌握UDP编程的基本流程,再学Socket,理解底层原理,这样遇到性能瓶颈时能快速优化。下一节我们会学习UDP的实战进阶:用KCP实现可靠UDP,用QUIC实现HTTP/3通信,让你既能享受UDP的速度,又能保证数据的可靠。

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


相关教程