目录
Keep-Alive 模式
管道机制(Pipelining)
Content-Length
Transfer Encoding Chunked
Keep-Alive 模式
理解 Keep-Alive 模式,就要提到http1.0协议的缺点。
http1.0协议的主要缺点就是,每个TCP连接只能发一个请求, 每次数据发送完毕,连接就关闭。 如果还要请求其他资源,就必须新建连接。
因为三次握手、慢启动等,TCP连接新建的成本很高。 随着web的发展,需要请求的资源越来越多,这个问题就越来越突出。
在http1.0时代,为了解决这个问题, 有些浏览器在请求时会在请求头里加上一个自定义字段:
Connection: keep-alive
这个字段就是告诉服务器不要关闭TCP连接,以便其他请求复用,但是,这还不是标准字段。
1997年1月, http1.1版本发布,引入了持久连接,即默认情况下TCP连接不关闭,可以被复用,也就是默认 keep-alive 。同时,支持通过 Connection:close 来通知关闭TCP连接。
所以实际上,http1.0协议下客户端和服务端可以通过 Connection: keep-alive 告知对方发送完数据后不要关闭TCP连接,而http1.1协议下默认就是 Connection: keep-alive 持久连接。
* 示例:服务端
[shell]# vim server.go
package main
import (
"io"
"log"
"net/http"
)
func indexServer(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hei !\n")
}
func main() {
http.HandleFunc("/index", indexServer)
log.Fatal(http.ListenAndServe(":8888", nil))
}
[shell]# go run server.go
http1.1协议默认情况下就加了Connection: keep-alive,会持久连接,如下面三次请求复用同一个TCP连接,而且在等待一段时间没有新请求的之后,关闭连接。
GET /index HTTP/1.1
Host 192.168.56.101:8888
Cache-Control max-age=0
Upgrade-Insecure-Requests 1
User-Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding gzip, deflate
Accept-Language zh-CN,zh;q=0.9,en;q=0.8
Connection keep-alive
图上本地端口始终是 51411:
当 Connection: close 时,数据发送完毕会断开TCP连接,每次请求都需要新建连接。
GET /index HTTP/1.1
Host 192.168.56.101:8888
Cache-Control max-age=0
Upgrade-Insecure-Requests 1
User-Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding gzip, deflate
Accept-Language zh-CN,zh;q=0.9,en;q=0.8
Connection close
管道机制(Pipelining)
http1.0还有一个问题是当客户端复用TCP连接发送请求时, 要先发送一个请求,等到服务器响应之后,再发出一个请求,这也会造成效率低。
http1.1为了解决这个问题引入了管道机制(Pipelining),管道机制允许客户端同时发出多个请求,然后服务端按照顺序响应。
Connection: keep-alive
在非持久连接下可以可以通过连接关闭得知数据已经发送完毕, 在持久连接下显然不行。http1.1开始, 一个TCP连接可以发出多个请求,并传送多个返回, 这时就需要区分数据包是哪一个请求返回的。有一个方法就是通过 Content-Length 字段, 请求双方可以通过
Content-Length: 6
告知对方响应数据字节数,比如Content-Length:6,6个字节后的数据就属于下一个请求。
Transfer Encoding Chunked
接着上面提到的持久连接下如何区分数据包边界, 在客户端请求的是一个静态页面或者一张图片时,服务端可以明确的计算出Content-Length返回给客户端。
但是当请求的内容是需要耗时计算的动态数据时,服务端不可能预先知道数据包大小,这时服务端就可以通过 Transfer Encoding:chunked 的方式来传输数据。
chunked 编码将数据分成一个个数据块发送, 每个数据块之前会有一个16进制的数值表示这个数据块的长度。最后是一个小为0的块,表示数据发送完毕。
* 服务端代码调整
package main
import (
"fmt"
"io"
"log"
"net/http"
"time"
)
func indexServer(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "Keep-Alive")
w.Header().Set("Transfer-Encoding", "chunked")
w.Header().Set("X-Content-Type-Options", "nosniff")
ticker := time.NewTicker(time.Second)
go func() {
for t := range ticker.C {
io.WriteString(w, "Chunk")
fmt.Println("Tick at", t)
}
}()
time.Sleep(time.Second * 5)
ticker.Stop()
fmt.Println("Finished: should return Content-Length: 0 here")
w.Header().Set("Content-Length", "0")
}
func main() {
http.HandleFunc("/index", indexServer)
log.Fatal(http.ListenAndServe(":8888", nil))
}
* 客户端:
参考文献
《HTTP权威指南》
参考资料
| 详解TCP、HTTP中的保活机制_Keepalive和Keep-Alive |
| HTTP灵魂之问,巩固你的 HTTP 知识体系 | HTTP协议中的Content-Length |
| HTTP 协议中的 Transfer-Encoding | 详解TCP、HTTP中的保活机制_Keepalive和Keep-Alive |