net/http 是什麼?
是GO的其中一個標準庫,用於Web應用的開發,使用這個庫,可以讓開發變得更加迅速和簡便,且易於上手。
那麼問題來了
使用庫,確實方便,無腦調介面,拼拼湊湊能跑就行,管他效率效能,出了問題,刪庫跑路就行了。。。
實際真的是這個樣子嗎?作為一個開發,一定要想辦法弄明白不清楚的事情,要弄明白用到工具的原理,更需要清晰的知道自己開發產品的運作原理,正所謂
知其然,而不知其所以然,欲摹寫其情狀,而心不能自喻,口不能自宣,筆不能自傳。
我們對於技術要有探索精神,對程式碼要有敬畏之心,那今天我們們就來看看net/http
的程式碼流程吧
使用框架/庫,必要要接受其自身的一套約定和模式,我們必須要了解和熟悉這些約定和模式的用法,否則就會陷入用錯了都不知道的境地。
在GOLANG中,net/http
的組成部分有客戶端 和 服務端
庫中的結構和函式有的只支援客戶端和伺服器這兩者中的一個,有的同時支援客戶端和伺服器,用圖說話:
- 只支援客戶端的
Client , response
- 只支援服務端的
ServerMux,Server ,ResponseWriter,Handler 和 HandlerFunc
- 客戶端,服務端都支援的
Header , Request , Cookie
net/http構建伺服器也很簡單,大體框架如下:
客戶端 請求 伺服器,伺服器裡面使用 net/http
包,包中有多路複用器,和對應多路複用器的介面,伺服器中的多個處理器處理不同的請求,最終需要落盤的資料即入庫
萬里長城第一步,我們發車了
開始寫一個簡單的Request
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/Hi", func(w http.ResponseWriter, r *http.Request) {
// <h1></h1> 是html 標籤
w.Write([]byte("<h1>Hi xiaomotong</h1>"))
})
// ListenAndServe 不寫ip 預設伺服器地址是 127.0.0.1
if err := http.ListenAndServe(":8888", nil); err != nil {
fmt.Println("http server error:", err)
}
}
執行伺服器程式碼,在瀏覽器中輸入127.0.0.1:8888/Hi
或者localhost:8888/Hi
,即可看到效果
建立一個Go寫的伺服器就是那麼簡單,只要呼叫ListenAndServe
並傳入網路地址,埠,處理請求的處理器(handler)即可。
注意:
- 如果網路地址引數為空字串,那麼伺服器預設使用80埠進行網路連線
- 如果處理器引數為
nil
,那麼伺服器將使用預設的多路複用器DefaultServeMux
。
可是實際上http是如何建立起來的呢?一頓操作猛如虎,一問細節二百五
HTTP的建立過程
HTTP的建立流程都是通用的,因為他是標準協議。寫C/C++
的時候,這些流程基本上自己都要去寫一遍,但是寫GO
的時候,標準庫裡面已經封裝好了,因此才會有上述一個函式就可以寫一個web伺服器的情況
服務端涉及的流程
- socket建立套接字
- bind繫結地址和埠
- listen設定最大監聽數
- accept開始阻塞等待客戶端的連線
- read讀取資料
- write回寫資料
- close 關閉
客戶端涉及的流程
- socket建立套接字
- connect 連線服務端
- write寫資料
- read讀取資料
那麼資料在各個層級之間是如何走的呢?
還是那個熟悉的7層OSI
模型,不過實際應用的話,我們用TCP/IP
5層模型
上述TCP/IP五層模型,可能會用到的協議大體列一下
應用層:
HTTP協議,SMTP,SNMP,FTP,Telnet,SIP,SSH,NFS,RTSP
傳輸層
比較常見的協議是TCP,UDP,SCTP,SPX,ATP等
UDP不可靠, SCTP有自己特殊的運用場景, 所以一般情況下HTTP是由TCP協議進行傳輸
不過企業應用的話,會將UDP改造成可靠的傳輸,實際上是對標準udp上封裝自定義的頭,模擬TCP的可靠傳輸,三次握手, 四次揮手就是在這裡發生的
- 網路層
IP協議、ICMP,IGMP,IPX,BGP,OSPF,RIP,IGRP,EIGRP,ARP,RARP協議 ,等等
- 資料鏈路層
Ethernet , PPP,WiFi ,802.11等等
- 物理層
SO2110,IEEE802 等等
知道HTTP的通用流程,那麼我們來具體看看net/http
標準庫是如何實現這整個流程的,先從建立socket看起
net/http 建立socket
還記得最上面說到的request小案例嗎?我們可以從這裡開始入手
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/Hi", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("<h1>Hi xiaomotong</h1> "))
})
if err := http.ListenAndServe(":8888", nil); err != nil {
fmt.Println("http server error:", err)
}
}
http.HandleFunc(“/Hi”, func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(“
})
HandleFunc這一段是註冊路由,這個路由的handler會預設放到到DefaultServeMux
中
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
HandleFunc 實際上是呼叫了 ServeMux
服務的HandleFunc
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
ServeMux
服務的HandleFunc
呼叫了自己服務的Handle
實現
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
看了實際的註冊路由實現還是比較簡單,我們先不再深入的網下看,要是感興趣的可以接著這裡往下追具體的資料結構
目前的註冊路由的流程是:
- http.HandleFunc ->
- (mux *ServeMux) HandleFunc ->
- (mux *ServeMux) Handle
net/http監聽埠+響應請求
那我們在來看看剛才request案例裡面的監聽地址和埠的程式碼是如何走的
if err := http.ListenAndServe(“:8888”, nil); err != nil {
fmt.Println(“http server error:”, err)
}
經過上面的三個函式流程,已經知道註冊路由是如何走的了,那麼ListenAndServe
這個函式的監聽已經handler處理資料後的響應是如何實現的呢?來我們繼續
ListenAndServe
偵聽TCP網路地址addr,然後呼叫handler來處理傳入連線的請求,收的連線配置為啟用TCP keep-alive,該引數通常為nil,在這種情況下使用DefaultServeMux
,上面提過一次,此處再次強調
// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe() // 呼叫Server服務的 ListenAndServe函式 (srv *Server) ListenAndServe
}
// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// If srv.Addr is blank, ":http" is used.
//
// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr) //實際是通過 net.Listen 進行監聽地址和埠的
if err != nil {
return err
}
return srv.Serve(ln)
}
func Listen(network, address string) (Listener, error) {
var lc ListenConfig
return lc.Listen(context.Background(), network, address)
}
func (srv *Server) Serve(l net.Listener) error {
if fn := testHookServerServe; fn != nil {
fn(srv, l) // call hook with unwrapped listener // 回撥函式的呼叫的位置
}
// ...此處省略15行程式碼...
var tempDelay time.Duration // how long to sleep on accept failure // accept阻塞失敗睡眠的間隔時間
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
// 開始Accept 阻塞監聽客戶端的連線
rw, err := l.Accept()
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx) // 此處開一個協程來處理具體的請求訊息
}
}
此處通過 go c.serve(connCtx)
開啟一個協程專門處理具體的請求訊息
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
// ... 此處省略部分程式碼
// HTTP cannot have multiple simultaneous active requests.[*]
// Until the server replies to this request, it can't read another,
// so we might as well run the handler in this goroutine.
// [*] Not strictly true: HTTP pipelining. We could let them all process
// in parallel even if their responses need to be serialized.
// But we're not going to implement HTTP pipelining because it
// was never deployed in the wild and the answer is HTTP/2.
//HTTP不能同時有多個活動請求。[*],直到伺服器響應這個請求,它不能讀取另一個
serverHandler{c.server}.ServeHTTP(w, w.req) // ServeHTTP 是重點
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
// ... 此處省略部分程式碼
}
此處ServeHTTP
相當重要
// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
srv *Server
}
// 處理請求
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
(sh serverHandler) ServeHTTP
呼叫 (mux *ServeMux) ServeHTTP
, ServeHTTP將請求傳送給處理程式 h, _ := mux.Handler(r)
原始碼看到這裡,對於net/http標準庫 對於註冊路由,監聽服務端地址和埠的流程,大致清楚了吧
整個過程,net/http
基本上是提供了 HTTP流程的整套服務,可以說是非常的香了, 整個過程基本上是這個樣子的
- net.Listen 做了初始化 套接字 socket,bind 繫結ip 和埠,listen 設定最大監聽數量的 操作
- Accept 進行阻塞等待客戶端的連線
- go c.serve(ctx) 啟動新的協程來處理當前的請求. 同時主協程繼續等待其他客戶端的連線, 進行高併發操作
- mux.Handler獲取註冊的路由, 然後拿到這個路由的handler 處理器, 處理客戶端的請求後,返回給客戶端結果
關於底層是如何封包解包,位元組是如何偏移的,ipv4,ipv6如何去處理的,有興趣的朋友們可以順著程式碼繼續追,歡迎多多溝通交流
好了,本次就到這裡,下一次是 gin的路由演算法分享,
技術是開放的,我們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。
我是小魔童哪吒,歡迎點贊關注收藏,下次見~
本作品採用《CC 協議》,轉載必須註明作者和本文連結