Skip to content

Latest commit

 

History

History
109 lines (83 loc) · 4.41 KB

15.HTTP之持久连接.md

File metadata and controls

109 lines (83 loc) · 4.41 KB

15.HTTP之持久连接

##15.1 持久连接 在HTTP1.0版本或之前,浏览器每建立一条TCP请求,只能发起一条HTTP请求,完成响应后就将TCP连接关闭。假设一个页面有100个资源,那么浏览器就需要为此建立100次TCP连接,这其实是__高消耗低性能__的做法。原因:

  • TCP端口消耗过多;
  • TCP重复建立握手造成时延和性能消耗;

从HTTP1.0增强版和HTTP1.1开始支持持久连接,所谓__持久连接__就是__使用同一条TCP连接建立多次HTTP请求。__假设每条TCP请求最多可以处理10个HTTP请求,那么原来的100次TCP连接则可减少为10次。那么就可以大大降低TCP的消耗。

如果要使用持久连接,则需要在HTTP头域使用Connection: keep-alive告诉服务器建立持久连接。假设要在一个TCP请求中发起10次HTTP请求,则需要在前9次的HTTP请求头域加入Connection: keep-alive,同时第十个HTTP头域使用Connection: close告诉服务器在响应这个HTTP请求后自动关闭TCP连接。(如果不写,TCP会在客户端或浏览器超时或自主断开后释放连接)

##15.2 示例

以下代码演示了一个TCP请求发起两个HTTP请求,第一个HTTP请求使用了Connection: keep-alive告诉服务器建立持久连接,而第二个请求则使用Connection: close告诉服务器在处理该HTTP请求后自动断开TCP连接。

public static void main(String[] args) throws UnknownHostException, IOException {
	Socket socket = new Socket("localhost", 8080);

	BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

	new Thread(() -> {
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));

			String line = null;
			while ((line = br.readLine()) != null) { // 迭代输出的文本
				System.out.println(line);
			}

			br.close();
			bw.close();
			socket.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}).start();

	bw.write("GET /springmvc/ HTTP/1.1\r\n");
	bw.write("HOST: localhost:8080\r\n");
	bw.write("Connection: keep-alive\r\n");
	bw.write("\r\n");
	bw.flush();

	bw.write("GET /springmvc/ HTTP/1.1\r\n");
	bw.write("HOST: localhost:8080\r\n");
	bw.write("Connection: close\r\n");
	bw.write("\r\n");
	bw.flush();
}

响应的报文如下:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=C1CC998B1DF491A2AA2DFC874624E247; Path=/springmvc/; HttpOnly
Content-Type: text/html;charset=ISO-8859-1
Content-Language: zh-CN
Content-Length: 89
Date: Mon, 28 Apr 2014 10:25:25 GMT


<html>
<head>
<title>Home</title>
</head>
<body>
	<h1>Hello world!</h1>
</body>
</html>
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=3E421B3CB47418054E0DDDA6BCB4CBD0; Path=/springmvc/; HttpOnly
Content-Type: text/html;charset=ISO-8859-1
Content-Language: zh-CN
Content-Length: 89
Date: Mon, 28 Apr 2014 10:25:25 GMT
Connection: close


<html>
<head>
<title>Home</title>
</head>
<body>
	<h1>Hello world!</h1>
</body>
</html>

可以看到,由于使用了持久连接,服务器将两个HTTP的报文都写在了一个TCP连接中,那么我们如何分割报文,以及找到HTTP请求对应的报文呢。

  • 首先,在服务器响应的报文中,会有一个Content-Length的头域,该头域用于指定对应的HTTP报文的Message Body大小,那么我们可以从头域后开始读取Content-Length指定大小的内容,这就是一个完整的HTTP报文了,依次类推就可以分割各个不同的HTTP请求的报文。
  • 在持久连接中,HTTP响应的报文顺序就是发起的HTTP请求顺序,所以浏览器只要把持久连接的HTTP请求顺序记录下来则可以找到对应的HTTP响应报文了。
  • 大概处理如此,考虑到各种异常情况,实际上处理要复杂的多。

##15.3 More

  • 在15.2的示例中,将第一个请求改为Connection: close则服务器在处理第一个HTTP请求后就会关闭TCP连接而不会处理其余的HTTP请求。
  • 如果将第二个请求改为Connection: keep-alive,则客户端在打印完两个HTTP报文后,并不会即使断开TCP连接,由于服务器以为客户端还会持续发送HTTP请求而不会主动断开TCP请求,所以line并不会读到null值。这时候,要么客户端或服务器主动断开TCP请求,或等待客户端或服务器任意一端超时。

##15.4 参考资料

  • 《HTTP权威指南》
  • 以上代码使用Java8编写