美文网首页HTTP网络我爱编程
HTTP首部Connection实践

HTTP首部Connection实践

作者: 大头8086 | 来源:发表于2017-07-27 09:50 被阅读652次

    引子

    在HTTP/1.0 时代,HTTP与TCP默认传输的关系如图1所示,每次HTTP请求和响应都发生一次TCP三次握手和四次挥手。以HTTP/1.0 时代的网络通信量来看,都是些容量很小的文本传输,所以图1的传输方式性能没有较大问题。


    图1 HTTP/1.0默认传输过程

    可是随着互联网普及,Web网页的图片逐渐多起来。比如,使用浏览器浏览一个包含多张图片的HTML页面时,在发送HTTP请求访问HTML页面资源的同时,也会请求该HTML页面里包含的其他资源,比如图片等静态文件。因此,每次的请求都会造成无谓的TCP连接建立和断开,增加网络通信量的开销,如图2所示。


    图2 一个Web页面的多次HTTP请求
    为了解决上述问题,HTTP/1.1和一部分的HTTP/1.0想出了持久连接的办法,即只要任意一端没有明确提出断开连接,则保持TCP连接状态,如图3所示。
    图3 HTTP持久连接
    HTTP持久连接不是绝对高性能

    HTTP持久连接允许在事务处理结束之后将TCP连接保持在打开状态,以便为未来的HTTP请求重用现存的连接。在事务处理结束之后仍然保持在打开状态的TCP连接被称为持久连接。持久连接会在不同事务之间保持打开状态,直到客户端或服务器决定将其关闭为止。
    优点:重用已对目标服务器打开的空闲持久连接,可以避开缓慢的连接建立阶段,更快速地进行数据的传输。
    缺点:管理不当可能会积累出大量的空闲连接,耗费本地客户端以及远程服务器上的资源。
    非持久连接会在每个事务处理结束之后关闭。

    HTTP持久连接实现手段是HTTP首部添加Connection字段
    • Connection: keep-alive , 开启HTTP持久连接,HTTP 1.1默认值
    • Connection: close , 关闭HTTP持久连接,HTTP 1.0默认值
    HTTP keep-alive与TCP keep-alive区别
    • HTTP keep-alive参数为了减少TCP连接和断开而提出的一种解决方案,HTTP持久连接即TCP长连接。
    • TCP keep-alive参数主要为探测长连接的存活状况,即TCP保活功能。

    本文将对HTTP首部Connction实践,对比keep-alive/close两个值在HTTP和TCP的表现情况。后端使用Spring boot+Java,前端使用HTML+CSS。

    HTTP Request首部Connection

    如果Client希望HTTP使用持久连接,在Request首部指定Connection: keep-alive,否则指定Connection: close
    后端Java代码如下:

    package com.demo.web.http;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestHeader;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping("http")
    public class ConnectionController {
        @RequestMapping("/connection")
        public String connection(@RequestHeader(value="Connection") String connection) {
            System.out.println("Connection: " + connection);
            return "http/connection";
        }
    }
    

    前端HTML代码:

    <!DOCTYPE HTML>
    <html>
    <head>
        <title>HTTP Connection Demo</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
    <h3>实践HTTP Connection</h3>
    <a href="connection">刷新页面</a>
    </body>
    </html>
    

    后端服务启动端口8080,执行nc 127.0.0.1 8080,输入如下两行命令,两次回车,HTTP Request首部Connection: keep-alive,执行情况如图4第一个红色方框,紧接着返回Web页面,后端日志打印Connection: keep-alive。从图5抓包红色方框看,TCP没有发起四次挥手释放连接,HTTP请求保持TCP长连接。

    GET http://127.0.0.1:8080/http/connection HTTP/1.1
    Connection: keep-alive
    
    图4 HTTP持久连接请求
    图5 HTTP请求tcpdump

    再次输入如下两行命令,两次回车,HTTP Request首部Connection: keep-alive,执行情况如图4第二个红色方框,紧接着返回Web页面,后端日志打印Connection: keep-alive。从图5绿色方框看,TCP没有发起四次挥手释放连接,HTTP请求保持TCP长连接。

    GET http://127.0.0.1:8080/http/connection HTTP/1.1
    Connection: keep-alive
    

    再次输入如下两行命令,两次回车,HTTP Request首部Connection: close,执行情况如图6绿色方框,紧接着返回Web页面,后端日志打印Connection: close。从图5蓝色色方框看,TCP发起四次挥手释放连接,HTTP请求的TCP连接断开。

    GET http://127.0.0.1:8080/http/connection HTTP/1.1
    Connection: close
    
    图5 HTTP请求

    HTTP Response首部Connection

    如果Server希望HTTP使用长连接,在Response首部指定Connection: keep-alive,否则指定Connection: close
    后端Java代码如下:

    package com.demo.web.http;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestHeader;
    import org.springframework.web.bind.annotation.RequestMapping;
    import javax.servlet.http.HttpServletResponse;
    
    @Controller
    @RequestMapping("http")
    public class ConnectionController {
        @RequestMapping("/connection")
        public String connection(HttpServletRequest request, HttpServletResponse response,
                                 @RequestHeader(value="Connection") String connection) {
            System.out.println("Connection: " + connection);
            response.addHeader("Connection", "keep-alive");
            return "http/connection";
        }
    }
    

    前端HTML代码:

    <!DOCTYPE HTML>
    <html>
    <head>
        <title>HTTP Connection Demo</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
    <h3>实践HTTP Connection</h3>
    <a href="connection">刷新页面</a>
    </body>
    </html>
    

    HTTP/1.1的Request首部Connection默认为keep-alive,当后端返回Response首部Connection: keep-alive,访问http://127.0.0.1:8080/http/connection,如图6所示,点击按钮刷新页面。从图7绿色方框和红色方框看,TCP没有发起四次挥手释放连接,HTTP请求保持TCP长连接。超时后TCP连接会自动断开,从四次挥手的开始时间21:31:40.000676与Web页面请求结束时间21:30:39.945438看,TCP长连接60s超时。

    图6 HTTP页面访问
    图7 HTTP页面访问tcpdump

    HTTP/1.0与HTTP/1.1 首部Connection默认值对比

    后端Java代码:

    package com.demo.web.http;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestHeader;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping("http")
    public class ConnectionController {
        @RequestMapping("/connection")
        public String connection() {
            return "http/connection";
        }
    }
    

    后端服务启动端口8080,执行nc 127.0.0.1 8080,输入如下命令,两次回车,重复执行如下命令,如图8所示,TCP没有发起四次挥手释放连接,HTTP请求保持TCP长连接,所以HTTP/1.1 首部Connection默认值为keep-alive。

    GET http://127.0.0.1:8080/http/connection HTTP/1.1
    
    图8 HTTP/1.1持久连接

    后端服务启动端口8080,执行nc 127.0.0.1 8080,输入如下命令,两次回车,如图9所示,TCP释放连接,HTTP请求也结束,所以HTTP/1.0 首部Connection默认值为close。

    GET http://127.0.0.1:8080/http/connection HTTP/1.0
    
    图9 HTTP 1.0

    再进一步,后端服务启动端口8080,执行nc 127.0.0.1 8080,输入如下命令,两次回车,如图9所示,TCP释放连接,HTTP请求也结束,所以HTTP默认使用1.0版本。

    GET http://127.0.0.1:8080/http/connection
    
    图10 HTTP默认版本

    HTTP持久连接的数据传输完成识别

    HTTP首部定义Connection: keep-alive后,客户端、服务端怎么知道本次传输结束呢?两部分:

    • 静态页面通过Content-Length提前告知对方数据传输大小,具体可以参考拙作HTTP Content-Length深入实践
    • 动态页面不能通过Content-Length提前告知对方数据传输大小,它是分块传输(chunked),这时候就要根据chunked编码来判断,chunked编码的数据在最后有一个空chunked块,表明本次传输数据结束,HTTP头部使用Transfer-Encoding: chunked来代替Content-Length。

    相关文章

      网友评论

        本文标题:HTTP首部Connection实践

        本文链接:https://www.haomeiwen.com/subject/fimokxtx.html