通过 Java 套接字传输大文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17285846/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-01 01:35:14  来源:igfitidea点击:

Large file transfer over java socket

javaandroidsocketslarge-files

提问by Vinit Shandilya

I have written a small client-server code for transferring small file. It uses Data output stream and readFully() method of data input stream. This code does not work for larger files for obvious reasons. I was thinking of fragmenting large files into smaller chunks of 1Kb each before sending them to client. But I can't think of any solution (like how to write multiple chunks on data output stream with correct offset and how to reassemble them at receiving end. Can anyone provide a workaround? It would be very helpful if you could modify my code:

我写了一个小的客户端 - 服务器代码来传输小文件。它使用数据输出流和数据输入流的 readFully() 方法。由于显而易见的原因,此代码不适用于较大的文件。在将大文件发送给客户端之前,我正在考虑将大文件分成每个 1Kb 的小块。但我想不出任何解决方案(例如如何在具有正确偏移量的数据输出流上写入多个块以及如何在接收端重新组装它们。谁能提供解决方法?如果您能修改我的代码,那将非常有帮助:

Sender (Server):

发件人(服务器):

public void sendFileDOS() throws FileNotFoundException {
    runOnUiThread( new Runnable() {
          @Override
          public void run() {
              registerLog("Sending. . . Please wait. . .");
          }
        });
    final long startTime = System.currentTimeMillis();
    final File myFile= new File(filePath); //sdcard/DCIM.JPG
    byte[] mybytearray = new byte[(int) myFile.length()];
    FileInputStream fis = new FileInputStream(myFile);  
    BufferedInputStream bis = new BufferedInputStream(fis);
    DataInputStream dis = new DataInputStream(bis);
    try {
        dis.readFully(mybytearray, 0, mybytearray.length);
        OutputStream os = socket.getOutputStream();
        //Sending file name and file size to the client  
        DataOutputStream dos = new DataOutputStream(os);     
        dos.writeUTF(myFile.getName());     
        dos.writeLong(mybytearray.length);     
        int i = 0;
        final ProgressBar myProgBar=(ProgressBar)findViewById(R.id.progress_bar);
        while (i<100) {
            dos.write(mybytearray, i*(mybytearray.length/100), mybytearray.length/100);
            final int c=i;
            runOnUiThread( new Runnable() {
                  @Override
                  public void run() {
                      myProgBar.setVisibility(View.VISIBLE);
                      registerLog("Completed: "+c+"%");
                      myProgBar.setProgress(c);
                      if (c==99)
                          myProgBar.setVisibility(View.INVISIBLE);
                  }
                });
            i++;
        }    
        dos.flush();

    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    runOnUiThread( new Runnable() {
          @Override
          public void run() {
              long estimatedTime = (System.currentTimeMillis() - startTime)/1000;
              registerLog("File successfully sent");
              registerLog("File size: "+myFile.length()/1000+" KBytes");
              registerLog("Elapsed time: "+estimatedTime+" sec. (approx)");
              registerLog("Server stopped. Please restart for another session.");
              final Button startServerButton=(Button)findViewById(R.id.button1);
              startServerButton.setText("Restart file server");
          }
        });
}

Receiver (Client):

接收器(客户端):

public class myFileClient {
final static String servAdd="10.141.21.145";
static String filename=null;
static Socket socket = null;
static Boolean flag=true;

/**
 * @param args
 */
public static void main(String[] args) throws IOException {
    // TODO Auto-generated method stub
    initializeClient();
    receiveDOS();      
}
public static void initializeClient () throws IOException {
    InetAddress serverIP=InetAddress.getByName(servAdd);
    socket=new Socket(serverIP, 4444);
}
public static void receiveDOS() {
    int bytesRead;
    InputStream in;
    int bufferSize=0;

    try {
        bufferSize=socket.getReceiveBufferSize();
        in=socket.getInputStream();
        DataInputStream clientData = new DataInputStream(in);
        String fileName = clientData.readUTF();
        System.out.println(fileName);
        OutputStream output = new FileOutputStream("//home//evinish//Documents//Android//Received files//"+ fileName);
        long size = clientData.readLong();
        byte[] buffer = new byte[bufferSize];
        while (size > 0
                && (bytesRead = clientData.read(buffer, 0,
                        (int) Math.min(buffer.length, size))) != -1) {
            output.write(buffer, 0, bytesRead);
            size -= bytesRead;
        }

    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

Please help! Thanks in advance! :)

请帮忙!提前致谢!:)

回答by user207421

You're right, this is a poor way to do it. It wastes both memory and time; it assumes the file size is 32 bits; it assumes the entire file fits into memory; it assumes the entire file is read in one read; and it doesn't send anything until the entire file has been read.

你是对的,这是一种糟糕的方式。它既浪费内存又浪费时间;它假定文件大小为 32 位;它假设整个文件都适合内存;它假设在一次读取中读取整个文件;并且在读取整个文件之前它不会发送任何内容。

The canonical way to copy a stream in Java is this:

在 Java 中复制流的规范方法是:

while ((count = in.read(buffer)) > 0)
{
  out.write(buffer, 0, count);
}

It will work with any size buffer you like and therefore with any size file you can come up with. Use the same code at both ends, although you don't have to use the same size buffer at both ends. As you're copying over a network you might think that 1k or 1.5k is the best size, but that overlooks the presence of the socket send and receive buffers in the kernel. When you take them into account it is probably better to use 8k or more.

它适用于您喜欢的任何大小的缓冲区,因此适用于您可以想出的任何大小的文件。在两端使用相同的代码,尽管您不必在两端使用相同大小的缓冲区。当您通过网络进行复制时,您可能认为 1k 或 1.5k 是最佳大小,但这忽略了内核中套接字发送和接收缓冲区的存在。考虑到它们时,最好使用 8k 或更多。

回答by Vinit Shandilya

I finally solved the problem. Here is my modified source code for server and client. Hope this would help other people too! :) Server Side code snippet (sender):

我终于解决了这个问题。这是我修改后的服务器和客户端源代码。希望这也能帮助其他人!:) 服务器端代码片段(发送方):

final File myFile= new File(filePath); //sdcard/DCIM.JPG
    byte[] mybytearray = new byte[8192];
    FileInputStream fis = new FileInputStream(myFile);  
    BufferedInputStream bis = new BufferedInputStream(fis);
    DataInputStream dis = new DataInputStream(bis);
    OutputStream os;
    try {
        os = socket.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeUTF(myFile.getName());     
        dos.writeLong(mybytearray.length);
        int read;
        while((read = dis.read(mybytearray)) != -1){
            dos.write(mybytearray, 0, read);
        }

    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

Client side code snippet (Receiver):

客户端代码片段(接收器):

int bytesRead;
    InputStream in;
    int bufferSize=0;

    try {
        bufferSize=socket.getReceiveBufferSize();
        in=socket.getInputStream();
        DataInputStream clientData = new DataInputStream(in);
        String fileName = clientData.readUTF();
        System.out.println(fileName);
        OutputStream output = new FileOutputStream("//home//evinish//Documents//Android//Received files//"+ fileName);
        byte[] buffer = new byte[bufferSize];
        int read;
        while((read = clientData.read(buffer)) != -1){
            output.write(buffer, 0, read);
        }

    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

A bit faster way of writing to output stream:

写入输出流的更快方式:

long acc=0;
long N=myFile.length();
 while(acc<N){
            noofbytes=dis.read(mybytearray, 0, 16384);
            dos.write(mybytearray, 0, noofbytes);
            acc=acc+noofbytes; } dos.flush();

I saved around 7 seconds while transferring a video file of 72MB.

我在传输 72MB 的视频文件时节省了大约 7 秒。