-
C#中关于UDP协议深度解析(无连接、不可靠、广播与组播)
第二部分:C#网络编程核心技术
第3章 UDP编程实战
3.1 UDP协议深度解析(无连接、不可靠、广播与组播)
一、为什么要学UDP?
我刚做网络编程时,觉得UDP“不可靠”,没用处——直到做实时视频传输项目,用TCP卡成PPT,换成UDP后,流畅度直接拉满。后来才明白:TCP是“可靠的慢车”,UDP是“快速的跑车”,各有各的适vb.net教程C#教程python教程SQL教程access 2010教程用场景。这节就把UDP的底层原理、核心特性、实战代码讲透,让你知道什么时候用UDP,怎么用UDP。
二、UDP的核心特性:无连接、不可靠、快速
UDP(User Datagram Protocol)是无连接的传输层协议,和TCP的核心区别可以用一句话概括:
TCP:先敲门,再说话,说完再告别;UDP:直接喊,不管对方听不听得到。
-
无连接:不需要握手,直接发送
TCP通信前必须三次握手建立连接,UDP不需要——你想发数据,直接把数据打包成“数据报”,发给目标IP和端口就行,不管对方有没有在听,也不管对方是否存在。
类比:TCP是打电话,必须等对方接了才能说话;UDP是发微信,不管对方有没有看,你直接发就行。 -
不可靠:不保证数据到达,不保证顺序
UDP不提供可靠传输机制:
数据可能丢失:如果网络拥堵,UDP数据报会直接被丢弃,不会重传;
数据可能乱序:如果两个UDP数据报走不同的路由,后发的可能先到;
数据可能重复:如果网络中出现重复的UDP数据报,UDP不会去重。
类比:UDP是寄明信片,可能丢了,可能寄错顺序,可能收到两张一样的,但寄的时候很快,不需要等对方确认。 -
快速:开销小,延迟低
UDP的报文头部只有8字节(TCP是20字节),没有握手、挥手、重传、滑动窗口等开销,所以传输速度快,延迟低——适合实时性要求高的场景,比如视频通话、游戏、实时监控。
三、UDP的通信流程:简单到极致
UDP的通信流程比TCP简单太多,就三步:
1.创建Socket:指定UDP协议;
2.发送数据:把数据打包成数据报,发给目标IP和端口;
3.接收数据:监听指定端口,接收来自任意IP的UDP数据报。
四、C#实战:UDP客户端与服务器(基础版) -
UDP服务器:监听端口,接收数据
csharp
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace UdpServerDemo;
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字节(IPv4),所以缓冲区设为65536字节足够
var buffer = new byte[65536];
var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); // 用于存储发送方的IP地址和端口
try
{
while (true)
{
// 4. 异步接收数据:ReceiveFromAsync会阻塞当前线程,直到收到数据
var receiveResult = await udpSocket.ReceiveFromAsync(buffer, SocketFlags.None, remoteEndPoint);
var senderEndPoint = receiveResult.RemoteEndPoint as IPEndPoint;
int bytesRead = receiveResult.ReceivedBytes;
// 5. 解析数据:把字节数组转换成字符串
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(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(buffer, SocketFlags.None, remoteEndPoint);:异步接收UDP数据报,返回SocketReceiveFromResult,包含收到的字节数和发送方的IP地址;
5.Encoding.UTF8.GetString(buffer, 0, bytesRead);:把字节数组转换成字符串,注意UDP数据报是完整的,不需要担心粘包(每个数据报是独立的);
6.await udpSocket.SendToAsync(responseBytes, SocketFlags.None, senderEndPoint);:异步发送响应给发送方,不需要建立连接,直接指定目标IP和端口。
2. UDP客户端:发送数据,接收响应
csharp
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace UdpClientDemo;
class Program
{
// 服务器的IP地址和端口
private const string ServerIp = "127.0.0.1";
private const int ServerPort = 8888;
static async Task Main(string[] args)
{
// 1. 创建UDP Socket
using var udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
// 2. 目标服务器的IP地址和端口
var serverEndPoint = new IPEndPoint(IPAddress.Parse(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. 异步发送数据到服务器
await udpSocket.SendToAsync(sendBytes, SocketFlags.None, serverEndPoint);
Console.WriteLine($"已向 {ServerIp}:{ServerPort} 发送消息:{input}");
// 6. 异步接收服务器的响应
var buffer = new byte[65536];
var receiveResult = await udpSocket.ReceiveFromAsync(buffer, SocketFlags.None, new IPEndPoint(IPAddress.Any, 0));
string responseMsg = Encoding.UTF8.GetString(buffer, 0, receiveResult.ReceivedBytes);
Console.WriteLine($"收到服务器响应:{responseMsg}");
}
}
catch (Exception ex)
{
Console.WriteLine($"客户端异常:{ex.Message}");
}
// 7. using会自动释放Socket资源
}
}
代码逐行讲:
1.new IPEndPoint(IPAddress.Parse(ServerIp), ServerPort);:指定服务器的IP地址和端口,UDP客户端不需要绑定端口,系统会自动分配一个临时端口;
2.await udpSocket.SendToAsync(sendBytes, SocketFlags.None, serverEndPoint);:异步发送UDP数据报到服务器,不需要建立连接;
3.await udpSocket.ReceiveFromAsync(buffer, SocketFlags.None, new IPEndPoint(IPAddress.Any, 0));:异步接收服务器的响应,注意UDP客户端可能收到来自任意IP的响应,所以需要验证发送方是否是服务器;
4.注意:如果服务器没有启动,客户端发送数据不会报错,因为UDP是无连接的,不知道对方是否存在。
五、UDP的高级特性:广播与组播
UDP除了一对一通信,还支持一对多通信:广播和组播——这是TCP没有的特性,适合需要向多个设备发送数据的场景,比如局域网设备发现、实时数据推送。
-
广播:向局域网内所有设备发送数据
广播是向同一局域网内的所有设备发送数据,目标IP是局域网的广播地址(比如192.168.1.255)。
C#实战:UDP广播客户端
csharp
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace UdpBroadcastClient;
class Program
{
// 局域网广播地址(根据你的局域网网段调整)
private const string BroadcastIp = "192.168.1.255";
private const int Port = 8888;
static async Task Main(string[] args)
{
using var udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
// 必须开启广播权限,否则无法发送广播数据
udpSocket.EnableBroadcast = true;
var broadcastEndPoint = new IPEndPoint(IPAddress.Parse(BroadcastIp), Port);
string message = "我是广播消息,局域网内的设备都能收到!";
byte[] sendBytes = Encoding.UTF8.GetBytes(message);
await udpSocket.SendToAsync(sendBytes, SocketFlags.None, broadcastEndPoint);
Console.WriteLine($"已发送广播消息:{message}");
}
}
代码关键:udpSocket.EnableBroadcast = true;——必须开启广播权限,否则操作系统会拒绝发送广播数据。
2. 组播:向指定组的设备发送数据
组播是向同一组播组内的设备发送数据,目标IP是组播地址(224.0.0.0~239.255.255.255),设备需要加入该组播组才能收到数据。
C#实战:UDP组播服务器(发送组播数据)
csharp
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace UdpMulticastServer;
class Program
{
// 组播地址(224.0.0.0~239.255.255.255)
private const string MulticastIp = "224.0.0.1";
private const int Port = 8888;
static async Task Main(string[] args)
{
using var udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
var multicastEndPoint = new IPEndPoint(IPAddress.Parse(MulticastIp), Port);
string message = "我是组播消息,只有加入组播组的设备才能收到!";
byte[] sendBytes = Encoding.UTF8.GetBytes(message);
// 循环发送组播数据,每秒一次
while (true)
{
await udpSocket.SendToAsync(sendBytes, SocketFlags.None, multicastEndPoint);
Console.WriteLine($"已发送组播消息:{message}");
await Task.Delay(1000);
}
}
}
C#实战:UDP组播客户端(加入组播组,接收数据)
csharp
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace UdpMulticastClient;
class Program
{
private const string MulticastIp = "224.0.0.1";
private const int Port = 8888;
static async Task Main(string[] args)
{
using var udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
// 绑定端口,必须和组播服务器的端口一致
var localEndPoint = new IPEndPoint(IPAddress.Any, Port);
udpSocket.Bind(localEndPoint);
// 加入组播组
var multicastAddress = IPAddress.Parse(MulticastIp);
udpSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress));
Console.WriteLine($"已加入组播组 {MulticastIp}:{Port},等待接收消息...");
var buffer = new byte[65536];
var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
while (true)
{
var receiveResult = await udpSocket.ReceiveFromAsync(buffer, SocketFlags.None, remoteEndPoint);
string message = Encoding.UTF8.GetString(buffer, 0, receiveResult.ReceivedBytes);
Console.WriteLine($"收到组播消息:{message}");
}
}
}
代码关键:udpSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress));——加入组播组,这样才能收到该组播组的数据。
六、UDP的可靠传输:自己造轮子?
UDP本身不可靠,但如果需要可靠的UDP传输,怎么办?可以自己实现可靠传输机制,或者用现成的库(比如KCP、QUIC)。
-
手动实现可靠UDP的核心机制
序列号与确认号:每个UDP数据报加序列号,接收方收到后回复确认号,发送方如果没收到确认号,就重传;
超时重传:发送方设置超时时间,超过时间没收到确认号,就重传数据报;
滑动窗口:控制发送速率,避免接收方处理不过来;
去重:接收方记录已收到的序列号,避免重复处理同一数据报。
C#简化版示例:
csharp
// 发送方:给数据报加序列号
int sequenceNumber = 0;
string message = "Hello UDP";
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
byte[] sequenceBytes = BitConverter.GetBytes(sequenceNumber);
byte[] sendBytes = new byte[sequenceBytes.Length + messageBytes.Length];
Buffer.BlockCopy(sequenceBytes, 0, sendBytes, 0, sequenceBytes.Length);
Buffer.BlockCopy(messageBytes, 0, sendBytes, sequenceBytes.Length, messageBytes.Length);
await udpSocket.SendToAsync(sendBytes, SocketFlags.None, serverEndPoint);
// 接收方:验证序列号,回复确认号
byte[] receiveBuffer = new byte[65536];
var receiveResult = await udpSocket.ReceiveFromAsync(receiveBuffer, SocketFlags.None, remoteEndPoint);
int sequenceNumber = BitConverter.ToInt32(receiveBuffer, 0);
string message = Encoding.UTF8.GetString(receiveBuffer, 4, receiveResult.ReceivedBytes - 4);
// 回复确认号
byte[] ackBytes = BitConverter.GetBytes(sequenceNumber + 1);
await udpSocket.SendToAsync(ackBytes, SocketFlags.None, receiveResult.RemoteEndPoint);
-
现成的可靠UDP库
KCP:轻量级可靠UDP协议,比TCP快,适合游戏、实时通信;
QUIC:Google开发的基于UDP的可靠传输协议,HTTP/3就是基于QUIC;
ENet:游戏开发常用的可靠UDP库,支持多通道、流量控制。
七、TCP vs UDP:怎么选?
| 场景 | 选TCP还是UDP? | 原因 |
|---|---|---|
| 文件传输、电商支付 | TCP | 需要可靠传输,不能丢数据 |
| 视频通话、游戏 | UDP | 实时性要求高,丢几个包不影响 |
| 局域网设备发现 | UDP广播 | 不需要连接,能快速发现所有设备 |
| 实时数据推送 | UDP组播 | 一对多传输,比TCP高效 |
| 物联网设备通信 | UDP | 设备资源有限,UDP开销小 |
八、总结:UDP不是“垃圾协议”,是“特种工具”
UDP的优势:无连接、开销小、延迟低、支持广播和组播;
UDP的劣势:不可靠、不保证顺序;
适用场景:实时性要求高的场景,比如视频通话、游戏、实时监控;一对多通信的场景,比如局域网设备发现、实时数据推送;资源有限的设备,比如物联网设备。
下一节我们会学习UDP的实战进阶:用KCP实现可靠UDP,用QUIC实现HTTP/3通信,让你既能享受UDP的速度,又能保证数据的可靠。
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49514.html










