You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

可靠UDP文件传输收发端异常:末尾数据包缺失修复方案

可靠UDP文件传输最后一包数据缺失问题解决

问题原因

  1. 固定缓冲区未处理实际读取长度:原代码通常按固定1KB缓冲区读取文件,最后一次读取的有效字节数往往小于1024,但发送时仍传递整个缓冲区或按固定长度发送,导致接收端无法识别有效数据的实际边界,最终丢失部分数据。
  2. 缺少结束标记与可靠确认:发送端发送最后一包后未传递明确的结束信号,接收端可能持续等待后续数据包;或发送端未等待接收端对最后一包的确认就关闭连接,导致最后一包未被正确接收写入。

修改后的Sender2代码

import java.io.FileInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class Sender2 {
    private static final int BUFFER_SIZE = 1024;
    private static final int PORT = 8888;

    public static void main(String[] args) {
        try (DatagramSocket socket = new DatagramSocket();
             FileInputStream fis = new FileInputStream("source.txt")) {

            InetAddress receiverAddr = InetAddress.getByName("localhost");
            byte[] buffer = new byte[BUFFER_SIZE];
            int readLen;
            int seq = 0;

            // 循环发送文件数据包
            while ((readLen = fis.read(buffer)) != -1) {
                // 数据包格式:[4字节序号][4字节数据长度][实际有效数据]
                byte[] sendData = new byte[8 + readLen];
                // 写入序号
                sendData[0] = (byte) (seq >> 24);
                sendData[1] = (byte) (seq >> 16);
                sendData[2] = (byte) (seq >> 8);
                sendData[3] = (byte) seq;
                // 写入实际数据长度
                sendData[4] = (byte) (readLen >> 24);
                sendData[5] = (byte) (readLen >> 16);
                sendData[6] = (byte) (readLen >> 8);
                sendData[7] = (byte) readLen;
                // 复制有效数据
                System.arraycopy(buffer, 0, sendData, 8, readLen);

                DatagramPacket packet = new DatagramPacket(sendData, sendData.length, receiverAddr, PORT);
                socket.send(packet);

                // 等待接收端确认,不匹配则重发
                byte[] ackBuffer = new byte[4];
                DatagramPacket ackPacket = new DatagramPacket(ackBuffer, ackBuffer.length);
                socket.receive(ackPacket);
                int ackSeq = ((ackBuffer[0] & 0xFF) << 24) | ((ackBuffer[1] & 0xFF) << 16) | ((ackBuffer[2] & 0xFF) << 8) | (ackBuffer[3] & 0xFF);
                if (ackSeq != seq) {
                    fis.skip(-readLen); // 回退文件指针,重新读取当前包
                    continue;
                }
                seq++;
            }

            // 发送结束标记包:序号为0xFFFFFFFF,数据长度为0
            byte[] endData = new byte[8];
            endData[0] = (byte) 0xFF;
            endData[1] = (byte) 0xFF;
            endData[2] = (byte) 0xFF;
            endData[3] = (byte) 0xFF;
            DatagramPacket endPacket = new DatagramPacket(endData, endData.length, receiverAddr, PORT);
            socket.send(endPacket);

            // 确认结束包被接收
            byte[] endAckBuffer = new byte[4];
            DatagramPacket endAckPacket = new DatagramPacket(endAckBuffer, endAckBuffer.length);
            socket.receive(endAckPacket);
            int endAckSeq = ((endAckBuffer[0] & 0xFF) << 24) | ((endAckBuffer[1] & 0xFF) << 16) | ((endAckBuffer[2] & 0xFF) << 8) | (endAckBuffer[3] & 0xFF);
            while (endAckSeq != 0xFFFFFFFF) {
                socket.send(endPacket);
                socket.receive(endAckPacket);
                endAckSeq = ((endAckBuffer[0] & 0xFF) << 24) | ((endAckBuffer[1] & 0xFF) << 16) | ((endAckBuffer[2] & 0xFF) << 8) | (endAckBuffer[3] & 0xFF);
            }

            System.out.println("文件发送完成");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

修改后的Receiver2代码

import java.io.FileOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class Receiver2 {
    private static final int BUFFER_SIZE = 1024 + 8; // 预留8字节的序号和长度头部
    private static final int PORT = 8888;

    public static void main(String[] args) {
        try (DatagramSocket socket = new DatagramSocket(PORT);
             FileOutputStream fos = new FileOutputStream("dest.txt")) {

            byte[] buffer = new byte[BUFFER_SIZE];
            int expectedSeq = 0;

            while (true) {
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                socket.receive(packet);

                // 解析数据包头部
                int seq = ((buffer[0] & 0xFF) << 24) | ((buffer[1] & 0xFF) << 16) | ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF);
                int dataLen = ((buffer[4] & 0xFF) << 24) | ((buffer[5] & 0xFF) << 16) | ((buffer[6] & 0xFF) << 8) | (buffer[7] & 0xFF);

                // 识别结束标记包
                if (seq == 0xFFFFFFFF && dataLen == 0) {
                    byte[] ackData = new byte[4];
                    ackData[0] = (byte) 0xFF;
                    ackData[1] = (byte) 0xFF;
                    ackData[2] = (byte) 0xFF;
                    ackData[3] = (byte) 0xFF;
                    DatagramPacket ackPacket = new DatagramPacket(ackData, ackData.length, packet.getAddress(), packet.getPort());
                    socket.send(ackPacket);
                    break;
                }

                // 处理有序数据包,写入有效长度数据
                if (seq == expectedSeq) {
                    fos.write(buffer, 8, dataLen);
                    fos.flush();

                    // 发送序号确认
                    byte[] ackData = new byte[4];
                    ackData[0] = (byte) (seq >> 24);
                    ackData[1] = (byte) (seq >> 16);
                    ackData[2] = (byte) (seq >> 8);
                    ackData[3] = (byte) seq;
                    DatagramPacket ackPacket = new DatagramPacket(ackData, ackData.length, packet.getAddress(), packet.getPort());
                    socket.send(ackPacket);

                    expectedSeq++;
                } else {
                    // 发送上一个正确序号的确认,要求重发
                    byte[] ackData = new byte[4];
                    ackData[0] = (byte) ((expectedSeq - 1) >> 24);
                    ackData[1] = (byte) ((expectedSeq - 1) >> 16);
                    ackData[2] = (byte) ((expectedSeq - 1) >> 8);
                    ackData[3] = (byte) (expectedSeq - 1);
                    DatagramPacket ackPacket = new DatagramPacket(ackData, ackData.length, packet.getAddress(), packet.getPort());
                    socket.send(ackPacket);
                }
            }

            System.out.println("文件接收完成");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

代码修改说明

  1. 明确数据包格式:新增4字节序号+4字节数据长度的固定头部,接收端可精准提取每包的有效数据长度,彻底解决最后一包的边界识别问题。
  2. 结束标记机制:发送端在文件传输完成后发送特殊结束包,接收端识别后立即停止接收,避免无限等待或遗漏最后一包。
  3. 可靠确认重发:每包数据发送后等待接收端的序号确认,若确认不匹配则回退文件指针重发,保证所有数据包(包括最后一包)都能被正确接收。

内容的提问来源于stack exchange,提问作者yyfcoder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 07:32:20