使用场景
一个常用的CS架构的http的请求代码例子:
request, err = http.NewRequest(method, reqUrl, nil)
request.Close = true // 是否开启http复用标志,正常客户端可以设置
// 初始化http client, 可进行参数设置
client := &http.Client{
Transport: &http.Transport{DisableKeepAlives: true},
}
// 发起请求
response, err := client.Do(request)
if response != nil {
defer response.Body.Close()
}
bodyBytes, err := ioutil.ReadAll(response.Body)
_, err = io.Copy(ioutil.Discard, response.Body)
连接复用配置项
- 代表http连接请求的request对象字段Close:
// Close indicates whether to close the connection after
// replying to this request (for servers) or after sending this
// request and reading its response (for clients).
//
// For server requests, the HTTP server handles this automatically
// and this field is not needed by Handlers.
//
// For client requests, setting this field prevents re-use of
// TCP connections between requests to the same hosts, as if
// Transport.DisableKeepAlives were set.
Close bool
Close参数的使用,是在判断是否复用连接的时候:
// isConnectionCloseRequest reports whether req should use its own
// connection for a single request and then close the connection.
func isConnectionCloseRequest(req *http.Request) bool {
return req.Close || httplex.HeaderValuesContainsToken(req.Header["Connection"], "close")
}
close值设置:
- close = true 表示关闭tcp长连接,不复用connect,需重新握手建立连接传输数据。
- close = false 反之。
- 客户端client设置Transport.DisableKeepAlives:
在client初始化的时候,设置transport参数的时候,配置是否复用参数
client := &http.Client{
Transport: &http.Transport{DisableKeepAlives: true},
}
- 底层隐藏的连接复用条件
官方文档方法Client.Do描述:
If the returned error is nil, the Response will contain a non-nil Body which the user is expected to close. If the Body is not both read to EOF and closed, the Client's underlying RoundTripper (typically Transport) may not be able to re-use a persistent TCP connection to the server for a subsequent "keep-alive" request.
文档描述了,如果想要复用一个tcp连接,需要客户端client将response的body数据处理同时满足2个条件:
- 要将响应的body数据全部读取出来,直到读取遇到EOF(表示无数据可读)
- 需要将响应对象close关闭。
关于这个tcp连接复用的官方讨论,可以参考:net/http: Docs update for connection reuse #26095
ioutil.Discard
Discard 如名字一样,是一个用于丢弃数据的地方,虽然有时候我们不在意数据内容,但可能存在数据不读出来就无法关闭连接的情况,或者需要将response响应中body数据清空满足EOF读取条件,从而满足TCP连接复用。这时候就可以使用 io.Copy(ioutil.Discard, io.Reader) 将数据写入 Discard。
在GO 1.16版本开始将Discard 迁移到io包。通过io.Discard使用。
// 手动丢弃读取完毕的数据
_, err = io.Copy(ioutil.Discard, response.Body)
总的来说,就是在需要使用tcp的连接复用的情况下,就是只需要建立一次连接后,发送方和接收方的数据传递就可以在同一条连接上进行交互处理,减少多次建立三次握手连接的资源消耗。
则在处理response响应的body数据的时候,需要手动通过io.Copy + io.Discard组合,将body中的数据全部读取出来,从而满足golang中SDK对于tcp连接复用的条件。
Keep-Alive
TCP中的Keep-Alive机制是一种用于检测和维持长时间空闲TCP连接的活动状态的协议。
其主要作用和意义包括:
1.检测死连接:当TCP连接在一定时间内没有数据传输时,Keep-Alive机制可以通过发送探测包来检测连接是否仍然活跃。如果对方没有响应探测包,那么连接可能已经不可用,系统可以据此关闭连接,释放资源。
2.减少资源占用:通过及时关闭不再使用的TCP连接,Keep-Alive机制有助于减少系统资源的占用,如套接字资源和内存等。
3.提高效率:通过维持空闲连接的活动状态,Keep-Alive机制避免了频繁建立和断开连接的开销,从而提高了网络通信的效率。
4.增强可靠性:在网络环境不稳定或存在丢包的情况下,Keep-Alive机制可以确保连接的持续监测,提高网络通信的可靠性。