Java.基于UDP协议的核心内容

一、UDP协议基础定义

UDP是TCP/IP协议簇中的传输层协议。基于UDP的网络通信通过数据报套接字实现,在Java中对应DatagramSocket类(用于创建数据报套接字)和DatagramPacket类(作为发送或接收的UDP数据报载体)。

二、UDP协议格式

UDP协议首部格式简洁,共8字节,具体结构如下:

  • 16位源端口号:标识发送端进程的端口

  • 16位目的端口号:标识接收端进程的端口

  • 16位UDP长度:表示整个数据报(UDP首部+UDP数据)的总长度

  • 16位UDP检验和:用于校验数据的完整性,若校验和出错,数据报会被直接丢弃

三、UDP协议核心特点

UDP传输过程类似于寄信,核心特点可概括为以下几点:

  1. 无连接:无需提前建立连接,只要知道对方的IP地址和端口号,就可以直接发送数据报,通信前不存在"三次握手"的过程。

  2. 不可靠传输:没有确认应答机制,也没有超时重传机制。如果数据报因网络故障丢失,UDP协议层不会向应用层返回任何错误信息,无法保证数据一定能到达接收端。

  3. 面向数据报:应用层交给UDP的报文会被原样发送,既不会拆分也不会合并。发送端调用一次发送操作(如Java中的send方法)发送的数据,接收端必须调用一次接收操作(如Java中的receive方法)完整接收,不能分多次接收。例如发送100字节的数据,接收端必须一次接收100字节,无法循环100次每次接收1字节。

  4. 缓冲区特性:仅存在接收缓冲区,无发送缓冲区。接收缓冲区用于存储收到的数据报,若应用层未及时读取,后续到达的数据报会被丢弃。

  5. 数据大小受限:一次UDP数据传输的最大长度受限于16位UDP长度字段,最大为64KB。若需传输超过64K的数据,需在应用层手动分包、多次发送,并在接收端手动拼装。

四、Java UDP数据报套接字通信模型

单轮发送端与接收端流程

  • 发送端:① 创建DatagramSocket对象;② 构造发送的数据报(DatagramPacket),需指定发送数据(字节数组形式)、接收端IP地址和端口号;③ 调用socket.send(packet)发送数据报(非阻塞,直接发送)。

  • 接收端:① 创建DatagramSocket对象;② 构造接收的数据报(DatagramPacket),指定空字节数组作为数据存储载体;③ 调用socket.receive阻塞式等待接收数据报;④ 接收成功后,通过DatagramPacket的方法获取接收的数据、发送端IP地址和端口号。

五、UDP相关API详解

1. DatagramSocket类

用于创建UDP数据报套接字,核心构造方法和方法如下:

构造方法

  • DatagramSocket():创建UDP数据报套接字,绑定到本机任意一个随机端口(一般用于客户端);

  • DatagramSocket(int port):创建UDP数据报套接字,绑定到本机指定的端口(一般用于服务端)。

核心方法

  • void receive(DatagramPacket p):从此套接字接收数据报,若未接收到数据,该方法会阻塞等待;

  • void send(DatagramPacket p):从此套接字发送数据报包,不会阻塞等待,直接发送;

  • void close():关闭此数据报套接字。

2. DatagramPacket类

作为UDP发送和接收的数据报载体,核心构造方法和方法如下:

构造方法

  • DatagramPacket(byte[] buf, int length):构造用于接收数据报的对象,接收的数据保存在字节数组buf中,接收长度为length;

  • DatagramPacket(byte[] buf, int offset, int length, SocketAddress address):构造用于发送数据报的对象,发送的数据为字节数组buf中从offset开始的length个字节,address指定目的主机的IP和端口号(通常使用InetSocketAddress实现)。

核心方法

  • InetAddress getAddress():从接收的数据报中获取发送端主机IP地址,或从发送的数据报中获取接收端主机IP地址;

  • int getPort():从接收的数据报中获取发送端主机端口号,或从发送的数据报中获取接收端主机端口号;

  • byte[] getData():获取数据报中的数据。

3. InetSocketAddress类

SocketAddress的子类,用于创建包含IP地址和端口号的Socket地址,核心构造方法:

  • InetSocketAddress(InetAddress addr, int port):创建一个Socket地址,包含指定的IP地址和端口号。

六、UDP协议使用注意事项

  • 数据大小限制处理:由于UDP一次传输最大为64K,若传输数据超过此大小,需在应用层手动分包,多次发送,并在接收端手动拼装。

  • 端口号使用规范:避免使用0-1023的知名端口号(如HTTP的80端口、FTP的21端口等),客户端端口号一般由操作系统从1024-65535的范围动态分配。

  • 粘包问题说明:UDP不存在粘包问题。因为UDP是面向数据报的,每个数据报都是独立的,接收端要么完整接收一个数据报,要么不接收,不会出现多个数据报合并或拆分的情况。

  • MTU对UDP的影响:以太网的MTU(最大传输单元)为1500字节,UDP数据报若超过MTU(扣除20字节IP首部和8字节UDP首部,实际数据部分超过1472字节),会在网络层被分片。一旦任意一个分片丢失,接收端网络层重组会失败,整个UDP数据报失效,导致数据丢失概率增加。

七. UDP与TCP的区别

UDP和TCP作为传输层的两大核心协议,不存在绝对的优劣,需根据场景选择,核心区别如下:

  • 连接性:UDP无连接,TCP有连接;

  • 可靠性:UDP不可靠,TCP可靠(具备确认应答、超时重传等机制);

  • 数据传输方式:UDP面向数据报,TCP面向字节流;

  • 性能:UDP开销小、延迟低,TCP因可靠性机制存在额外开销,延迟相对较高;

  • 适用场景:UDP适用于高速传输、实时性要求高的场景(如视频传输、早期QQ),也可用于广播;TCP适用于需要可靠传输的场景(如文件传输、重要状态更新)。

八、用UDP实现可靠传输

UDP本身是不可靠的,但可参考TCP的可靠性机制,在应用层实现可靠传输,核心思路包括:

  • 引入序列号:对发送的每个数据报进行编号,确保接收端能按序接收并去重;

  • 引入确认应答:接收端收到数据报后,向发送端返回确认信息,告知已收到的数据;

  • 引入超时重传:发送端发送数据后启动定时器,若在规定时间内未收到确认应答,则重发该数据报;

  • 其他补充机制:可根据需求引入滑动窗口(提高传输效率)、流量控制(根据接收端处理能力调整发送速度)、拥塞控制(根据网络状态调整发送速度)等机制。

九、UDP典型代码示例

1. UDP Echo Server

java 复制代码
public class UdpEchoServer {
    private DatagramSocket socket = null;

    // 构造方法:绑定指定端口
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    // 启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 1. 读取请求并解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket); // 阻塞等待接收数据
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());

            // 2. 处理请求(回显:响应与请求相同)
            String response = process(request);

            // 3. 发送响应
            DatagramPacket responsePacket = new DatagramPacket(
                    response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress()
            );
            socket.send(responsePacket);

            // 记录日志
            System.out.printf("[%s:%d] req: %s, resp: %s\n",
                    requestPacket.getAddress().toString(),
                    requestPacket.getPort(),
                    request, response);
        }
    }

    // 处理请求的核心方法
    private String process(String request) {
        return request; // 回显逻辑
    }

    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}

2. UDP Echo Client

java 复制代码
public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;

    // 构造方法:指定服务器IP和端口
    public UdpEchoClient(String ip, int port) throws SocketException {
        serverIp = ip;
        serverPort = port;
        socket = new DatagramSocket(); // 客户端绑定随机端口
    }

    // 启动客户端
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("客户端启动!");
        while (true) {
            // 1. 从控制台读取用户输入
            System.out.print("-> ");
            String request = scanner.next();

            // 2. 发送请求到服务器
            DatagramPacket requestPacket = new DatagramPacket(
                    request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIp), serverPort
            );
            socket.send(requestPacket);

            // 3. 接收服务器响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);

            // 4. 解析并打印响应
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println(response);
        }
    }

    public static void main(String[] args) throws IOException {
        // 连接本地服务器,端口9090
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

3. UDP Dict Server(英译汉服务器)

java 复制代码
public class UdpDictServer extends UdpEchoServer {
    private Map<String, String> dict = new HashMap<>();

    // 构造方法:初始化词典数据
    public UdpDictServer(int port) throws SocketException {
        super(port);
        dict.put("cat", "小猫");
        dict.put("dog", "小狗");
        dict.put("fuck", "卧槽");
        // 可添加更多单词映射
    }

    // 重写process方法,实现翻译逻辑
    @Override
    public String process(String request) {
        return dict.getOrDefault(request, "该词没有查询到!");
    }

    public static void main(String[] args) throws IOException {
        UdpDictServer server = new UdpDictServer(9090);
        server.start(); // 复用父类的start方法
    }
}
相关推荐
情缘晓梦.17 小时前
C语言数据存储
c语言·开发语言
xunyan623417 小时前
第九章 JAVA常用类
java·开发语言
IOT-Power17 小时前
QT 对话框(QDialog)中 accept、reject、exec、open的使用
开发语言·qt
froginwe1117 小时前
ASP Session
开发语言
China_Yanhy17 小时前
AWS S3 深度配置指南:每一栏每个选项有什么作用
java·数据库·aws
lbb 小魔仙17 小时前
【Python】零基础学 Python 爬虫:从原理到反爬,构建企业级爬虫系统
开发语言·爬虫·python
Swift社区17 小时前
ArkTS Web 组件里,如何通过 javaScriptProxy 让 JS 同步调用原生方法
开发语言·前端·javascript
Q741_14717 小时前
海致星图招聘 数据库内核研发实习生 一轮笔试 总结复盘(1) 作答语言:C/C++ 链表 二叉树
开发语言·c++·经验分享·面试·笔试
秃了也弱了。18 小时前
FASTJSON库:阿里出品java界json解析库,使用与踩坑记录
java·开发语言·json