4

假设我有一个 java 程序,它使用 HTTP 1.1 在服务器上发出 HTTP 请求并且不关闭连接。我提出一个请求,并读取从绑定到套接字的输入流返回的所有数据。但是,在发出第二个请求时,我没有得到服务器的响应(或者流有问题 - 它不再提供任何输入)。如果我按顺序发出请求(请求,请求,读取)它工作正常,但(请求,读取,请求,读取)没有。

有人可以对为什么会发生这种情况有所了解吗?(代码片段如下)。无论我做什么,第二个读取循环的 isr_reader.read() 只返回-1。

try{
        connection = new Socket("SomeServer", port);
        con_out = connection.getOutputStream();
        con_in  = connection.getInputStream();
        PrintWriter out_writer = new PrintWriter(con_out, false);
        out_writer.print("GET http://somesite HTTP/1.1\r\n");
        out_writer.print("Host: thehost\r\n");
        //out_writer.print("Content-Length: 0\r\n");
        out_writer.print("\r\n");
        out_writer.flush();

        // If we were not interpreting this data as a character stream, we might need to adjust byte ordering here.
        InputStreamReader isr_reader = new InputStreamReader(con_in);
        char[] streamBuf = new char[8192];
        int amountRead;
        StringBuilder receivedData = new StringBuilder();
        while((amountRead = isr_reader.read(streamBuf)) > 0){
            receivedData.append(streamBuf, 0, amountRead);
        }

// Response is processed here.

        if(connection != null && !connection.isClosed()){
            //System.out.println("Connection Still Open...");

        out_writer.print("GET http://someSite2\r\n");
        out_writer.print("Host: somehost\r\n");
        out_writer.print("Connection: close\r\n");
        out_writer.print("\r\n");
        out_writer.flush();

        streamBuf = new char[8192];
        amountRead = 0;
        receivedData.setLength(0);
        while((amountRead = isr_reader.read(streamBuf)) > 0 || amountRead < 1){
            if (amountRead > 0)
                receivedData.append(streamBuf, 0, amountRead);
        }
}
        // Process response here
    }

对问题的回答:是的,我收到了来自服务器的分块响应。由于外部限制,我正在使用原始套接字。

为代码混乱道歉 - 我正在从内存中重写它,似乎引入了一些错误。

所以共识是我必须要么做(请求,请求,读取)并让服务器在我结束时关闭流,或者,如果我做(请求,读取,请求,读取)在我结束之前停止流,以便流不会关闭。

4

5 回答 5

5

根据您的代码,您甚至会到达处理发送第二个请求的语句的唯一时间是服务器在接收/响应第一个请求后关闭输出流(您的输入流)。

原因是您的代码应该只读取第一个响应

while((amountRead = isr_reader.read(streamBuf)) > 0) {
  receivedData.append(streamBuf, 0, amountRead);
}

将阻塞直到服务器关闭输出流(即read返回时-1)或直到套接字上的读取超时过去。在读取超时的情况下,将引发异常,您甚至无法发送第二个请求。

HTTP 响应的问题在于,在响应结束之前,它们不会告诉您要从流中读取多少字节。这对于 HTTP 1.0 响应来说没什么大不了的,因为服务器只是在响应之后关闭连接,从而使您能够通过简单地读取所有内容直到流结束来获取响应(状态行 + 标头 + 正文)。

使用 HTTP 1.1 持久连接,您不能再简单地读取所有内容,直到流结束。您首先需要逐行读取状态行和标头,然后根据状态代码和标头(例如 Content-Length)决定读取多少字节以获得响应正文(如果它存在于全部)。如果您正确执行上述操作,您的读取操作将在连接关闭或发生超时之前完成,并且您将准确读取服务器发送的响应。这将使您能够发送下一个请求,然后以与第一个请求完全相同的方式读取第二个响应。

PS Request, request, read 可能正在“工作”,因为您的服务器支持请求管道,因此接收和处理这两个请求,因此,您将两个响应读入一个缓冲区作为您的“第一个”响应。

PPS 确保您PrintWriter使用的是US-ASCII编码。否则,根据您的系统编码,您的 HTTP 请求的请求行和标头可能格式不正确(编码错误)。

于 2008-10-08T23:04:29.683 回答
3

编写一个简单的符合 RFC 的 http/1.1 客户端并不是一项艰巨的任务。要解决在java中读取套接字时阻塞i/o访问的问题,您必须使用java.nio类。SocketChannels 提供了执行非阻塞 i/o 访问的可能性。

这是在持久连接上发送 HTTP 请求所必需的。

此外,nio 类将提供更好的性能。

我的压力测试给出以下结果:

  • HTTP/1.0 (java.io) -> HTTP/1.0 (java.nio) = +20% 更快

  • HTTP/1.0 (java.io) -> HTTP/1.1 (java.nio 与持久连接) = +110% 更快

于 2010-07-21T15:29:17.243 回答
0

确保您Connection: keep-alive的请求中有一个。不过,这可能是一个有争议的问题。

服务器返回什么样的响应?您是否使用分块传输?如果服务器不知道响应正文的大小,它就不能提供Content-Length标头,并且必须在响应正文结束时关闭连接以向客户端指示内容已经结束。在这种情况下,keep-alive 将不起作用。如果您使用 PHP、JSP 等即时生成内容,您可以启用输出缓冲、检查累积正文的大小、推送Content-Length标题并刷新输出缓冲区。

于 2008-10-08T15:45:43.860 回答
0

您使用原始套接字而不是 Java 的 URL Connection 或Commons HTTPClient是否有特殊原因?

HTTP 不容易做到正确。我知道 Commons HTTP Client 可以像您尝试做的那样重用连接。

如果您没有使用 Sockets 的具体原因,这就是我的建议:)

于 2008-10-08T15:55:05.620 回答
0

编写自己的正确客户端 HTTP/1.1 实现并非易事;从历史上看,我见过的大多数尝试它的人都弄错了。他们的实现通常会忽略规范,只执行似乎与特定测试服务器一起工作的事情 - 特别是,他们通常忽略能够处理分块响应的要求。

编写自己的 HTTP 客户端可能是个坏主意,除非您有一些非常奇怪的要求。

于 2008-10-12T08:21:01.373 回答