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
}