0

经过近 2 个工作日的谷歌搜索并尝试了我在整个网络上找到的几种不同的可能性,我在这里提出这个问题,希望我最终能得到答案。

首先,这就是我想要做的:

我正在开发一个客户端和一个服务器应用程序,目的是在单个服务器上的多个客户端之间交换大量大文件。客户端使用纯Java (JDK 1.6) 开发,而 Web 应用程序使用Grails (2.0.0) 完成。

由于客户端的目的是允许用户交换大量大文件(通常每个大约 2GB),我必须以某种方式实现它,以便上传可恢复,即用户能够停止和恢复上传随时。

是我到目前为止所做的:

我实际上设法做我想做的事情并将大文件流式传输到服务器,同时仍然能够使用原始套接字暂停和恢复上传。我会向服务器发送一个常规请求(使用 Apache 的 HttpClient 库),让服务器向我发送一个可供我免费使用的端口,然后在服务器上打开一个 ServerSocket 并从客户端连接到该特定套接字。

这是问题所在

实际上,这至少存在两个问题:

  1. 我自己打开这些端口,所以我必须自己管理打开和使用的端口。这是相当容易出错的。
  2. 我实际上绕过了 Grails 管理大量(并发)连接的能力。

最后,这是我现在应该做的事情和问题

由于我上面提到的问题是不可接受的,我现在应该使用 Java 的 URLConnection/HttpURLConnection 类,同时仍然坚持使用 Grails。

连接到服务器并发送简单的请求完全没有问题,一切正常。当我尝试使用流(客户端中的连接的 OutputStream 和服务器中的请求的 InputStream)时,问题就开始了。打开客户端的 OutputStream 并向其写入数据非常简单。但是从请求的 InputStream 中读取对我来说似乎是不可能的,因为它看起来总是空的。

示例代码

这是服务器端(Groovy 控制器)的示例:

def test() {
    InputStream inStream = request.inputStream
    
    if(inStream != null) {
        int read = 0;
        byte[] buffer = new byte[4096];
        long total = 0;
        
        println "Start reading"
        
        while((read = inStream.read(buffer)) != -1) {
            println "Read " + read + " bytes from input stream buffer"      //<-- this is NEVER called
        }       
        
        println "Reading finished"
        println "Read a total of " + total + " bytes"   // <-- 'total' will always be 0 (zero)
    } else {
        println "Input Stream is null"      // <-- This is NEVER called
    }
}

这是我在客户端(Java 类)所做的:

public void connect() {
    final URL url = new URL("myserveraddress");
    final byte[] message = "someMessage".getBytes();        // Any byte[] - will be a file one day
    HttpURLConnection connection = url.openConnection();
    connection.setRequestMethod("GET");                     // other methods - same result
    
    // Write message 
    DataOutputStream out = new DataOutputStream(connection.getOutputStream());
    out.writeBytes(message);
    out.flush();
    out.close();
    
    // Actually connect
    connection.connect();                                   // is this placed correctly?
    
    // Get response
    BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
    
    String line = null;
    while((line = in.readLine()) != null) {
        System.out.println(line);                   // Prints the whole server response as expected
    }
    
    in.close();
}

正如我所提到的,问题在于它request.inputStream总是产生一个空的 InputStream,所以我永远无法从中读取任何内容(当然)。但这正是我想要做的(所以我可以流式传输要上传到服务器的文件,从 InputStream 中读取并将其保存到文件中),这相当令人失望。

我尝试了不同的 HTTP 方法不同的数据负载,还一遍遍地重新排列代码,但似乎无法解决问题。

我希望找到什么

当然,我希望找到解决我的问题的方法。任何东西都会受到高度赞赏:提示、代码片段、库建议等等。也许我什至都错了,需要朝着完全不同的方向前进。

那么,如何在不手动打开服务器端端口的情况下实现从 Java 客户端到 Grails Web 应用程序的相当大(二进制)文件的可恢复文件上传?

4

2 回答 2

0

HTTP GET 方法具有用于范围检索的特殊标头:http ://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35大多数下载者使用它从服务器进行可恢复下载。

据我了解,没有将此标头用于 POST/PUT 请求的标准做法,但这取决于您,对吗?您可以制作非常标准的 Grails 控制器,它将接受标准的 http 上传,标头如Range: bytes=500-999. 控制器应该将这 500 个从客户端上传的字节放入文件中,从位置 500 开始

在这种情况下,您不需要打开任何套接字,并制定自己的协议等。

PS 500 字节只是一个示例,可能您使用的是更大的部分。

于 2012-07-06T15:36:31.987 回答
0

客户端Java编程:

    public class NonFormFileUploader {
    static final String UPLOAD_URL= "http://localhost:8080/v2/mobileApp/fileUploadForEOL";


    static final int BUFFER_SIZE = 4096;

    public static void main(String[] args) throws IOException {
        // takes file path from first program's argument
        String filePath = "G:/study/GettingStartedwithGrailsFinalInfoQ.pdf";
        File uploadFile = new File(filePath);

        System.out.println("File to upload: " + filePath);

        // creates a HTTP connection
        URL url = new URL(UPLOAD_URL);
        HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
        httpConn.setDoOutput(true);
        httpConn.setRequestMethod("POST");
        // sets file name as a HTTP header
        httpConn.setRequestProperty("fileName", uploadFile.getName());

        // opens output stream of the HTTP connection for writing data
        OutputStream outputStream = httpConn.getOutputStream();

        // Opens input stream of the file for reading data
        FileInputStream inputStream = new FileInputStream(uploadFile);

        byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead = -1;

        while ((bytesRead = inputStream.read(buffer)) != -1) {
            System.out.println("bytesRead:"+bytesRead);
            outputStream.write(buffer, 0, bytesRead);
            outputStream.flush();
        }

        System.out.println("Data was written.");
        outputStream.flush();
        outputStream.close();
        inputStream.close();

        int responseCode = httpConn.getResponseCode();
        if (responseCode == HttpURLConnection.HTTP_OK) {
            // reads server's response
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    httpConn.getInputStream()));
            String response = reader.readLine();
            System.out.println("Server's response: " + response);
        } else {
            System.out.println("Server returned non-OK code: " + responseCode);
        }
    }
 }

服务器端 Grails 程序:

控制器内部:

def fileUploadForEOL(){
    def result
    try{
        result = mobileAppService.fileUploadForEOL(request);
    }catch(Exception e){
        log.error "Exception in fileUploadForEOL service",e
    }
    render result as JSON
}

服务类内部:

    def fileUploadForEOL(request){
    def status = false;
    int code = 500
    def map = [:]
    try{
    String fileName = request.getHeader("fileName");
    File saveFile = new File(SAVE_DIR + fileName);

    System.out.println("===== Begin headers =====");
    Enumeration<String> names = request.getHeaderNames();
    while (names.hasMoreElements()) {
        String headerName = names.nextElement();
        System.out.println(headerName + " = " + request.getHeader(headerName));        
    }
    System.out.println("===== End headers =====\n");

    // opens input stream of the request for reading data
    InputStream inputStream = request.getInputStream();

    // opens an output stream for writing file
    FileOutputStream outputStream = new FileOutputStream(saveFile);

    byte[] buffer = new byte[BUFFER_SIZE];
    int bytesRead = inputStream.read(buffer);

    long count =  bytesRead

    while(bytesRead != -1) {
        outputStream.write(buffer, 0, bytesRead);
      bytesRead = inputStream.read(buffer);
      count += bytesRead
    }
     println "count:"+count
    System.out.println("Data received.");
    outputStream.close();
    inputStream.close();

    System.out.println("File written to: " + saveFile.getAbsolutePath());

    code = 200

    }catch(Exception e){
        mLogger.log(java.util.logging.Level.SEVERE,"Exception in fileUploadForEOL",e);
    }finally{
        map <<["code":code]
    }
    return map
}

我已经尝试使用上面的代码,它对我有用(仅适用于文件大小 3 到 4MB,但对于小文件,一些字节的代码丢失或什至没有出现,但在请求标头内容长度即将到来,不知道为什么会发生.)

于 2015-11-19T13:09:39.287 回答