Xử lý phân mảnh và tái hợp tệp tin qua giao thức UDP trên Android

Khi truyền tệp tin qua UDP trên nền tảng Android, việc vượt quá giới hạn kích thước gói (thường ≤ 1500 byte) đòi hỏi chia nhỏ tệp thành nhiều đoạn, đóng gói riêng lẻ và gửi tuần tự. Bên nhận cần thu thập đầy đủ các đoạn theo thứ tự, sau đó nối ghép lại thành tệp gốc — một quy trình yêu cầu cơ chế đánh dấu vị trí, kiểm soát thứ tự và xử lý dữ liệu dư thừa.

Các hằng số và tài nguyên được khai báo như sau:

// Đối tượng truy cập ngẫu nhiên để đọc tệp nguồn
private static RandomAccessFile inputFile;
// Đối tượng truy cập ngẫu nhiên để ghi tệp đích
private static RandomAccessFile outputFile;
// Tổng kích thước tệp (byte)
private static long totalSize;
// Độ dời hiện tại khi đọc
private static int currentOffset;
// Kích thước phần dữ liệu thực tế trong mỗi gói (trừ tiêu đề 5 byte)
private static final int PAYLOAD_SIZE = 1019; // 1024 - 5
// Mảng đệm cố định cho mỗi gói UDP
private static final byte[] packetBuffer = new byte[1024];

Lớp UdpFileProcessor đảm nhiệm hai tác vụ chính: chia tệp (mode = 0) và tái hợp (mode = 1):

public UdpFileProcessor(String filePath, int mode) throws IOException {
    if (mode == 0) {
        // Chế độ chia tệp
        inputFile = new RandomAccessFile(filePath, "r");
        totalSize = inputFile.length();
        int packetCount = (int) Math.ceil((double) totalSize / PAYLOAD_SIZE);
        // Gói cuối có thể nhỏ hơn PAYLOAD_SIZE → tính riêng
        int lastPacketSize = (int) (totalSize % PAYLOAD_SIZE);
        if (lastPacketSize == 0 && totalSize > 0) lastPacketSize = PAYLOAD_SIZE;
        
        generatePackets(packetCount, lastPacketSize);
    } else if (mode == 1) {
        // Chế độ tái hợp
        outputFile = new RandomAccessFile(filePath, "rw");
        reconstructFile();
    }
}

private void generatePackets(int totalPackets, int finalPayloadSize) throws IOException {
    int packetIndex = 0;
    currentOffset = 0;

    // Gửi các gói đầy đủ
    while (currentOffset + PAYLOAD_SIZE <= totalSize) {
        prepareHeader(packetIndex, totalPackets, 2); // Loại gói: FILE_DATA
        inputFile.seek(currentOffset);
        inputFile.read(packetBuffer, 5, PAYLOAD_SIZE);
        sendUdpPacket(packetBuffer);
        currentOffset += PAYLOAD_SIZE;
        packetIndex++;
    }

    // Gói cuối: ghi rõ kích thước thực tế
    prepareHeader(packetIndex, totalPackets, 2);
    // Mã hóa kích thước dư thừa bằng 2 byte (MSB/LSB)
    int remainder = (int) (totalSize - currentOffset);
    packetBuffer[5] = (byte) ((remainder >> 8) & 0xFF);
    packetBuffer[6] = (byte) (remainder & 0xFF);
    inputFile.seek(currentOffset);
    inputFile.read(packetBuffer, 7, remainder);
    sendUdpPacket(packetBuffer);
}

private void prepareHeader(int seqId, int total, int type) {
    packetBuffer[0] = (byte) type;           // Loại gói
    packetBuffer[1] = (byte) ClientConfig.CLIENT_ID;
    packetBuffer[2] = (byte) ClientConfig.SESSION_ID;
    packetBuffer[3] = (byte) total;         // Tổng số gói trong phiên
    packetBuffer[4] = (byte) seqId;         // ID tuần tự
}

private void sendUdpPacket(byte[] data) {
    // Gọi lớp gửi UDP (ví dụ: UdpTransmitter.send(data))
    UdpTransmitter.getInstance().enqueue(data.clone());
}

Tái hợp tệp tại phía nhận dựa trên danh sách các gói đã lưu tạm, được sắp xếp theo seqId:

private void reconstructFile() throws IOException {
    // Sắp xếp các gói theo thứ tự ID
    List<UdpPacket> sortedPackets = new ArrayList<>(ReceivedPackets.getBuffer());
    sortedPackets.sort(Comparator.comparingInt(p -> p.sequenceId));

    int writtenBytes = 0;
    for (UdpPacket pkt : sortedPackets) {
        if (pkt.clientId != ClientConfig.CLIENT_ID || 
            pkt.sessionId != ClientConfig.SESSION_ID) continue;

        int payloadLen;
        if (pkt.sequenceId == sortedPackets.size() - 1) {
            // Gói cuối: sử dụng kích thước thực từ header
            payloadLen = ((pkt.data[5] & 0xFF) << 8) | (pkt.data[6] & 0xFF);
        } else {
            payloadLen = PAYLOAD_SIZE;
        }

        outputFile.write(pkt.data, 7, payloadLen);
        writtenBytes += payloadLen;
    }
    outputFile.setLength(writtenBytes); // Đảm bảo kích thước tệp chính xác
}

Thẻ: Android UDP file-transfer packet-fragmentation Java

Đăng vào ngày 14 tháng 7 lúc 05:28