杰瑞科技汇

Java UDP Socket编程如何实现可靠数据传输?

UDP (User Datagram Protocol,用户数据报协议) 是一种无连接、不可靠、但传输速度快的协议,它不像 TCP 那样需要建立连接、维护连接状态和保证数据顺序到达,而是直接将数据包(Datagram)发送出去,不关心对方是否收到。

Java UDP Socket编程如何实现可靠数据传输?-图1
(图片来源网络,侵删)

UDP 编程的核心就是 发送数据包接收数据包

在 Java 中,java.net 包提供了 DatagramSocketDatagramPacket 这两个核心类来实现 UDP 通信。

  • DatagramSocket: 代表一个 socket,用于发送和接收数据报,它就像是邮局,负责将你的信件(数据包)寄出去或接收别人的信件。
  • DatagramPacket: 代表一个数据报,包含了要发送的数据、目标地址(IP 和端口)或接收到的数据以及来源地址(IP 和端口),它就像是信件本身,上面写满了内容、收件人地址和寄件人地址。

核心概念

DatagramSocket 的构造方法

  • DatagramSocket(): 创建一个未绑定的 socket,系统会自动分配一个可用的端口,通常用于客户端。
  • DatagramSocket(int port): 创建一个绑定到指定端口的 socket,通常用于服务器,因为服务器需要监听一个固定的端口。
  • DatagramSocket(int port, InetAddress laddr): 创建一个绑定到指定 IP 地址和端口的 socket。

DatagramPacket 的构造方法

  • 用于发送:
    • DatagramPacket(byte[] buf, int length, InetAddress address, int port)
      • buf: 要发送的数据字节数组。
      • length: 要发送的数据长度(从 buf 的第一个字节开始算)。
      • address: 目标主机的 IP 地址。
      • port: 目标主机的端口号。
  • 用于接收:
    • DatagramPacket(byte[] buf, int length)
      • buf: 用于接收数据的字节数组,这个数组的大小决定了能接收的最大数据包大小。
      • length: 数组的长度。

编程模型(Echo 示例)

下面我们通过一个经典的 "Echo"(回声)服务来演示 UDP 编程,一个服务器接收客户端发来的消息,然后将原消息返回给客户端。

服务器端代码

服务器需要:

Java UDP Socket编程如何实现可靠数据传输?-图2
(图片来源网络,侵删)
  1. 创建一个 DatagramSocket 并绑定到一个固定的端口。
  2. 创建一个 DatagramPacket 用于接收数据,并指定一个缓冲区。
  3. 循环调用 receive() 方法来等待并接收客户端的数据包。
  4. 从接收到的数据包中提取数据、客户端地址和端口。
  5. 创建一个新的 DatagramPacket,将接收到的数据发回给客户端。
  6. 调用 send() 方法发送回声数据包。
  7. 关闭 DatagramSocket
// UDPEchoServer.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class UDPEchoServer {
    // 服务器监听的端口号
    private static final int PORT = 9876;
    public static void main(String[] args) {
        // try-with-resources 语句,确保 DatagramSocket 自动关闭
        try (DatagramSocket serverSocket = new DatagramSocket(PORT)) {
            System.out.println("服务器已启动,正在监听端口 " + PORT + "...");
            // 创建一个缓冲区用于接收数据
            byte[] receiveBuffer = new byte[1024];
            while (true) {
                // 1. 创建一个空的 DatagramPacket 用于接收数据
                DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
                // 2. 接收客户端的数据包(此方法会阻塞,直到收到数据)
                serverSocket.receive(receivePacket);
                // 3. 从数据包中提取数据
                String receivedMessage = new String(receivePacket.getData(), 0, receivePacket.getLength());
                System.out.println("收到来自 " + receivePacket.getAddress().getHostAddress() + ":" + receivePacket.getPort() + " 的消息: " + receivedMessage);
                // 4. 准备回声数据
                byte[] echoMessage = receivedMessage.getBytes();
                // 5. 创建一个新的 DatagramPacket 用于发送回声
                //    使用客户端的地址和端口
                DatagramPacket sendPacket = new DatagramPacket(
                    echoMessage,
                    echoMessage.length,
                    receivePacket.getAddress(),
                    receivePacket.getPort()
                );
                // 6. 发送回声数据包
                serverSocket.send(sendPacket);
                System.out.println("已将回声发送回客户端。");
            }
        } catch (SocketException e) {
            System.err.println("创建或绑定 Socket 时出错: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("I/O 错误: " + e.getMessage());
        }
    }
}

客户端代码

客户端需要:

  1. 创建一个 DatagramSocket,系统会自动分配一个端口。
  2. 创建一个 DatagramPacket,将要发送的数据、服务器的 IP 地址和端口封装进去。
  3. 调用 send() 方法发送数据包。
  4. 创建另一个 DatagramPacket 用于接收服务器的回声,并指定一个缓冲区。
  5. 调用 receive() 方法等待接收回声数据包。
  6. 从接收到的数据包中提取并打印数据。
  7. 关闭 DatagramSocket
// UDPEchoClient.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
public class UDPEchoClient {
    // 服务器的 IP 地址和端口号
    private static final String SERVER_IP = "127.0.0.1"; // 本地回环地址
    private static final int SERVER_PORT = 9876;
    public static void main(String[] args) {
        // try-with-resources 语句,确保 DatagramSocket 自动关闭
        try (DatagramSocket clientSocket = new DatagramSocket()) {
            InetAddress serverAddress = InetAddress.getByName(SERVER_IP);
            Scanner scanner = new Scanner(System.in);
            System.out.println("客户端已启动,连接到服务器 " + SERVER_IP + ":" + SERVER_PORT);
            while (true) {
                System.out.print("请输入要发送的消息 (输入 'exit' 退出): ");
                String message = scanner.nextLine();
                if ("exit".equalsIgnoreCase(message)) {
                    break;
                }
                // 1. 准备要发送的数据
                byte[] sendBuffer = message.getBytes();
                // 2. 创建用于发送的 DatagramPacket
                DatagramPacket sendPacket = new DatagramPacket(
                    sendBuffer,
                    sendBuffer.length,
                    serverAddress,
                    SERVER_PORT
                );
                // 3. 发送数据包
                clientSocket.send(sendPacket);
                System.out.println("消息已发送: " + message);
                // 4. 创建用于接收的 DatagramPacket
                byte[] receiveBuffer = new byte[1024];
                DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
                // 5. 接收服务器的回声(此方法会阻塞,直到收到数据)
                clientSocket.receive(receivePacket);
                // 6. 从数据包中提取并打印回声消息
                String echoMessage = new String(receivePacket.getData(), 0, receivePacket.getLength());
                System.out.println("收到回声: " + echoMessage);
            }
        } catch (SocketException e) {
            System.err.println("创建 Socket 时出错: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("I/O 错误: " + e.getMessage());
        }
    }
}

如何运行

  1. 编译代码:

    javac UDPEchoServer.java UDPEchoClient.java
  2. 运行服务器:

    java UDPEchoServer

    你会看到控制台输出:服务器已启动,正在监听端口 9876...

    Java UDP Socket编程如何实现可靠数据传输?-图3
    (图片来源网络,侵删)
  3. 运行客户端: 打开另一个终端窗口,运行客户端:

    java UDPEchoClient
  4. 交互: 在客户端的控制台输入消息,"Hello UDP",然后按回车。

    • 客户端会显示:消息已发送: Hello UDP
    • 服务器会显示:收到来自 127.0.0.1:54321 的消息: Hello UDP (端口可能不同)
    • 客户端会接着显示:收到回声: Hello UDP

    输入 exit 可以退出客户端程序,服务器会一直运行,等待下一个客户端连接。


UDP 的特点与注意事项

  • 无连接: 发送数据前无需建立连接,开销小,速度快,但这也意味着发送前必须知道对方的 IP 和端口。
  • 不可靠:
    • 丢包: 数据包可能在传输过程中丢失。
    • 乱序: 数据包可能不按顺序到达。
    • 重复: 数据包可能被重复接收。
    • 如果你的应用需要可靠性(如文件传输、网页浏览),应该使用 TCP,UDP 适用于对实时性要求高、能容忍少量丢包的场景,如:
      • 在线游戏: 玩家的位置更新,偶尔丢一两个包影响不大。
      • 视频/音频会议: 偶尔的卡顿可以接受,但不能因为重传而延迟。
      • DNS 查询: 请求和响应都很小,速度至关重要。
      • 广播/多播: 向网络中的多个主机同时发送消息。
  • 面向数据报: 应用程序以数据报的形式读写数据,保留了消息的边界,TCP 是面向字节流的,没有消息边界。
  • 大小限制: 单个 UDP 数据报的最大理论大小是 65,507 字节(由 IP 包的 16 位总长度字段限制,IP 头 20 字节,UDP 头 8 字节,剩下 65535 - 28 = 65507),实际应用中,为了网络传输效率,通常建议将数据包大小控制在 512 字节以内。
  • 广播和多播: Java UDP 也支持广播和多播。InetAddress.getByName("255.255.255.255") 用于广播,发送到局域网内的所有主机,多播则需要使用特定的 IP 地址范围(D类地址,如 224.0.0.0 到 239.255.255.255)。
分享:
扫描分享到社交APP
上一篇
下一篇