Java TCP文件传输仅在第一次尝试时完成

Java TCP文件传输仅在第一次尝试时完成,java,file,sockets,tcp,transfer,Java,File,Sockets,Tcp,Transfer,尽管我花了几个小时研究这个问题,但进展甚微。根据我的教授所说,代码应该是按书面形式运行的 我有一个保持打开的服务器和一个请求文件的客户端。一旦客户端收到文件,客户端将关闭 当我打开服务器时,我能够传输一个完整的.jpg图像文件。然后,客户端关闭,而服务器保持打开状态。我启动另一个客户端并尝试传输相同的映像,但只有一部分字节被传输/写入磁盘。文件传输仅在服务器传输的第一个文件中完全成功 此外,奇怪的是,一个简单的.txt文本文件从未成功传输。我认为原因在于服务器端,因为它与每次重新启动的客户端相反

尽管我花了几个小时研究这个问题,但进展甚微。根据我的教授所说,代码应该是按书面形式运行的

我有一个保持打开的服务器和一个请求文件的客户端。一旦客户端收到文件,客户端将关闭

当我打开服务器时,我能够传输一个完整的.jpg图像文件。然后,客户端关闭,而服务器保持打开状态。我启动另一个客户端并尝试传输相同的映像,但只有一部分字节被传输/写入磁盘。文件传输仅在服务器传输的第一个文件中完全成功

此外,奇怪的是,一个简单的.txt文本文件从未成功传输。我认为原因在于服务器端,因为它与每次重新启动的客户端相反,始终保持打开状态

服务器代码:

import java.io.*;
import java.net.*;
import java.util.Arrays;

class ft_server {

    public static void main(String args[]) throws Exception {

        /*
         * Asks user for port number and listens on that port
         */
        BufferedReader portFromUser = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("Enter the port you'd like to use: ");
        int portNumber = Integer.valueOf(portFromUser.readLine());

        if (portNumber < 1 || portNumber > 65535) {
            System.out.println("Please choose a port number between 1 and 65535.");
            return;
        }
        portFromUser.close();


        ServerSocket listenSocket = new ServerSocket(portNumber);
        /*
         * Finished with user input
         */

        /*
         * Continuously listens for clients:
         */
        while (true) {
            Socket clientSocket = listenSocket.accept();
            BufferedReader inFromClient = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            DataOutputStream outToClient = new DataOutputStream(clientSocket.getOutputStream());

            String clientIP = clientSocket.getRemoteSocketAddress().toString();
            System.out.println("The client " + clientIP + " connected!");

            String clientMessage = inFromClient.readLine();
            System.out.println("The client requested file: " + clientMessage);

            // Get file. If doesn't exist, let's client know.
            // Otherwise informs client of file size.
            File myFile = new File(clientMessage);

            if (!myFile.exists()) {
                outToClient.writeBytes("File does not exist!\n");
                return;
            } else {
                outToClient.writeBytes(String.valueOf((int)myFile.length()) + "\n");
            }

            // Create array for storage of file bytes:
            byte[] byteArray = new byte[(int)myFile.length()];
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(myFile));

            // Read file into array:
            bis.read(byteArray, 0, byteArray.length);

            // Send the file:
            outToClient.write(byteArray, 0, byteArray.length);

            outToClient.close();
            clientSocket.close();
        }
    }
}

缓冲读卡器是否干扰输出流?有没有更好的传输文件的方法?

值得在服务器代码中检查file
read()
调用返回的值,因此:

int bytesRead = bis.read(byteArray, 0, byteArray.length);
System.out.println("File bytes read: " + bytesRead + " from file size: " + myFile.length());
read()
方法没有义务填充byteArray-只返回一些内容并告诉您它读取了多少字节。从中可以看出:

从该输入流读取多达len字节的数据到 字节。如果len不为零,则该方法将阻塞,直到输入为零 可用的;否则,不读取字节,返回0

你需要保持循环阅读。我会这样做(事实上,和你的客户一样!):

或者类似的东西。我也关闭了该文件:它将在GC/finalize时关闭,但这可能需要一段时间,同时您将保持该文件打开

编辑

在本例中,图像读取的具体问题在客户端代码中。读取代码顶部附近的文件大小:

    // Creates InputStream from server to get file size and other messages:
    BufferedReader inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
然后再次访问客户端:

    InputStream is = clientSocket.getInputStream(); // calling clientSocket.getInputStream() twice???
正如你的评论所暗示的,这很糟糕!感谢@EJP突出显示这一点

这会导致缓冲区过度摄取的问题:BufferedReader在其肚子中消耗的字节数多于从中提取的字节数,因此,当您第二次访问clientSocket inputstream时,读取指针已移动。您再也不会看到BufferedReader消耗了什么

一般来说,一旦将缓冲代码插入到某个对象上,就必须小心地只从该缓冲区读取。在这种情况下,这很困难,因为您无法从读取器读取图像(原始二进制)数据,因为它将忙于将二进制值解释为字符,并将其读取为UTF-8或其他内容

即使没有缓冲区,在同一个流上混合读取器(面向文本)和二进制数据(数据流)也是一个小错误。HTTP和电子邮件可以做到这一点,所以你是一个很好的伙伴,但它们通过非常严格的指定而得以逃脱。问题是,无论您是在读Unix“LF”还是Windows“CR/LF”行的结尾,您都很容易被每一端的本地/默认字符编码问题所困扰

在这种情况下,请尝试根本不使用BufferedReader,并一直尝试使用DataInput/Output流。尝试使用
writeUTF
readUTF()
传输字符串数据。理想情况下,按如下方式创建它们:

    DataInputStream inFromServer = new DataInputStream (new BufferedInputStream(clientSocket.getInputStream()));
因此,您仍然可以获得缓冲的好处

编辑2

看到新的客户端代码:

        byteSize = (int) Integer.valueOf(response);
        byte[] byteArray = new byte[byteSize];
        FileOutputStream fos = new FileOutputStream(message);

        int readBytes = inFromServer.read(byteArray);

        // Continuously writes the file to the disk until complete:
        int total = 0;
        for (int i=0; i<byteArray.length; i++) {
            fos.write(byteArray[i]);
            total++;
        }

        fos.close();
一个变体是这样的-相同的东西,但一次字节。可能更清楚一点。这会很慢-所有这些读写操作都会影响操作系统,但是如果在套接字/文件流周围放置一个BufferedInputStream/BufferedOutputStream,就会解决这个问题。我添加了以下内容:

    DataInputStream inFromServer = 
            new DataInputStream(new BufferedInputStream(clientSocket.getInputStream()));
    ...         
        byteSize = (int) Integer.valueOf(response);

        OutputStream fos = new BufferedOutputStream(FileOutputStream(message));
        int total = 0;
        int ch;
        // Continuously writes the file to the disk until complete:
        while (total < byteSize && (ch = inFromServer.read()) != -1) {
            fos.write(ch);
            total ++;
        }
        fos.close();
对!!20世纪90年代,那些善良的人在Javasoft中添加了一个方法,它可以满足您的需求基本上包装了上面的代码。这是最简单的解决方案,也可以说是最正确的方法:“尽可能使用现有库”。OTOH,这是最没有教育意义的,你花在阅读/写作上的时间不会从你的预期寿命中扣除

事实上,
readFully
方法有严重的局限性。试着将它指向1GB文件,看看会发生什么(在您确定了顶部的阵列大小之后):您将a)耗尽内存,b)希望在接收一个巨大的blob时,您至少可以将其假脱机到磁盘。如果您尝试使用2.5G文件,您会注意到其中一些int应该变成long以处理大于等于2^31的数字


如果是我,我会做4K缓冲区。(顺便说一句,我是在一台没有安装Java编译器的笔记本电脑上写这篇文章的,所以我没有实际运行上面的内容!如果有任何困难,一定要响应。)

不要在同一个套接字上混合使用缓冲流和非缓冲流以及读卡器。找到另一种发送文件名的方法,例如使用
DataOutputStream.writeUTF()
,然后使用
DataInputStream.readUTF()
读取。我会尝试一下。但是,混合它们是否有内在的问题?缓冲读取器,呃,缓冲区。如果读取文件名时图像数据已经存在,它会读取一些图像数据。混合流类型有点糟糕,是的。客户端使用
DataOutput.writeBytes
发送文件名,后者发送每个字符的底部字节;然后,服务器使用系统默认的字符编码,通过字节->字符转换器(
InputStreamReader
)使用
BufferedReader.readLine()
读取它。只要您坚持使用普通ASCII,您可能会很好,但如果您使用UTF-8作为默认编码,并且文件名中包含非ASCII字符,则所有文件都会变得非常混乱。@EJP哦,天哪,我看不出您所说的缓冲区问题!一定很晚了。不是每次在服务器循环中都会重新创建所有的BufferedReader吗?客户端每次都会重新启动….?由于某种原因,
bis.read
从未返回-1,因此程序只是在那里暂停。“我只是取消了这个循环,因为它不是必需的。”尼克
        byteSize = (int) Integer.valueOf(response);
        byte[] byteArray = new byte[byteSize];
        FileOutputStream fos = new FileOutputStream(message);

        int readBytes = inFromServer.read(byteArray);

        // Continuously writes the file to the disk until complete:
        int total = 0;
        for (int i=0; i<byteArray.length; i++) {
            fos.write(byteArray[i]);
            total++;
        }

        fos.close();
        byteSize = (int) Integer.valueOf(response);
        byte[] byteArray = new byte[4096];
        FileOutputStream fos = new FileOutputStream(message);
        int total = 0;
        // Continuously writes the file to the disk until complete:
        while (total < byteSize && (readBytes = inFromServer.read(byteArray)) != -1) {
            fos.write(byteArray, 0, readBytes);
            total += readBytes;
        }
        fos.close();
    DataInputStream inFromServer = 
            new DataInputStream(new BufferedInputStream(clientSocket.getInputStream()));
    ...         
        byteSize = (int) Integer.valueOf(response);

        OutputStream fos = new BufferedOutputStream(FileOutputStream(message));
        int total = 0;
        int ch;
        // Continuously writes the file to the disk until complete:
        while (total < byteSize && (ch = inFromServer.read()) != -1) {
            fos.write(ch);
            total ++;
        }
        fos.close();
        int readBytes = inFromServer.readFully(byteArray);