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

标签 java android tftp

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

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

该设备的创建者表示, block 大小应该正好是 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);
}

正在下载 block ,但有一个主要问题 - 我下载的文件是分成几部分的,每个部分 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 字节)时,接收到的数据包的实际大小。此更改导致获得最后一个数据包的数组的正确大小,因此接收到的文件在操作结束时具有正确的大小。

关于java - 通过 TFTP 传输的文件与主机上传输的文件大小不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58606025/

相关文章:

java - 使用JAVA从TFTP服务器获取文件大小,无需下载文件

c++ - 错误代码 1024 tftp 服务器

Android InAppBilling - 如何检查产品是否是从其他 Activity 购买的?

android 获取组中选中的复选框

ubuntu - tftp options put 选项不上传文件

java - 将序列化对象写入文件

java - Android不同屏幕下的ImageButton

java - 从 C++ 到 java 的 Android JNI 回调

java - 如何改进返回给定 int 数组的具有排序索引的 int 数组的代码?

java - JSF 不是 Richfaces 的 a4j 的有效方法表达式 :ajax listener