Golang中基于HTTP協(xié)議的網(wǎng)絡服務
一、HTTP協(xié)議的網(wǎng)絡服務
HTTP協(xié)議是基于TCP/IP協(xié)議棧的,并且它也是一個面向普通文本的協(xié)議。
只要搞清楚了HTTP請求的報文(報文的頭部(header)和主體(body))應該包含的內(nèi)容,使用任何一個文本編譯器,就餓可以編寫一個完整的HTTP請求報文。
在這種情況下,直接使用net.Dial
函數(shù),就可以。
使用net/http
代碼包中的程序?qū)嶓w,可以更便捷的訪問基于HTTP協(xié)議的網(wǎng)絡服務。其中最便捷的是使用http.Get
函數(shù)。
1.1 使用http.Get
函數(shù)訪問HTTP協(xié)議的網(wǎng)絡服務
package main import ( "fmt" "net/http" ) func main() { url1 := "http://www.google.cn/" fmt.Printf("Send request to %q with method GET ... \n", url1) response1, err := http.Get(url1) if err != nil { fmt.Printf("request sending error: %v\n", err) } defer response1.Body.Close() line1 := response1.Proto + " " + response1.Status fmt.Printf("The first line of response: \n %s \n", line1) }
http.Get
函數(shù)會返回兩個結(jié)果值:
- 第一個結(jié)果值的類型是*http.Response,它是網(wǎng)絡服務給我們傳回來的響應內(nèi)容的結(jié)構化表示。
- 第二個結(jié)果值是error類型。它代表了在創(chuàng)建和發(fā)送HTTP請求,以及接受和解析HTTP響應的過程中可能發(fā)生的錯誤。
在http.Get
函數(shù)內(nèi)部會使用缺省的HTTP客戶端,并調(diào)用它的Get方法以完成功能。缺省客戶端類型是*http.Client
,由公開變量DefaultClient代表。
1.2 使用缺省客戶端DefaultClient(類型為*http.Client
)
package main import ( "fmt" "net/http" ) func main() { url1 := "http://www.google.cn/" fmt.Printf("Send request to %q with method GET ... \n", url1) // response1, err := http.Get(url1) response1, err := http.DefaultClient.Get(url1) if err != nil { fmt.Printf("request sending error: %v\n", err) } defer response1.Body.Close() line1 := response1.Proto + " " + response1.Status fmt.Printf("The first line of response: \n %s \n", line1) }
它的基本類型(http.Client
)可以開箱即用。
1.3 使用http.Client
訪問HTTP協(xié)議的網(wǎng)絡服務
package main import ( "fmt" "net/http" ) func main() { url1 := "http://www.google.cn/" fmt.Printf("Send request to %q with method GET ... \n", url1) // response1, err := http.Get(url1) // response1, err := http.DefaultClient.Get(url1) var oneClient http.Client response1, err := oneClient.Get(url1) if err != nil { fmt.Printf("request sending error: %v\n", err) } defer response1.Body.Close() line1 := response1.Proto + " " + response1.Status fmt.Printf("The first line of response: \n %s \n", line1) }
http.Client
是一個結(jié)構體類型,并且它包含的字段是公開的。之所以該類型的零值仍然可以使用,是因為它的這些字段要么存在著響應的缺省值,要么其零值直接可以使用,且代表著特定的含義。
二、http.Client中的Transport字段
http.Client
類型中的Transport字段代表著:向網(wǎng)絡服務發(fā)送HTTP請求,并從網(wǎng)絡服務接收HTTP響應的操作過程。
Transport字段的RoundTrip方法實現(xiàn)單次HTTP事務(或者說基于HTTP協(xié)議的單詞交互)需要的所有步驟。
Transport 字段是http.RoundTrip
接口類型,它有一個缺省值,這個缺省值的變量名為DefaultTransport。DefaultTransport的實際類型為*http.Transport
,*http.Transport
可以被復用,并且是線程安全的。
如果沒有顯式的為
http.Client
中的Transport字段賦值,這個Client就會直接使DefaultTransport。
http.Client
中的Timeout字段,代表前面所說的單詞HTTP事務的超時時間,它time.Duration
類型,它的零值是可用的,用于表示沒有設置超時時間。
(1)http.Transport
類型中的DialContext字段
http.Transport
類型,在內(nèi)部使用一個net.Dialer
類型的值,并且會把該值的Timeout字段的值,設定為30秒。
也就是說,這個Dialer值如果在30秒內(nèi)還沒有建立好網(wǎng)絡連接,那么就會被判定為操作超時。
在DefaultTransport的值被初始化的時候,這樣的Dialer值的DialContext方法會被賦給前者DialContext字段:
var DefaultTransport RoundTripper = &Transport{ Proxy: ProxyFromEnvironment, DialContext: defaultTransportDialContext(&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }), ForceAttemptHTTP2: true, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, } func defaultTransportDialContext(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) { return dialer.DialContext }
KeepAlive的背后是一種針對網(wǎng)絡連接(更確切地說,是TCP連接)的存活探測機制。它的值用于表示每隔多長時間發(fā)送一次探測包。當該值不大于0時,則表示不開啟這種機制。
DefaultTransport會把這個字段的值設定為30秒。
(2)http.Transport
類型中的其它字段
一些是關于超時操作
IdleConnTimeout
:含義是空閑的連接在多久之后就應該關閉。
DefaultTransport 會把該字段的值設定為90秒。
如果該值為0,那么就表示不關閉空閑連接。注意,這樣可能會造成資源的泄露。
ResponseHeaderTimeout
:含義是,從客戶端把請求完全遞交給操作系統(tǒng)到從操作系統(tǒng)那里接收到響應報文頭到最長時長。
DefaultTransport并沒有設定該字段的值。
-
ExpectContinueTimeout
:含義是,在客戶端提交了請求報文頭之后,等待接收第一個響應報文頭的最長時間。
DefaultTransport 把該字段的值設定為1秒。
在客戶端想要使用HTTP的“POST”方法把一個很大的報文體發(fā)送給服務端的時候,它可以先通過發(fā)送一個包含了“Expect: 100-continue”的請求報文頭,來詢問服務端是否愿意接受這個大報文體。這個字段就是用于設定在這種情況下的超時時間的。
注意,如果該字段的值不大于0,那么無論多大的請求報文體都將會被立即發(fā)送出去。
TLSHandshakeTimeout
:TLS是Transport Layer Security 的縮寫,可以被翻譯為傳輸層安全。這個字段代表了基于TLS協(xié)議的連接在被建立時的握手階段的超時時間。
DefaultTransport 把該字段的值設置為10秒。
若該值為0,則表示對這個值不設限。
一些與IdleConnTimeout
相關的字段值
MaxIdleConns
:用于控制訪問所有主機的最大空閑連接。如果為0,不做限制。
DefaultTransport 把MaxIdleConns設定為100。
MaxIdleConns字段只會對空閑連接的總數(shù)做出限定。
MaxIdleConnsPerHost
: 控制Transport值訪問每一個網(wǎng)絡服務的最大空閑連接數(shù)。如果為0,將使用缺省值2, 這個缺省值由DefaultMaxIdleConnsPerHost
所代表。
也就是說,默認情況下,對于某一個Transport值訪問的每一個網(wǎng)絡服務,它的空閑連接數(shù)都最多只能由兩個。
MaxConnsPerHost
:針對某一個Transport值訪問的每一個網(wǎng)絡服務的最大連接數(shù),不論這些連接是否是空閑的。
該字段沒有缺省值,零值表示不限定。
MaxIdleConns和MaxIdleConnsPerHost兩個與空閑連接數(shù)有關的字段的值應該是聯(lián)動的,所以,有時需要根據(jù)實際情況定制它們,可以參考DefaultTransport變量的聲明。
三、為什么會出現(xiàn)空閑的連接
3.1 空閑連接的產(chǎn)生
HTTP協(xié)議有一個請求報文頭,叫做“Connection”。在HTTP協(xié)議的1.1 版本中,這個報文頭的值默認是“keep-alive”。
在這種情況下,網(wǎng)絡連接都是持久連接,它們會在當前的HTTP事務完成后仍然保持著連通性,因此是可以被復用的。
連接的可復用,帶來兩種可能:
- 一種可能是,針對同一個網(wǎng)絡服務,有新的HTTP請求被提交,該連接被再次使用。
- 另一種可能是,不再有對該網(wǎng)絡服務的HTTP請求,該連接被閑置。(產(chǎn)生空閑的連接)
后一種情況就產(chǎn)生了空閑連接。另外,如果分配給某一個網(wǎng)絡服務的連接過多的話,也可能會導致空閑連接的產(chǎn)生。因為每一個新遞交的HTTP請求,都只會征用一個空閑的連接。所以,為空閑連接設定限制,在大多數(shù)情況下都是很有必要的,也是需要斟酌的。
3.2 杜絕空閑連接的產(chǎn)生
如果想徹底杜絕空閑連接的產(chǎn)生,那么可以在初始化的時候,把它的DisableKeepAlives字段的值設定為true。這時,HTTP請求的“Connection”報文頭的值就會被設置為“close”。這會告訴網(wǎng)絡服務,這個網(wǎng)絡連接不必保持,當前的HTTP事務完成后就可以斷開它。
如此一來,每當一個HTTP請求被遞交時,就會產(chǎn)生一個新的網(wǎng)絡連接。這樣做會明顯地加重網(wǎng)絡服務以及客戶端的負載。所以,在一般情況下,我們都不要去設置這個DisableKeepAlive字段。
在net.Dialer類型中,也有一個看起來很相似的字段KeepAlive。不過,它與前面所說的HTTP 持久連接不是一個概念,KeepAlive是直接作用在底層的socket上的。
KeepAlive的背后是一種針對網(wǎng)絡連接(更確切地說,是TCP連接)的存活探測機制。它的值用于表示每隔多長時間發(fā)送一次探測包。當該值不大于0時,則表示不開啟這種機制。DefaultTransport會把這個字段的值設定為30秒。
四、http.Server
http.Server類型與http.Client相對應。http.Server代表的是基于HTTP協(xié)議的服務端,或者網(wǎng)絡服務。
4.1 http.Server類型的ListenAndServe方法
http.Server類型的ListenAndServe方法的功能是:監(jiān)聽一個基于TCP協(xié)議的網(wǎng)絡地址,并對接收到的HTTP請求進行處理。
- 這個方法默認會開啟針對網(wǎng)絡連接的存活探測機制,以保證連接是持久的。
- 同時,該方法會一直執(zhí)行,直到有嚴重的錯誤發(fā)生或被外界關掉。
當被外界關掉時,它會返回一個由http.ErrServerClosed
變量代表的錯誤值。
4.2 ListenAndServe方法主要做的事情
func (srv *Server) ListenAndServe() error { if srv.shuttingDown() { return ErrServerClosed } addr := srv.Addr if addr == "" { addr = ":http" } ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(ln) }
ListenAndServe
方法主要會做下面的事情:
- 檢查當前的
http.Server
類型的值的Addr字段。
該字段的值代表了當前的網(wǎng)絡服務需要使用的網(wǎng)絡地址。即:IP地址和端口號。如果這個字段的值為空字符串,那么就用":http"代替。
也就是說,使用任何可以代表本機的域名和IP地址,并且端口號為80.
- 通過調(diào)用
net.Listen
函數(shù)在已確定的網(wǎng)絡地址上啟動基于TCP協(xié)議的監(jiān)聽。 - 檢查
net.Listen
函數(shù)返回的錯誤值。
如果該錯誤值不為nil,那么就直接返回該值。否則,通過調(diào)用當前值的Serve方法準備接受和處理將要到來的HTTP請求。
4.3 (衍生問題)net.Listen 函數(shù)都做了哪些事情
net.Listen
函數(shù)做的事情:
- 解析參數(shù)值中包含的網(wǎng)絡地址隱含的IP地址和端口號;
- 根據(jù)給定的網(wǎng)絡協(xié)議,確定監(jiān)聽的方法,并開始進行監(jiān)聽;
這里還可以延伸到net.socket函數(shù),以及socket相關的知識。
4.4 (衍生問題)http.Server類型的Serve方法是怎么接受和處理HTTP請求的
在一個for循環(huán)中,網(wǎng)絡監(jiān)聽的Accept方法會被不斷的調(diào)用,
for { rw, err := l.Accept() }
該方法會返回兩個結(jié)果值:
- 第一個結(jié)果值是net.Conn 類型,代表包含了新到來的HTTP請求的網(wǎng)絡連接;
- 第二個結(jié)果值是error類型值,代表可能發(fā)生的錯誤。
如果錯誤不為nil,除非它代表了一個暫時性的錯誤,否則循環(huán)都會被終止。如果是暫時性的錯誤,那么循環(huán)的下一次迭代將會在一段時間之后開始執(zhí)行。
如果這里的Accept方法沒有返回非nil的錯誤值,那么這里的程序?qū)阉牡谝粋€結(jié)果值包裝成一個*http.conn類型的值,然后通過在新的goroutine中調(diào)用這個*http.conn 類型值的serve方法,來對當前的HTTP請求進行處理。
HTTP請求相關的,更多的衍生問題:
- 這個*http.conn類型值的狀態(tài)有幾種,分別代表著處理的哪個階段?
- 處理的過程中會用到哪些讀取器和寫入器,它們的作用分別是什么?
- 這里的程序是怎么調(diào)用我們自定義的處理函數(shù)的?
五、思考:怎么優(yōu)雅地停止基于HTTP協(xié)議的網(wǎng)絡服務程序?
srv.Shutdown(context.Background())
的方式停止服務,通過RegisterOnShutdown可添加服務停止時的調(diào)用。
以上就是Golang中基于HTTP協(xié)議的網(wǎng)絡服務的詳細內(nèi)容,更多關于Golang HTTP協(xié)議的資料請關注腳本之家其它相關文章!
相關文章
Go創(chuàng)建Grpc鏈接池實現(xiàn)過程詳解
這篇文章主要為大家介紹了Go創(chuàng)建Grpc鏈接池實現(xiàn)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03解決Golang中goroutine執(zhí)行速度的問題
這篇文章主要介紹了解決Golang中goroutine執(zhí)行速度的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-05-05