在使用 golang 进行 http 编程时,我们需要经常考虑如何关闭连接。关闭连接可以有效地避免资源浪费、提升性能,减少网络问题带来的不必要麻烦。本文将详细介绍 golang 中如何关闭 http 连接,并解析其中的一些细节问题。
一、HTTP 连接的关闭方式
Go 语言中的 HTTP 客户端和服务端实现了一系列的底层处理来管理 HTTP 连接。这些底层处理通常不会向用户暴露出来,而是被 HTTP 客户端和服务端隐藏在内部实现中。那么在 HTTP 客户端和服务端中,如何关闭连接呢?
- HTTP 客户端
在 HTTP 客户端中,我们有几种方式可以关闭连接:
- 使用 defer 关键字来手动关闭连接:在客户端调用接口后,一般会自动关闭连接。但如果当前客户端需要多次请求接口,可以考虑手动控制连接关闭,使用defer延迟关闭连接,代码如下:
package main
import (
"net/http"
"io/ioutil"
"fmt"
"log"
)
func main() {
client := &http.Client{}
req, err := http.NewRequest("GET", "http://www.baidu.com", nil)
if err != nil {
log.Fatal(err)
}
defer client.CloseIdleConnections() // 当函数返回时释放连接
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close() // 当函数返回时关闭body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body)[:50])
}- 使用 Transport 类的 MaxIdleConns 或 MaxIdleConnsPerHost 来控制连接池中的连接上限:Go 语言的 http.Transport 包含了默认的连接池,用于管理 HTTP 连接。我们可以使用其 MaxIdleConns 和 MaxIdleConnsPerHost 两个参数来指定最大空闲连接数和最大空闲主机连接数。当达到这个空闲数时,Transport 会自动关闭多余的连接。代码如下:
package main
import (
"net/http"
"io/ioutil"
"fmt"
"log"
)
func main() {
transport := &http.Transport{
MaxIdleConns: 10, // 最大空闲连接数
MaxIdleConnsPerHost: 3, // 每个域名地址最大空闲连接数
}
client := &http.Client{Transport: transport}
req, err := http.NewRequest("GET", "http://www.baidu.com", nil)
if err != nil {
log.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body)[:50])
}- HTTP 服务端
在 HTTP 服务端中,连接管理通常由服务器实现自动管理。我们只需要创建服务器并在不需要使用连接时关闭它即可。通常情况下,服务器在处理完一个请求后会自动关闭连接。但有几种情况比较特殊,需要我们手动控制连接的关闭:
立即学习“go语言免费学习笔记(深入)”;
- 在一个 HTTP 处理器中使用 conn.Close() 关闭连接:在处理器处理 HTTP 请求时,如果处理器需要关闭连接,则可以使用 conn.Close() 方法关闭连接。代码如下:
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, req *http.Request) {
// 需要关闭连接时,使用conn.Close()
conn, _, _ := w.(http.Hijacker).Hijack()
conn.Close()
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}- 使用 Keep-Alive:在使用 Keep-Alive 时,连接的生命周期由服务器管理。服务器可以定义一个 keep-alive 时间,来控制连接在闲置时间后应当关闭。这个时间一般在 HTTP header 中设置。代码如下:
package main
import (
"log"
"net/http"
)
func handler(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "keep-alive") // 定义keep-alive时间
w.Write([]byte("hello world"))
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}二、HTTP 连接的回收机制
当连接使用完毕后,我们需要为连接回收资源,防止出现资源泄漏、性能下降等问题。HTTP 连接资源的回收机制通常需要考虑如下问题:
- HTTP 长连接的最大存在时间:在连接空闲一段时间后,服务器会立刻回收这个连接。这个时间可以由服务器决定,在创建服务器对象时设置连接超时时间即可。
- HTTP 连接的最大连接数:在一段时间内,允许连接在连接池中的最大数量。当连接池中出现超出连接池大小的连接时,它们将被自动关闭。
- HTTP 连接的复用机制:连接复用是指在同一时间多个请求共享一个连接。这可以减少长时间等待状态下不必要的资源占用。但是需要注意的是,如果连接超时时间较短,可能会导致一次具有网络瞬时性波动的请求失败,从而影响系统的稳定性。
三、HTTP 连接池的管理
在大型高并发的系统中,我们需要使用连接池来管理 HTTP 连接,以提升系统的性能和稳定性。当调用 HTTP 请求时,连接池可以提供一个空闲的网络连接从而避免了频繁创建销毁连接的开销。连接池通常用常规队列来管理,以确保最先进入队列的元素最先被使用,即 FIFO 队列。以下是使用 Go 语言实现 HTTP 连接池的代码:
package main
import (
"fmt"
"log"
"net/http"
"sync"
"time"
)
type MyHttpClient struct {
client *http.Client // http客户端对象
Transport *http.Transport // transport对象,用于管理http连接池
}
var once sync.Once
var myClient *MyHttpClient
func GetMyHttpClient() *MyHttpClient {
once.Do(func() {
myClient = &MyHttpClient{
Transport: &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(network, addr, 10*time.Second)
if err != nil {
return nil, err
}
return conn, nil
},
MaxIdleConns: 100, // 连接池中最多拥有的连接数
MaxIdleConnsPerHost: 2, // 每个域名最多拥有的连接数
IdleConnTimeout: 60 * time.Second, // 连接在闲置多久后被关闭
TLSHandshakeTimeout: 10 * time.Second, // TLS握手时限
ExpectContinueTimeout: 1 * time.Second, // 100-continue超时时限
},
client: &http.Client{},
}
})
return myClient
}
func main() {
client := GetMyHttpClient().client
req, err := http.NewRequest("GET", "http://www.baidu.com", nil)
if err != nil {
log.Fatal(err)
}
defer client.CloseIdleConnections()
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body)[:50])
}四、总结
HTTP 连接的控制和回收是一个关键的性能优化点。在 Golang 中,我们可以通过手动控制、使用 Transport 类的参数设置、使用连接池等方式来管理 HTTP 连接,以提升系统的性能和稳定性。除此之外,我们还需要考虑到连接回收机制、HTTP 连接池的管理等问题,做好系统的维护和优化。










