写一个模拟浏览器的时候,使用inputstream的read方法读取服务器这边的数据时,发现一直不返回。
代码如下:
public static void main(String[] args) throws IOException {
InputStream inputStream = null;
OutputStream outputStream = null;
Socket socket = null;
try {
socket = new Socket("120.78.55.19", 80);
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
outputStream.write("GET / HTTP/1.1n".getBytes());
outputStream.write("HOST:120.78.55.19n".getBytes());
outputStream.write("n".getBytes());
int i = inputStream.read();
while (i != -1) {
System.out.print((char) i);
i = inputStream.read();
System.out.print("好");
}
System.out.println("跳出while循环");
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
System.out.println("进入finally");
if (null != inputStream) {
inputStream.close();
}
System.out.println("关掉了in");
if (null != outputStream) {
outputStream.close();
}
System.out.println("关掉了out");
if (null != socket) {
socket.close();
}
System.out.println("关掉了socket");
}
System.out.println(socket);
}
也就是这里是模仿了浏览器发送的http请求,请求服务端的网页。然后情况是:
网页数据都返回了,可是程序却一直没关闭。然后分析了一波,发现是停在了inputstream的read方法了。然后看read方法的注释:
Reads the next byte of data from the input stream. The value byte is returned as an int
in the range 0
to 255
. If no byte is available because the end of the stream has been reached, the value -1
is returned. This method blocks until input data is available, the end of the stream is detected, or an exception is thrown.
也就是从输入流中获得下一个字节的数据,返回的是一个在0到255的int类型的值。如果因为字节流到了尽头而没有字节了,就返回-1.这个方法会阻塞直到有输入数据,或者到达输入流的尽头,或者发送异常。
理解起来也很简单,就是一个输入流,read是按字节进行读取的,将一个8bit按ascii码读取为整数,如果读的是文本,那么会有文本结束符,遇到结束符姐返回。
下面是我的分析思路:
我刚开始以为服务端这边返回完数据之后就会结束,后来检测到并没有结束,没想明白,然后开个wireshark对数据进行分析,没想到过了大约75秒后,read方法返回了,然后结束了程序。与此同时,wireshark中也有了反应,此时刚好是服务端和客户端的tcp的4次挥手结束。如下图:
![174ee0c866096037de260ec8d2a22057.png](https://img-blog.csdnimg.cn/img_convert/174ee0c866096037de260ec8d2a22057.png)
这里我是试了两次,大家看下面一次即可。即8到14部分。这里只显示了服务端向客户端发送的数据。刚开始是tcp的三次握手,然后是发送http信息,然后停了75s左右的时间,就出现了后面的tcp的4次挥手。
此时我反应过来,inputstream的read方法在socket中是面向的是一次的tcp连接。并不是一次的http传输。
这里我有发现这和http的请求头重的connection=keeplive有关。
我们先来了解一下这个请求头的作用:
参考维基百科:
HTTP持久连接(HTTP persistent connection,也称作HTTP keep-alive或HTTP connection reuse)是使用同一个TCP连接来发送和接收多个HTTP请求/应答,而不是为每一个新的请求/应答打开新的连接的方法。
在 HTTP 1.0 中, 没有官方的 keepalive 的操作。通常是在现有协议上添加一个指数。如果浏览器支持 keep-alive,它会在请求的包头中添加:
Connection: Keep-Alive
然后当服务器收到请求,作出回应的时候,它也添加一个头在响应中:
Connection: Keep-Alive
这样做,连接就不会中断,而是保持连接。当客户端发送另一个请求时,它会使用同一个连接。这一直继续到客户端或服务器端认为会话已经结束,其中一方中断连接。
在 HTTP 1.1 中 所有的连接默认都是持续连接,除非特殊声明不支持。HTTP 持久连接不使用独立的 keepalive 信息,而是仅仅允许多个请求使用单个连接。然而, Apache 2.0 httpd 的默认连接过期时间是仅仅15秒,对于 Apache 2.2 只有5秒。 短的过期时间的优点是能够快速的传输多个web页组件,而不会绑定多个服务器进程或线程太长时间。
优势
- 较少的CPU和内存的使用(由于同时打开的连接的减少了)
- 允许请求和应答的HTTP管线化
- 降低拥塞控制 (TCP连接减少了)
- 减少了后续请求的延迟(无需再进行握手)
- 报告错误无需关闭TCP连接
劣势
对于现在的广泛普及的宽带连接来说,Keep-Alive也许并不像以前一样有用。web服务器会保持连接若干秒(Apache中默认15秒),这与提高的性能相比也许会影响性能。[7]
对于单个文件被不断请求的服务(例如图片存放网站),Keep-Alive可能会极大的影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间。
然后再回到我们的问题来:我是使用nginx搞在服务器上,nginx上默认这个keep-live的时间为75s. 我感觉了一下,大约也就这个时间就断开tcp连接,然后我的read方法就返回了。
然后,忽然醒悟,原来是这样啊。关键还是看服务器那边怎么配置啊。
http只是一次请求,read是看这个tcp连接断开了没。前面我们分析过,socket其实就是在tcp层上的数据传输。如果你自己写一个服务器,然后不设置对keep-alive的反应,那你就会发现,即使浏览器keep-alive了,也是一连上来就直接断了。对计算机网络的理解,感觉达到tcp的字节上就算有了一些基本的认识了。
唉,还是太年轻了。
欢迎交流讨论。