UDP 协议详解与实战

简介

什么是 UDP?

UDP(User Datagram Protocol,用户数据报协议)是一种简单的面向 无连接 的传输层协议,它提供了一种 不可靠 的数据传输服务。

UDP 与 TCP 的区别

UDP 与 TCP 处于 OSI 模型 的传输层,都属于传输层协议。
在这里插入图片描述

其与 TCP 的区别如下图:

/UDPTCP
面向连接不面向连接面向连接
可靠性不可靠可靠
数据传输速度较快较慢
数据传输方式单播(1:1)、广播(1:n)、组播(n:m)、任播单播(1:1)
OSI模型传输层传输层
用途游戏、语音、视频等场景文件传输、HTTP等场景
优点低延迟、低开销、无连接特性、支持组播与广播可靠传输、流量控制、拥塞控制、面向连接、有序传输
缺点不可靠传输、无流量控制、无拥塞控制、安全性较低较高开销、较复杂、不支持组播和广播

在这里插入图片描述

UDP 数据传输方式

UDP 的数据传输方式有四种,分别是 单播广播组播任播

单播 - Unicast(1:1)

单播是指数据报从一个单一的发送方发送到一个单一的接收方。它是最常见的UDP数据传输方式,适用于一对一的通信。其与 TCP 通信类似。

udp单播
在上图中,消息发送者在知道了消息接收者的 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)内部,不会穿过路由器传播到其他网络。它是向当前网络段内所有主机发送数据的一种方式。
udp广播
在上图中,消息发送者发送一条数据到 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。

这种广播可以跨路由器传播,但需要路由器配置允许广播流量通过,这在现代网络中较为少见,因为广播通常被限制在本地网络以减少广播风暴和提高网络效率。

udp广播-直接广播
上图中,消息发送者往 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 之间。
udp组播/多播
在上图中,消息发送者 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 模型 — 维基百科

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宾有为

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值