使用Java I/O读取HTTP网络流

4

我现在正在处理与Java I / O相关的性能改进问题。关于使用Java I / O通过网络读取/写入流,我有一些疑问,如下所述。我的脑海中出现了许多不同的观点,但我想要澄清它们。

代码

URL url = new URL("http://example.com/connector/url2Service");  

URLConnection urlConnection = url.openConnection(); // Position 1

HttpURLConnection httpURLConnection = (HttpURLConnection)urlConnection;

String requestStr = buildRequestString();// Position 2

ByteArrayOutputStream rqByteArrayOutputStream = new ByteArrayOutputStream();
rqByteArrayOutputStream.write(((String)requestStr).getBytes()); // Position 3

httpURLConnection.setDoOutput(true);
httpURLConnection.setUseCaches(false);
httpURLConnection.setDoInput(true);
httpURLConnection.setRequestMethod("POST");

rqByteArrayOutputStream.writeTo(httpURLConnection.getOutputStream()); // Position 4

// Waiting for the response.

InputStream inputStream = httpURLConnection.getInputStream(); // Position 5

ByteArrayOutputStream rsByteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int length = 0;

while ((length = inputStream.read(buffer)) != -1) { // Position 6
    rsByteArrayOutputStream.write(buffer, 0, length);// Position 7
}

String response  = new String(rsByteArrayOutputStream.toByteArray());// Position 8

我的理解

  • 位置1:这将提供一个对象与远程资源通信。但是连接尚未建立。
  • 位置2:构建并获取请求。
  • 位置3:将字节写入ByteArrayOutputStream。
  • 位置4:这是与服务器通信开始的地方。我们正在编写字节。因此,服务器可以开始读取它们。当执行退出此行时,我们已完成发送请求对象。
  • 位置5:当我们退出此行时,服务器已完成发送响应对象。因此,我们可以开始读取响应对象。
  • 位置6:以4096字节块的形式读取对象。
  • 位置7:将读取的字节写入ByteArrayOutputStream。
  • 位置8:完成读取响应并将其转换为字符串。

我的问题

  1. 我们何时可以说请求上传已完成?(我相信当执行退出位置4时,这已经完成了)
  2. 我们何时可以说响应下载已完成?(我对位置5和8之间存在疑虑)
  3. 当我们退出位置5时,这是否意味着响应已完全下载,还是刚刚开始下载?
  4. 到哪个位置网络(带宽)会影响性能?(位置5、6、7...)
  5. 现在我正在调整InputStream读取代码。如果您有任何建议,请分享?

参考资料:

3个回答

3

我们在什么时候可以说上传请求完成了?(我相信当执行离开位置4时,它就已经完成了)

是的,但你已经浪费了一些时间用于ByteArrayOutputStream。直接将请求写入连接输出流即可。节省内存和延迟。

我们在什么时候可以说响应下载已经完成?(我在第5点和第8点之间有疑问)

当你从read()方法中收到-1时。

当我们退出第5点时,这意味着响应已经完全下载了还是刚刚开始下载了?

都不是。请求已经被写入,而且你还没有开始加载任何内容,因此没有任何内容被下载。它可能已经开始到达套接字接收缓冲区,但你看不到。

网络带宽会影响到哪个程度的性能?(第5点、第6点、第7点...)

以上都不是。只有在收到-1之前才会使用网络带宽。

我正在调整InputStream读取代码。如果你有任何建议,请分享吧?

使用更大的缓冲区。除此之外没有什么其他的可做的了。


谢谢你的建议。但是你认为更大的缓冲区总是有助于提高性能吗? - ironwood
2
我不仅是认为,而是知道。几年前,我进行了一项大型实验,使用了1700个数据点,其中我变化了每个可能的API和TCP参数。缓冲区大小是传输速度中最重要的因素。 - user207421

2
JDK Http相关类基于旧的java.io实现。请查看OpenJDK HttpURLConnection的代码。(关于基于Oracle JDK 7的OpenJDK根源的详细信息,请参见此处)。使用基于操作系统原语直接构建的新的java.nio实现可以为工作带来全新的性能范围。NIO = 非阻塞IO或事件驱动IO。 Netty是一个构建在java.nio基本组件之上的高性能网络实现。我曾经参与过一个项目,其中利用了Netty在sun blade硬件上实现了五位数的性能。Netty示例http代码可在此处找到。
LMAX的Martin Thompson还撰写了一篇很好的文章,涉及ByteArrayOutputStream的性能问题,可查看java序列化。这能更深刻地理解Java性能或内置JDK类的性能。
这些信息是否符合您的需求?或者由于遗留的JDK代码库,仅可使用java.io?

这非常有帮助,Mike。我也会对它们进行一些研究和开发。非常感谢你。 - ironwood

2
  1. 当连接建立时,请求可能会在系统缓冲区中被缓存,所以发送("上传")请求直到通过位置6并不保证已经完成。在此之后,你知道GET请求已经被服务器接收到,因为此时服务器正在发送响应。通过位置4只是意味着将请求传递到自己的系统缓冲区。

  2. 在最后一次通过位置6时,收到了响应("下载"完成),此时返回-1表示已到达流的结尾。

  3. 当你退出位置5时,可能已经收到了响应,也可能还没有收到响应。你要做的仅仅是获取一个已经存在的输入流。响应可能已经完全接收并缓存在系统TCP缓冲区中,也可能还没有接收任何响应。

  4. 从位置4到最后一个位置6迭代期间需要打开网络资源。在代码中,你可以在退出位置6/7的while循环后关闭连接,以节省网络资源。

  5. (a) 不要使用rqByteArrayOutputStream,而是将连接的输出流包装在OutputStreamWriter中,并直接将请求字符串写入其中。这样可以节省几行代码,移除一次复制请求字符串的迭代。

  6. (b) 当你完成使用输出流和输入流后,调用close()方法以释放网络资源。


感谢您提供这些有益的答案。非常有帮助。 - ironwood

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接