目录
- 简介
- 什么是 UDP?
- UDP 与 TCP 的区别
- UDP 数据传输方式
- 单播 - Unicast(1:1)
- 广播 - Broadcast(1:n)
- 有限广播 - Limited Broadcast
- 直接广播 - Directed Broadcast
- 组/多播 - Multicast(n:m)
- 任播 - Anycast
- 注意事项
简介
什么是 UDP?
UDP(User Datagram Protocol,用户数据报协议)是一种简单的面向
无连接
的传输层协议,它提供了一种不可靠
的数据传输服务。
UDP 与 TCP 的区别
UDP 与 TCP 处于 OSI 模型 的传输层,都属于传输层协议。
其与 TCP 的区别如下图:
/ | UDP | TCP |
---|---|---|
面向连接 | 不面向连接 | 面向连接 |
可靠性 | 不可靠 | 可靠 |
数据传输速度 | 较快 | 较慢 |
数据传输方式 | 单播(1:1)、广播(1:n)、组播(n:m)、任播 | 单播(1:1) |
OSI模型 | 传输层 | 传输层 |
用途 | 游戏、语音、视频等场景 | 文件传输、HTTP等场景 |
优点 | 低延迟、低开销、无连接特性、支持组播与广播 | 可靠传输、流量控制、拥塞控制、面向连接、有序传输 |
缺点 | 不可靠传输、无流量控制、无拥塞控制、安全性较低 | 较高开销、较复杂、不支持组播和广播 |
UDP 数据传输方式
UDP 的数据传输方式有四种,分别是 单播
、广播
、组播
、任播
。
单播 - Unicast(1:1)
单播是指数据报从一个单一的发送方发送到一个单一的接收方。它是最常见的UDP数据传输方式,适用于一对一的通信。其与 TCP 通信类似。
在上图中,消息发送者在知道了消息接收者的 IP 地址,并给消息接收者发送了数据,消息接收者通过监听与消息发送者发送数据的端口号,即可监听到消息发送者发送过来的数据。代码如下:
消息发送者
@Override
public void sendMessage(@NonNull String ip, @NonNull String message) {
SingleThreads.getInstance().execute(() -> {
try {
DatagramSocket socket = new DatagramSocket();
// 设置端口复用
byte[] sendData = message.getBytes();
// 创建回复的数据包
InetAddress inetAddress = InetAddress.getByName(ip);
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, inetAddress, port);
// 发送消息到目标IP
socket.send(sendPacket);
socket.close();
Log.e(TAG, "发送广播成功,message:" + message);
} catch (IOException e) {
Log.e(TAG, "发送广播失败,message:" + e.getMessage());
throw new RuntimeException(e);
}
});
}
消息接收者
@Override
public void onReceiveMessageListener(@NonNull String ip, @NonNull ReceiveMessageInterface receiveMessage) {
SingleThreads.getInstance().execute(() -> {
try {
byte[] receiveData = new byte[1024];
DatagramSocket socket = new DatagramSocket(port);
// 创建DatagramPacket准备接收数据
while (isFinish) {
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
// 接收数据包
socket.receive(receivePacket);
// 获取数据和发送方地址
String message = new String(receivePacket.getData(), 0, receivePacket.getLength());
Log.e(TAG, "广播收到一条消息,message:" + message);
receiveMessage.onReceiveMessageListener(message);
}
socket.close();
} catch (IOException e) {
Log.e(TAG, "广播接收消息失败,message:" + e.getMessage());
throw new RuntimeException(e);
}
});
}
使用场景:在线游戏、VoIP(网络电话)等需要低延迟的通信。
广播 - Broadcast(1:n)
广播是指数据报从一个发送方发送到同一网络内的所有设备。UDP 广播常用于局域网(LAN)内的设备发现和服务公告。UDP 广播不会通过路由器将数据包传播到其他网络,仅限于本地网络中的设备可见并响应。
有限广播 - Limited Broadcast
有限广播(Limited Broadcast)使用特殊的 IP 地址 255.255.255.255 作为目标地址。
有限广播的广播消息限制在本地局域网(LAN)内部,不会穿过路由器传播到其他网络。它是向当前网络段内所有主机发送数据的一种方式。
在上图中,消息发送者发送一条数据到 255.255.255.255 的地址,这个地址被称为“有限广播地址”,它代表了本网络广播,即当一个 UDP 数据包发送到该地址时,该数据包会在局域网内部广播,传递给同一子网的内的所有设备。示例代码如下:
消息发送者
@Override
public void sendMessage(@NonNull String ip, @NonNull String message) {
SingleThreads.getInstance().execute(() -> {
try {
byte[] bytes = message.getBytes();
// 有限广播 ip 一般为 255.255.255.255
InetAddress inet = InetAddress.getByName(ip);
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, inet, port);
DatagramSocket datagramSocket = new DatagramSocket();
datagramSocket.send(packet);
Log.e(TAG, "发送广播成功,message:" + message);
} catch (IOException e) {
Log.e(TAG, "发送广播失败,message:" + e.getMessage());
e.printStackTrace();
}
});
}
消息接收者
@Override
public void onReceiveMessageListener(@NonNull String ip, @NonNull ReceiveMessageInterface receiveMessage) {
SingleThreads.getInstance().execute(() -> {
try {
// 创建缓冲区以接收数据
byte[] receiveData = new byte[1024];
DatagramSocket socket = new DatagramSocket(port);
socket.setBroadcast(true);
while (isFinish) {
// 创建DatagramPacket来接收数据
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
// 接收广播消息
socket.receive(receivePacket);
// 处理接收到的数据
String message = new String(receivePacket.getData(), 0, receivePacket.getLength());
receiveMessage.onReceiveMessageListener(message);
Log.e(TAG, "广播收到一条消息,message:" + message);
}
socket.close();
} catch (IOException e) {
Log.e(TAG, "广播接收消息失败,message:" + e.getMessage());
throw new RuntimeException(e);
}
});
}
使用场景:局域网内配置设备时发送广播包来发现或配置新设备。
直接广播 - Directed Broadcast
直接广播(Directed Broadcast)是针对特定子网的广播,使用子网的广播地址进行。
子网广播地址是通过将子网的网络地址部分保持不变,而将主机地址部分全部设为 1 得到的。例如,如果子网是 192.168.1.0/24(即 IP 地址为 192.168.1.1 ~ 192.168.1.254),则其广播地址为192.168.1.255。
这种广播可以跨路由器传播,但需要路由器配置允许广播流量通过,这在现代网络中较为少见,因为广播通常被限制在本地网络以减少广播风暴和提高网络效率。
上图中,消息发送者往 192.168.1.255 发送了数据,同网段的设备(即IP地址为 192.168.1.1 ~ 192.168.1.254 的设备)监听与消息发送者相同的端口即可收到该广播消息。示例代码如下:
消息发送者
@Override
public void sendMessage(@NonNull String ip, @NonNull String message) {
SingleThreads.getInstance().execute(() -> {
try {
// 创建DatagramSocket实例,并启用广播
DatagramSocket socket = new DatagramSocket();
socket.setBroadcast(true);
// 准备发送的数据
byte[] sendData = message.getBytes();
// 构建DatagramPacket,目标地址为子网广播地址,IP 一般为 192.168.子网网段.255
InetAddress broadcastAddress = InetAddress.getByName(ip);
DatagramPacket packet = new DatagramPacket(sendData, sendData.length, broadcastAddress, PORT);
// 发送广播数据包
socket.send(packet);
Log.e(TAG, "发送广播成功,message:" + message);
// 关闭socket
socket.close();
} catch (IOException e) {
Log.e(TAG, "发送广播失败,message:" + e.getMessage());
e.printStackTrace();
}
});
}
消息接收者
@Override
public void onReceiveMessageListener(@NonNull String ip, @NonNull ReceiveMessageInterface receiveMessage) {
SingleThreads.getInstance().execute(() -> {
try {
// 创建缓冲区以接收数据
byte[] receiveData = new byte[1024];
DatagramSocket socket = new DatagramSocket(PORT);
socket.setBroadcast(true);
while (isFinish) {
// 创建DatagramPacket来接收数据
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
// 接收广播消息
socket.receive(receivePacket);
// 处理接收到的数据
String message = new String(receivePacket.getData(), 0, receivePacket.getLength());
receiveMessage.onReceiveMessageListener(message);
Log.e(TAG, "广播收到一条消息,message:" + message);
}
socket.close();
} catch (IOException e) {
Log.e(TAG, "广播接收消息失败,message:" + e.getMessage());
throw new RuntimeException(e);
}
});
}
使用场景:向整个子网发送告警或通知。
组/多播 - Multicast(n:m)
组播(Multicast,又称多播)是指数据报从一个发送方发送到多个接收方,但这些接收方是预先定义的一组设备,而不是整个网络。
组播常用于需要同时向多个接收方发送数据的场景,如视频直播、股票行情分发等。组播数据报发送到一个特定的组播地址,只有加入该组的接收方才能接收到数据。
当然,组播预定义的一组设备的 IP 也是有所要求的,组播地址范围一般在 224.0.0.0 至 239.255.255.255 之间。
在上图中,消息发送者 A 和消息发送者 B 分别向 224.0.0.1 和 224.0.0.2 发送数据,消息接收者 A 只加入了 224.0.0.1 的组,那么它只能接收到 224.0.0.1 的消息,而消息接收者 B 分别加入了 224.0.0.1 和 224.0.0.2 的组,那么它就可以接收到消息发送者 A 和消息发送者 B 发送的消息。示例代码如下:
消息发送者
@Override
public void sendMessage(@NonNull String ip, @NonNull String message) {
SingleThreads.getInstance().execute(() -> {
try {
// 组播地址,范围在224.0.0.0至239.255.255.255
InetAddress group = InetAddress.getByName(ip);
// 将消息转换为字节数组
byte[] buffer = message.getBytes();
// 创建DatagramPacket,使用组播地址和端口
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, group, port);
// 发送组播消息
MulticastSocket socket = new MulticastSocket();
socket.send(packet);
socket.close();
Log.e(TAG, "发送广播成功,message:" + message);
} catch (IOException e) {
Log.e(TAG, "发送广播失败,message:" + e.getMessage());
throw new RuntimeException(e);
}
});
}
消息接收者
@Override
public void onReceiveMessageListener(@NonNull String ip,@NonNull ReceiveMessageInterface receiveMessage) {
SingleThreads.getInstance().execute(() -> {
try {
MulticastSocket socket = new MulticastSocket(port);
InetAddress group = InetAddress.getByName(ip);
// 加入组播组,一个设备可加入多个组播组
socket.joinGroup(group);
// 创建缓冲区以接收数据
byte[] receiveData = new byte[1024];
while (isFinish) {
// 创建DatagramPacket来接收数据
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
// 接收组播消息
socket.receive(receivePacket);
// 处理接收到的数据
String message = new String(receivePacket.getData(), 0, receivePacket.getLength());
receiveMessage.onReceiveMessageListener(message);
Log.e(TAG, "广播收到一条消息,message:" + message);
}
socket.close();
} catch (IOException e) {
Log.e(TAG, "广播接收消息失败,message:" + e.getMessage());
e.printStackTrace();
}
});
}
使用场景:数据分发、在线会议、视频流媒体传输。
任播 - Anycast
任播(Anycast)是一种网络地址分配方法,其中多个主机共享同一个IP地址,当客户端发送数据包到该地址时,网络路由器会根据一定的策略将数据包发送到其中一个最接近的主机。与广播(将数据包发送到所有主机)和组播(将数据包发送到组内所有主机)不同,任播只将数据包发送到一个特定的、通常是最优路径的单个主机。
使用场景:服务冗余和负载均衡。
注意事项
1、设备 A 同时发送数据给设备 A 与设备 B,设备 A 能接收到,但设备 B 无法接收到,这时候要考虑是否是网络问题,也可以尝试使用自己的手机开热点,再让设备 A 给设备 B 发消息。
代码链接 👉 DatagramSocket
参考文献
1、OSI 模型 — 维基百科