通过 TFTP 传输的文件与主机上的文件大小不同

File transferred via TFTP has different size than on host

很长一段时间以来,我一直在努力处理我的 Android 应用程序中的 TFTP 协议。它的主要功能是从托管 TFTP 服务器的自定义设计设备下载文件。

我正在浏览互联网,希望找到一些好的、已经编写好的实现。首先,我尝试使用作为 Apache Commons 一部分的 TFTP 库。不幸的是,运气不好 - 不断超时甚至完全冻结。经过进一步研究,我在 github - please take a look 上找到了一些代码。我已经采用了 Android 的代码,经过一些调整后我终于收到了一些文件。

设备的创建者声明,块大小应恰好为 1015 字节。所以我将包大小增加到 1015 并更新了创建读取请求包的方法:

DatagramPacket createReadRequestPacket(String strFileName) {
    byte[] filename = strFileName.getBytes();
    byte[] mode = currentMode.getBytes();
    int len = rOpCode.length + filename.length + mode.length + 2;
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream(len);
    try {
        outputStream.write(rOpCode);
        outputStream.write(filename);
        byte term = 0;
        outputStream.write(term);
        outputStream.write(mode); // "octet"
        outputStream.write(term);
        outputStream.write("blksize".getBytes());
        outputStream.write(term);
        outputStream.write("1015".getBytes());
        outputStream.write(term);
    } catch (IOException e) {
        e.printStackTrace();
    }

    byte[] readPacketArray = outputStream.toByteArray();
    return new DatagramPacket(readPacketArray, readPacketArray.length, serverAddr, port);
}

正在下载块,但有一个主要问题 - 我正在下载的文件是分段的,每个 512kB(最后一个除外),我在 Android 设备上收到的每个部分大约为 0,大 5kB。似乎每次都多了一个字节或多了一个整体。显然我不完全理解它并且我错过了一些东西。

这是我接收文件的方法:

    byte previousBlockNumber = (byte) -1;
    try {
        PktFactory pktFactory;
        DatagramSocket clientSocket;
        byte[] buf;
        DatagramPacket sendingPkt;
        DatagramPacket receivedPkt;

        System.out.print(ftpHandle);

        if (isConnected) {
            System.out.println("You're already connected to " + hostname.getCanonicalHostName());
        }
        try {
            hostname = InetAddress.getByName(host);
            if (!hostname.isReachable(4000)) {
                System.out.println("Hostname you provided is not responding. Try again.");
                return false;
            }
        } catch (UnknownHostException e) {
            System.out.println("tftp: nodename nor servname provided, or not known");
            return false;
        }
        clientSocket = new DatagramSocket();
        pktFactory = new PktFactory(PKT_LENGTH + 4, hostname, TFTP_PORT);
        System.out.println("Connecting " +
                hostname.getCanonicalHostName() + " at the port number " + TFTP_PORT);
        isConnected = true;
        ftpHandle = "tftp@" + hostname.getCanonicalHostName() + "> ";

        System.out.println("mode " + PktFactory.currentMode);

        if (!isConnected) {
            System.out.println("You must be connected first!");
        }
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        buf = new byte[PKT_LENGTH + 4];

        /* Sending the reading request with the filename to the server. **/
        try {
            /* Sending a RRQ with the filename. **/
            System.out.println("Sending request to server.");
            sendingPkt = pktFactory.createReadRequestPacket(filename);
            clientSocket.setSoTimeout(4500);
            clientSocket.send(sendingPkt);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Connection with server failed");
        }
        boolean receivingMessage = true;

        while (true) {
            try {
                receivedPkt = new DatagramPacket(buf, buf.length);
                clientSocket.setSoTimeout(10000);
                clientSocket.receive(receivedPkt);

                byte[] dPkt = receivedPkt.getData();
                byte[] ropCode = pktFactory.getOpCode(dPkt);

                /* rPkt either a DATA or an ERROR pkt. If an error then print the error message and
                 * terminate the program finish get command. **/
                if (ropCode[1] == 5) {
                    String errorMsg = pktFactory.getErrorMessage(dPkt);
                    System.out.println(errorMsg);
                    return false;
                }

                if (receivedPkt.getLength() < PKT_LENGTH + 4 && ropCode[1] == 3) {
                    byte[] fileDataBytes = pktFactory.getDataBytes(dPkt);
                    outputStream.write(fileDataBytes);
                    if (isListFile) {
                        listBytes = outputStream.toByteArray();
                    } else {
                        FileOutputStream fstream = new FileOutputStream(Constants.EEG_DATA_PATH.concat("file.bin"), true);
                        // Let's get the last data pkt for the current transfering file.
                        fstream.write(outputStream.toByteArray());
                        fstream.close();
                    }

                    // It's time to send the last ACK message before Normal termination.
                    byte[] bNum = pktFactory.getBlockNum(dPkt);
                    DatagramPacket sPkt = pktFactory.createAckPacket(bNum, receivedPkt.getPort());
                    clientSocket.send(sPkt);

                    disconnect();
                    return true;
                }

                if (ropCode[1] == 3) {
                    if (receivingMessage) {
                        System.out.println("Receiving the file now..");
                        receivingMessage = false;
                    }
                    byte[] bNum = pktFactory.getBlockNum(dPkt);

                    //I've added this if and it reduces file size a little (it was more than 0,5kB bigger)
                    if (previousBlockNumber != bNum[1]) {
                        byte[] fileDataBytes = pktFactory.getDataBytes(dPkt);
                        previousBlockNumber = bNum[1];
                        outputStream.write(fileDataBytes);
                    }

                    /* For each received DATA pkt we need to send ACK pkt back. **/
                    DatagramPacket sPkt = pktFactory.createAckPacket(bNum, receivedPkt.getPort());
                    clientSocket.send(sPkt);
                }
            } catch (SocketTimeoutException e) {
                disconnect();
                System.out.println("Server didn't respond and timeout occured.");
                return false;
            }
        }
    } catch (Exception e) {
        System.out.println(e.getMessage());
        return false;
    }

我知道出了什么问题。这种奇怪的行为是收到最后一个数据包时这一行的结果:

byte[] fileDataBytes = pktFactory.getDataBytes(dPkt);

返回的数组大小始终等于指定的数据包长度,即使接收到的数据更小。在我的例子中,最后一个数据包是 0 字节(tftp + 4 字节),但即使这样额外的 512 字节也被添加到输出流中。

为了解决这个问题,我用额外的参数重载了提到的方法 - 当接收到的数据大小高于 4 字节且低于指定的数据包大小(512 字节)时接收到的数据包的实际大小。此更改导致为最后一个数据包获得正确的数组大小,因此接收到的文件在操作结束时具有正确的大小。