日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

Go的Net/Http有哪些值得關注的細節?

來源: 責編: 時間:2023-08-14 22:01:04 425觀看
導讀golang的net/http庫是我們平時寫代碼中,非常常用的標準庫。由于go語言擁有goroutine,goroutine的上下文切換成本比普通線程低很多,net/http庫充分利用了這個優勢,因此,它的內部實現跟其他語言會有一些區別。其中最大的區別

VyT28資訊網——每日最新資訊28at.com

golang的net/http庫是我們平時寫代碼中,非常常用的標準庫。由于go語言擁有goroutine,goroutine的上下文切換成本比普通線程低很多,net/http庫充分利用了這個優勢,因此,它的內部實現跟其他語言會有一些區別。VyT28資訊網——每日最新資訊28at.com

其中最大的區別在于,其他語言中,一般是多個網絡句柄共用一個或多個線程,以此來減少線程之間的切換成本。而golang則會為每個網絡句柄創建兩個goroutine,一個用于讀數據,一個用于寫數據。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

讀寫協程VyT28資訊網——每日最新資訊28at.com

下圖是net/http源碼中創建這兩個goroutine的地方。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

源碼中創建兩個協程的地方VyT28資訊網——每日最新資訊28at.com

了解它的內部實現原理,可以幫助我們寫出更高性能的代碼,以及避免協程泄露造成的內存泄漏問題。VyT28資訊網——每日最新資訊28at.com

這篇文章是希望通過幾個例子讓大家對net/http的內部實現有更直觀的理解。VyT28資訊網——每日最新資訊28at.com

連接與協程數量的關系

首先我們來看一個例子。VyT28資訊網——每日最新資訊28at.com

func main() {    tr := &http.Transport{        MaxIdleConns:    100,        IdleConnTimeout: 3 * time.Second,    }    n := 5    for i := 0; i < n; i++ {        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)        req.Header.Add("content-type", "application/json")        client := &http.Client{            Transport: tr,            Timeout:   3 * time.Second,        }        resp, _ := client.Do(req)        _, _ = ioutil.ReadAll(resp.Body)        _ = resp.Body.Close()    }    time.Sleep(time.Second * 5)    fmt.Printf("goroutine num is %d/n", runtime.NumGoroutine())}

上面的代碼做的事情很簡單,執行5次循環http請求,最終通過runtime.NumGoroutine()方法打印當前的goroutine數量。VyT28資訊網——每日最新資訊28at.com

代碼里只有三個地方需要注意:VyT28資訊網——每日最新資訊28at.com

  • Transport設置了一個3s的空閑連接超時。
  •  for循環執行了5次http請求。
  • 程序退出前執行了5s sleep。

答案輸出1。也就是說當程序退出的時候,當前的goroutine數量為1,毫無疑問它指的是正在運行main方法的goroutine,后面我們都叫它main goroutine。VyT28資訊網——每日最新資訊28at.com

再來看個例子。VyT28資訊網——每日最新資訊28at.com

func main() {    tr := &http.Transport{        MaxIdleConns:    100,        IdleConnTimeout: 3 * time.Second,    }    n := 5    for i := 0; i < n; i++ {        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)        req.Header.Add("content-type", "application/json")        client := &http.Client{            Transport: tr,            Timeout:   3 * time.Second,        }        resp, _ := client.Do(req)        _, _ = ioutil.ReadAll(resp.Body)        _ = resp.Body.Close()    }    time.Sleep(time.Second * 1)    fmt.Printf("goroutine num is %d/n", runtime.NumGoroutine())}

在原來的基礎上,我們程序退出前的睡眠時間,從5s改成1s,此時輸出3。也就是說除了main方法所在的goroutine,還多了兩個goroutine,我們大概也能猜到,這就是文章開頭提到的讀goroutine和寫goroutine。也就是說程序在退出時,還有一個網絡連接沒有斷開。VyT28資訊網——每日最新資訊28at.com

這是一個TCP長連接。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

HTTP1.1底層依賴TCPVyT28資訊網——每日最新資訊28at.com

網絡五層模型中,HTTP處于應用層,它的底層依賴了傳輸層的TCP協議。VyT28資訊網——每日最新資訊28at.com

當我們發起http請求時,如果每次都要建立新的TCP協議,那就需要每次都經歷三次握手,這會影響性能,因此更好的方式就是在http請求結束后,不立馬斷開TCP連接,將它放到一個空閑連接池中,后續有新的http請求時就復用該連接。VyT28資訊網——每日最新資訊28at.com

像這種長時間存活,被多個http請求復用的TCP連接,就是所謂的長連接。反過來,如果每次HTTP請求結束就將TCP連接進行四次揮手斷開,下次有需要執行HTTP調用時就再建立,這樣的TCP連接就是所謂的短連接VyT28資訊網——每日最新資訊28at.com

HTTP1.1之后默認使用長連接。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

連接池復用連接VyT28資訊網——每日最新資訊28at.com

那為什么這跟5s和1s有關系?VyT28資訊網——每日最新資訊28at.com

這是因為長連接在空閑連接池也不能一直存放著,如果一直沒被使用放著也是浪費資源,因此會有個空閑回收時間,也就是上面代碼中的IdleConnTimeout,我們設置的是3s,當代碼在結束前sleep了5s后,長連接就已經被釋放了,因此輸出結果是只剩一個main goroutine。當sleep 1s時,長連接還在空閑連接池里,因此程序結束時,就還剩3個goroutine(main goroutine+網絡讀goroutine+網絡寫goroutine)。VyT28資訊網——每日最新資訊28at.com

我們可以改下代碼下驗證這個說法。我們知道,HTTP可以通過connection的header頭來控制這次的HTTP請求是用的長連接還是短連接。connection:keep-alive 表示http請求結束后,tcp連接保持存活,也就是長連接, connection:close則是短連接。VyT28資訊網——每日最新資訊28at.com

req.Header.Add("connection", "close")

就像下面這樣。VyT28資訊網——每日最新資訊28at.com

func main() {    tr := &http.Transport{        MaxIdleConns:    100,        IdleConnTimeout: 3 * time.Second,    }    n := 5    for i := 0; i < n; i++ {        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)        req.Header.Add("content-type", "application/json")        req.Header.Add("connection", "close")        client := &http.Client{            Transport: tr,            Timeout:   3 * time.Second,        }        resp, _ := client.Do(req)        _, _ = ioutil.ReadAll(resp.Body)        _ = resp.Body.Close()    }    time.Sleep(time.Second * 1)    fmt.Printf("goroutine num is %d/n", runtime.NumGoroutine())}

此時,會發現,程序重新輸出1。完全符合我們預期。VyT28資訊網——每日最新資訊28at.com

resp.body是否讀取對連接復用的影響

func main() {   n := 5   for i := 0; i < n; i++ {      resp, _ := http.Get("https://www.baidu.com")      _ = resp.Body.Close()   }   fmt.Printf("goroutine num is %d/n", runtime.NumGoroutine())}

注意這里沒有執行 ioutil.ReadAll(resp.Body)。也就是說http請求響應的結果并沒有被讀取的情況下,net/http庫會怎么處理。VyT28資訊網——每日最新資訊28at.com

上面的代碼最終輸出3,分別是main goroutine,read goroutine 以及write goroutine。也就是說長連接沒有斷開,那長連接是會在下一次http請求中被復用嗎?先說答案,不會復用。VyT28資訊網——每日最新資訊28at.com

我們可以看代碼。resp.Body.Close() 會執行到 func (es * bodyEOFSignal) Close() error 中,并執行到es.earlyCloseFn()中。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

earlyCloseFn的邏輯也非常簡單,就是將一個false傳入到waitForBodyRead的channel中。那寫入通道后的數據會在另外一個地方被讀取,我們來看下讀取的地方。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

bodyEOF為false, 也就不需要執行 tryPutIdleConn()方法。VyT28資訊網——每日最新資訊28at.com

tryPutIdleConn會將連接放到長連接池中備用)。VyT28資訊網——每日最新資訊28at.com

最終就是alive=bodyEOF ,也就是false,字面意思就是該連接不再存活。因此該長連接并不會復用,而是會釋放。VyT28資訊網——每日最新資訊28at.com

那為什么output輸出為3?這是因為長連接釋放需要時間。VyT28資訊網——每日最新資訊28at.com

我們可以在結束前加一個休眠,比如再執行休眠1毫秒。VyT28資訊網——每日最新資訊28at.com

func main() {    n := 5    for i := 0; i < n; i++ {        resp, _ := http.Get("https://www.baidu.com")        _ = resp.Body.Close()    }    time.Sleep(time.Millisecond * 1)    fmt.Printf("goroutine num is %d/n", runtime.NumGoroutine())}

此時就會輸出1。說明協程是退出中的,只是沒來得及完全退出,休眠1ms后徹底退出了。VyT28資訊網——每日最新資訊28at.com

如果我們,將在代碼中重新加入 ioutil.ReadAll(resp.Body),就像下面這樣。VyT28資訊網——每日最新資訊28at.com

func main() {    n := 5    for i := 0; i < n; i++ {        resp, _ := http.Get("https://www.baidu.com")        _, _ = ioutil.ReadAll(resp.Body)        _ = resp.Body.Close()    }    fmt.Printf("goroutine num is %d/n", runtime.NumGoroutine())}

此時,output還是輸出3,但這個3跟上面的3不太一樣,休眠5s后還是輸出3。這是因為長連接被推入到連接池了,連接會重新復用。VyT28資訊網——每日最新資訊28at.com

下面是源碼的解釋。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com


VyT28資訊網——每日最新資訊28at.com

body.close()不執行會怎么樣

網上都說不執行body.close()會協程泄漏(導致內存泄露),真的會出現協程泄漏嗎,如果泄漏,會泄漏多少?VyT28資訊網——每日最新資訊28at.com

func main() {    tr := &http.Transport{        MaxIdleConns:    100,        IdleConnTimeout: 3 * time.Second,    }    n := 5    for i := 0; i < n; i++ {        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)        req.Header.Add("content-type", "application/json")        client := &http.Client{            Transport: tr,        }        resp, _ := client.Do(req)        _, _ = ioutil.ReadAll(resp.Body)        //_ = resp.Body.Close()    }    time.Sleep(time.Second * 1)    fmt.Printf("goroutine num is %d/n", runtime.NumGoroutine())}

我們可以運行這段代碼,代碼中將resp.body.close()注釋掉,結果輸出3。debug源碼,會發現連接其實復用了。代碼執行到tryPutIdleConn函數中,會將連接歸還到空閑連接池中。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

休眠5s,結果輸出1,這說明達到idleConnTimeout,空閑連接斷開。看起來一切正常。VyT28資訊網——每日最新資訊28at.com

將resp.Body.Close()那一行代碼重新加回來,也就是下面這樣,會發現代碼結果依然輸出3。我們是否刪除這行代碼,對結果沒有任何影響。VyT28資訊網——每日最新資訊28at.com

func main() {    tr := &http.Transport{        MaxIdleConns:    100,        IdleConnTimeout: 3 * time.Second,    }    n := 5    for i := 0; i < n; i++ {        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)        req.Header.Add("content-type", "application/json")        client := &http.Client{            Transport: tr,        }        resp, _ := client.Do(req)        _, _ = ioutil.ReadAll(resp.Body)        _ = resp.Body.Close()    }    time.Sleep(time.Second * 1)    fmt.Printf("goroutine num is %d/n", runtime.NumGoroutine())}

既然執不執行body.close()都沒啥區別,那body.close()的作用是什么呢?VyT28資訊網——每日最新資訊28at.com

它是為了標記當前連接請求中,response.body是否使用完畢,如果不執行body.close(),則resp.Body中的數據是可以不斷重復讀且不報錯的(但不一定能讀到數據),執行了body.close(),再次去讀取resp.Body則會報錯,如果resp.body數據讀一半,處理代碼邏輯就報錯了,此時你不希望其他地方繼續去讀,那就需要使用body.close()去關閉它。這更像是一種規范約束,它可以更好的保證數據正確。VyT28資訊網——每日最新資訊28at.com

也就是說不執行body.close(),并不一定會內存泄露。那么什么情況下會協程泄露呢?VyT28資訊網——每日最新資訊28at.com

直接說答案,既不執行 ioutil.ReadAll(resp.Body) 也不執行resp.Body.Close(),并且不設置http.Client內timeout的時候,就會導致協程泄露。VyT28資訊網——每日最新資訊28at.com

比如下面這樣。VyT28資訊網——每日最新資訊28at.com

func main() {    tr := &http.Transport{        MaxIdleConns:    100,        IdleConnTimeout: 3 * time.Second,    }    n := 5    for i := 0; i < n; i++ {        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)        req.Header.Add("content-type", "application/json")        client := &http.Client{            Transport: tr,        }        resp, _ := client.Do(req)        _ = resp    }    time.Sleep(time.Second * 5)    fmt.Printf("goroutine num is %d/n", runtime.NumGoroutine())}

最終結果會輸出11,也就是1個main goroutine + (1個read goroutine + 1個read goroutine)* 5次http請求。VyT28資訊網——每日最新資訊28at.com

前面提到,不執行ioutil.ReadAll(resp.Body),網絡連接無法歸還到連接池。不執行resp.Body.Close(),網絡連接就無法為標記為關閉,也就無法正常斷開。因此能導致協程泄露,非常好理解。VyT28資訊網——每日最新資訊28at.com

但http.Client內timeout有什么關系?這是因為timeout是指,從發起請求到從resp.body中讀完響應數據的總時間,如果超過了,網絡庫會自動斷開網絡連接,并釋放read+write goroutine。因此如果設置了timeout,則不會出現協程泄露的問題。VyT28資訊網——每日最新資訊28at.com

另外值得一提的是,我看到有不少代碼都是直接用下面的方式去做網絡請求的。VyT28資訊網——每日最新資訊28at.com

resp, _ := http.Get("https://www.baidu.com")

這種方式用的是DefaultClient,是沒有設置超時的,生產環境中使用不當,很容易出現問題。VyT28資訊網——每日最新資訊28at.com

func Get(url string) (resp *Response, err error) {    return DefaultClient.Get(url)}var DefaultClient = &Client{}

連接池的結構

我們了解到連接池可以復用網絡連接,接下來我們通過一個例子來看看網絡連接池的結構。VyT28資訊網——每日最新資訊28at.com

func main() {    tr := &http.Transport{        MaxIdleConns:    100,        IdleConnTimeout: 3 * time.Second,    }    n := 5    for i := 0; i < n; i++ {        req, _ := http.NewRequest("POST", "http://www.baidu.com", nil)        req.Header.Add("content-type", "application/json")        client := &http.Client{            Transport: tr,            Timeout:   3 * time.Second,        }        resp, _ := client.Do(req)        _, _ = ioutil.ReadAll(resp.Body)        _ = resp.Body.Close()    }    time.Sleep(time.Second * 1)    fmt.Printf("goroutine num is %d/n", runtime.NumGoroutine())}

注意這里請求的不是https,而是http。最終結果輸出5,為什么?VyT28資訊網——每日最新資訊28at.com

這是因為,http://www.baidu.com會返回307,重定向到https://www.baidu.com。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

http重定向為httpsVyT28資訊網——每日最新資訊28at.com

在網絡中,我們可以通過一個五元組來唯一確定一個TCP連接。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

五元組VyT28資訊網——每日最新資訊28at.com

它們分別是源ip,源端口,協議,目的ip,目的端口。只有當多次請求的五元組一樣的情況下,才有可能復用連接。VyT28資訊網——每日最新資訊28at.com

放在我們這個場景下,源ip、源端口、協議都是確定的,也就是兩次http請求的目的ip或目的端口有區別的時候,就需要使用不同的TCP長連接。VyT28資訊網——每日最新資訊28at.com

而http用的是80端口,https用的是443端口。于是連接池就為不同的網絡目的地建立不同的長連接。VyT28資訊網——每日最新資訊28at.com

因此最終結果5個goroutine,其實2個goroutine來自http,2個goroutine來自https,1個main goroutine。VyT28資訊網——每日最新資訊28at.com

我們來看下源碼的具體實現。net/http底層通過一個叫idleConn的map去存空閑連接,也就是空閑連接池。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

idleConn這個map的key是協議和地址,其實本質上就是ip和端口。map的value是長連接的數組([]*persistConn),說明net/http支持為同一個地址建立多個TCP連接,這樣可以提升傳輸的吞吐。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

連接池的結構和邏輯VyT28資訊網——每日最新資訊28at.com

Transport是什么?

Transport本質上是一個用來控制http調用行為的一個組件,里面包含超時控制,連接池等,其中最重要的是連接池相關的配置。VyT28資訊網——每日最新資訊28at.com

我們通過下面的例子感受下。VyT28資訊網——每日最新資訊28at.com

func main() {    n := 5    for i := 0; i < n; i++ {        httpClient := &http.Client{}        resp, _ := httpClient.Get("https://www.baidu.com")        _, _ = ioutil.ReadAll(resp.Body)        _ = resp.Body.Close()    }    time.Sleep(time.Second * 1)    fmt.Printf("goroutine num is %d/n", runtime.NumGoroutine())}
func main() {    n := 5    for i := 0; i < n; i++ {        httpClient := &http.Client{            Transport:  &http.Transport{},        }        resp, _ := httpClient.Get("https://www.baidu.com")        _, _ = ioutil.ReadAll(resp.Body)        _ = resp.Body.Close()    }    time.Sleep(time.Second * 1)    fmt.Printf("goroutine num is %d/n", runtime.NumGoroutine())}

上面的代碼第一個例子的代碼會輸出3。分別是main goroutine + read goroutine + write goroutine,也就是有一個被不斷復用的TCP連接。VyT28資訊網——每日最新資訊28at.com

在第二例子中,當我們在每次client中都創建一個新的http.Transport,此時就會輸出11。VyT28資訊網——每日最新資訊28at.com

說明TCP連接沒有復用,每次請求都會產生新的連接。這是因為每個http.Transport內都會維護一個自己的空閑連接池,如果每個client都創建一個新的http.Transport,就會導致底層的TCP連接無法復用。如果網絡請求過大,上面這種情況會導致協程數量變得非常多,導致服務不穩定。VyT28資訊網——每日最新資訊28at.com

因此,最佳實踐是所有client都共用一個transport。VyT28資訊網——每日最新資訊28at.com

func main() {    tr := &http.Transport{        MaxIdleConns:    100,        IdleConnTimeout: 3 * time.Second,    }    n := 5    for i := 0; i < n; i++ {        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)        req.Header.Add("content-type", "application/json")        client := &http.Client{            Transport: tr,            Timeout:   3 * time.Second,        }        resp, _ := client.Do(req)        _, _ = ioutil.ReadAll(resp.Body)        _ = resp.Body.Close()    }    time.Sleep(time.Second * 1)    fmt.Printf("goroutine num is %d/n", runtime.NumGoroutine())}

如果創建客戶端的時候不指定http.Client,會默認所有http.Client都共用同一個DefaultTransport。這一點可以從源碼里看出。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

默認使用DefaultTransportVyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

DefaultTransportVyT28資訊網——每日最新資訊28at.com

因此當第二段代碼中,每次都重新創建一個Transport的時候,每個Transport內都會各自維護一個空閑連接池。因此每次建立長連接后都會多兩個協程(讀+寫),對應1個main goroutine+(read goroutine + write goroutine)* 5 =11。VyT28資訊網——每日最新資訊28at.com

別設置 Transport.Dail里的SetDeadline

http.Transport.Dial的配置里有個SetDeadline,它表示連接建立后發送接收數據的超時時間。聽起來跟client.Timeout很像。VyT28資訊網——每日最新資訊28at.com

那么他們有什么區別呢?我們通過一個例子去看下。VyT28資訊網——每日最新資訊28at.com

package mainimport (    "bytes"    "encoding/json"    "fmt"    "io/ioutil"    "net"    "net/http"    "time")var tr *http.Transportfunc init() {    tr = &http.Transport{        MaxIdleConns: 100,        Dial: func(netw, addr string) (net.Conn, error) {            conn, err := net.DialTimeout(netw, addr, time.Second*2) //設置建立連接超時            if err != nil {                return nil, err            }            err = conn.SetDeadline(time.Now().Add(time.Second * 3)) //設置發送接受數據超時            if err != nil {                return nil, err            }            return conn, nil        },    }}func main() {    for {        _, err := Get("http://www.baidu.com/")        if err != nil {            fmt.Println(err)            break        }    }}func Get(url string) ([]byte, error) {    m := make(map[string]interface{})    data, err := json.Marshal(m)    if err != nil {        return nil, err    }    body := bytes.NewReader(data)    req, _ := http.NewRequest("Get", url, body)    req.Header.Add("content-type", "application/json")    client := &http.Client{        Transport: tr,    }    res, err := client.Do(req)    if res != nil {        defer res.Body.Close()    }    if err != nil {        return nil, err    }    resBody, err := ioutil.ReadAll(res.Body)    if err != nil {        return nil, err    }    return resBody, nil}

上面這段代碼,我們設置了SetDeadline為3s,當你執行一段時間,會發現請求baidu會超時,但其實baidu的接口很快,不可能超過3s。VyT28資訊網——每日最新資訊28at.com

在生產環境中,假如是你的服務調用下游服務,你看到的現象就是,你的服務顯示3s超時了,但下游服務可能只花了200ms就已經響應你的請求了,并且這是隨機發生的問題。遇到這種情況,我們一般會認為是“網絡波動”。VyT28資訊網——每日最新資訊28at.com

但如果我們去對網絡抓包,就很容易發現問題的原因 。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

抓包結果VyT28資訊網——每日最新資訊28at.com

可以看到,在tcp三次握手之后,就會開始多次網絡請求。直到3s的時候,就會觸發RST包,斷開連接。也就是說,我們設置的SetDeadline,并不是指單次http請求的超時是3s,而是指整個tcp連接的存活時間是3s,計算長連接被連接池回收,這個時間也不會重置。VyT28資訊網——每日最新資訊28at.com

VyT28資訊網——每日最新資訊28at.com

SetDeadline的解釋VyT28資訊網——每日最新資訊28at.com

我實在想不到什么樣的場景會需要這個功能,因此我的建議是,不要使用它。VyT28資訊網——每日最新資訊28at.com

下面是修改后的代碼。這個問題其實在我另外一篇文章有過詳細的解釋,如果你對源碼解析感興趣的話,可以去看看。VyT28資訊網——每日最新資訊28at.com

package mainimport (    "bytes"    "encoding/json"    "fmt"    "io/ioutil"    "net/http"    "time")var tr *http.Transportfunc init() {    tr = &http.Transport{        MaxIdleConns: 100,        // 下面的代碼被干掉了        //Dial: func(netw, addr string) (net.Conn, error) {        // conn, err := net.DialTimeout(netw, addr, time.Second*2) //設置建立連接超時        // if err != nil {        //  return nil, err        // }        // err = conn.SetDeadline(time.Now().Add(time.Second * 3)) //設置發送接受數據超時        // if err != nil {        //  return nil, err        // }        // return conn, nil        //},    }}func Get(url string) ([]byte, error) {    m := make(map[string]interface{})    data, err := json.Marshal(m)    if err != nil {        return nil, err    }    body := bytes.NewReader(data)    req, _ := http.NewRequest("Get", url, body)    req.Header.Add("content-type", "application/json")    client := &http.Client{        Transport: tr,        Timeout: 3*time.Second,  // 超時加在這里,是每次調用的超時    }    res, err := client.Do(req)     if res != nil {        defer res.Body.Close()    }    if err != nil {        return nil, err    }    resBody, err := ioutil.ReadAll(res.Body)    if err != nil {        return nil, err    }    return resBody, nil}func main() {    for {        _, err := Get("http://www.baidu.com/")        if err != nil {            fmt.Println(err)            break        }    }}

總結

golang的net/http部分有不少細節點,直接上源碼分析怕勸退不少人,所以希望以幾個例子作為引子展開話題然后深入了解它的內部實現。總體內容比較碎片化,但這個庫的重點知識點基本都在這里面了。希望對大家后續排查問題有幫助。VyT28資訊網——每日最新資訊28at.com

本文鏈接:http://m.www897cc.com/showinfo-26-5715-0.htmlGo的Net/Http有哪些值得關注的細節?

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 警惕 C++ 中的隱式類型轉換

下一篇: 計算機底層原理~CPU緩存一致性

標簽:
  • 熱門焦點
Top 日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不
国产色爱av资源综合区| 国产婷婷色综合av蜜臀av| 久久影院亚洲| 裸体歌舞表演一区二区| 欧美连裤袜在线视频| 欧美一区二区精美| 久久一本综合频道| 欧美日韩另类字幕中文| 国产精品亚洲产品| 国产精品扒开腿做爽爽爽软件| 国产精品乱码人人做人人爱| 欧美女同视频| 国产欧美精品| 亚洲国产精品成人久久综合一区| 国产精品区一区| 激情伊人五月天久久综合| 日韩西西人体444www| 性做久久久久久久免费看| 久久综合给合久久狠狠狠97色69| 欧美日韩在线三区| 欧美美女日韩| 国产视频一区免费看| 亚洲黄色高清| 亚洲欧美国产毛片在线| 一区二区电影免费观看| 99精品福利视频| 久久se精品一区精品二区| 你懂的网址国产 欧美| 国产精品人人做人人爽| 国产精品网站在线播放| 亚洲国产精品一区二区第一页 | 国产视频在线观看一区| 亚洲精品视频啊美女在线直播| 性欧美办公室18xxxxhd| 欧美伦理在线观看| 伊人成人在线| 亚洲综合999| 欧美精品色综合| 精品99视频| 午夜久久久久久久久久一区二区| 欧美电影在线| 国产综合色在线视频区| 在线欧美日韩| 午夜欧美大片免费观看| 欧美日韩1234| 亚洲国产成人精品久久| 最新国产の精品合集bt伙计| 性亚洲最疯狂xxxx高清| 欧美日韩在线三区| 亚洲人成网站999久久久综合| 久久精品国产999大香线蕉| 国产精品国产三级国产| 日韩视频在线播放| 欧美1区视频| 国内一区二区三区| 亚洲国产成人在线视频| 欧美一区成人| 国产精品剧情在线亚洲| 精品av久久707| 小黄鸭视频精品导航| 国产精品ⅴa在线观看h| 99在线精品免费视频九九视| 欧美成年人视频网站| 精品69视频一区二区三区| 午夜精品久久久久久久久久久 | 国产亚洲欧洲一区高清在线观看| 亚洲午夜av电影| 欧美日韩亚洲一区| 日韩视频在线观看| 欧美精品久久一区| 亚洲精品一区二区三区不| 嫩草国产精品入口| 国产精品成人一区二区网站软件| 亚洲精品视频在线观看免费| 欧美大成色www永久网站婷| 在线欧美电影| 午夜精品福利一区二区蜜股av| 国产精品av久久久久久麻豆网| 中文精品一区二区三区| 欧美视频一区| 1024精品一区二区三区| 久久免费黄色| 激情久久久久久久| 久久亚洲国产成人| 亚洲电影视频在线| 欧美粗暴jizz性欧美20| 91久久夜色精品国产网站| 欧美国产日本韩| 99精品国产热久久91蜜凸| 久久精品在这里| 黄色影院成人| 免费观看亚洲视频大全| 91久久久久久| 欧美日本亚洲视频| 亚洲午夜精品久久久久久浪潮 | 国产欧美日韩激情| 久久精彩视频| 亚洲国产一区二区视频| 欧美激情视频在线免费观看 欧美视频免费一 | 精品二区视频| 欧美成人午夜激情在线| 亚洲美女色禁图| 欧美偷拍另类| 欧美一进一出视频| 精品1区2区| 欧美国产日韩精品| 亚洲视频 欧洲视频| 国产女人aaa级久久久级| 久久久久久9999| 国产欧美一区二区三区久久人妖| 欧美一区影院| 欧美美女bbbb| 亚洲欧美日韩国产成人精品影院| 国产午夜精品理论片a级探花| 亚洲一区自拍| 国产日韩欧美中文| 噜噜噜噜噜久久久久久91 | 国产一区视频在线观看免费| 蜜桃久久精品乱码一区二区| 99在线观看免费视频精品观看| 国产精品久久久久久久久搜平片 | 国产精品久久久久久久久动漫| 欧美诱惑福利视频| 亚洲国产裸拍裸体视频在线观看乱了| 欧美日韩精品一区二区天天拍小说| 亚洲自拍啪啪| 欧美黄色网络| 亚洲小说欧美另类社区| 国产伪娘ts一区| 欧美激情久久久久久| 亚洲欧美日韩视频一区| 在线观看欧美| 国产精品videosex极品| 久久久蜜桃一区二区人| 亚洲最新合集| 国产一区二区0| 欧美精品日韩精品| 久久动漫亚洲| 99视频精品| 精品999在线观看| 国产精品高潮粉嫩av| 免费成人av资源网| 翔田千里一区二区| 99精品福利视频| 激情久久五月天| 国产精品久久国产精品99gif | 亚洲国产一区二区三区a毛片| 国产精品麻豆va在线播放| 美腿丝袜亚洲色图| 午夜视频一区二区| 99人久久精品视频最新地址| 伊人久久综合97精品| 国产麻豆视频精品| 久久精品欧美日韩| 极品尤物久久久av免费看| 欧美手机在线| 欧美gay视频激情| 欧美一区二区黄色| 99在线热播精品免费| 在线观看91精品国产麻豆| 国产精品入口麻豆原神| 欧美精品在线一区二区| 久久久一二三| 午夜一级久久| 中国av一区| 国产亚洲毛片在线| 欧美日韩综合不卡| 免费在线看一区| 久久精品伊人| 亚洲欧美日韩综合aⅴ视频| 日韩视频免费观看高清在线视频| 激情视频亚洲| 国产亚洲欧美日韩在线一区 | 9l国产精品久久久久麻豆| 在线看片日韩| 国产一区二区三区日韩| 女人香蕉久久**毛片精品| 欧美伊人久久大香线蕉综合69| 一区二区三区高清| 亚洲美女免费精品视频在线观看| 亚洲国产精品久久久久婷婷884 | 国产精品任我爽爆在线播放 | 国产精品v日韩精品| 欧美另类高清视频在线| 欧美v亚洲v综合ⅴ国产v| 久久久人成影片一区二区三区| 欧美亚洲一区二区在线观看| 亚洲一区国产一区| 中文在线资源观看网站视频免费不卡| 亚洲久久在线| 亚洲精品色婷婷福利天堂| 亚洲国产三级在线| 亚洲电影在线免费观看| 在线精品观看| 在线日韩中文字幕| 韩日欧美一区| 激情久久久久久| 在线播放中文字幕一区| 在线观看欧美日韩| 亚洲高清视频一区| 91久久国产综合久久蜜月精品| 91久久在线观看| 亚洲精品视频中文字幕| 99re8这里有精品热视频免费 |