[TOC]html
這是我參與更文挑戰的第 3 天,活動詳情查看: 更文挑戰
是GO的其中一個標準庫,用於Web應用的開發,使用這個庫,可讓開發變得更加迅速和簡便,且易於上手。web
那麼問題來了算法
使用庫,確實方便,無腦調接口,拼拼湊湊能跑就行,管他效率性能,出了問題,刪庫跑路就好了。。。瀏覽器
實際真的是這個樣子嗎?做爲一個開發,必定要想辦法弄明白不清楚的事情,要弄明白用到工具的原理,更須要清晰的知道本身開發產品的運做原理,正所謂服務器
知其然,而不知其因此然,欲摹寫其情狀,而心不能自喻,口不能自宣,筆不能自傳。
咱們對於技術要有探索精神,對代碼要有敬畏之心,那今天我們就來看看net/http
的代碼流程吧網絡
使用框架/庫,必要要接受其自身的一套約定和模式,咱們必需要了解和熟悉這些約定和模式的用法,不然就會陷入用錯了都不知道的境地。數據結構
在GOLANG中,net/http
的組成部分有客戶端 和 服務端併發
庫中的結構和函數有的只支持客戶端和服務器這二者中的一個,有的同時支持客戶端和服務器,用圖說話:app
Client , response框架
ServerMux,Server ,ResponseWriter,Handler 和 HandlerFunc
Header , Request , Cookie
net/http構建服務器也很簡單,大致框架以下:
客戶端 請求 服務器,服務器裏面使用 net/http
包,包中有多路複用器,和對應多路複用器的接口,服務器中的多個處理器處理不一樣的請求,最終須要落盤的數據即入庫
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)便可。
注意:
nil
,那麼服務器將使用默認的多路複用器DefaultServeMux
。但是實際上http是如何創建起來的呢?一頓操做猛如虎,一問細節二百五
HTTP的創建流程都是通用的,由於他是標準協議。寫C/C++
的時候,這些流程基本上本身都要去寫一遍,可是寫GO
的時候,標準庫裏面已經封裝好了,所以纔會有上述一個函數就能夠寫一個web服務器的狀況
服務端涉及的流程
客戶端涉及的流程
那麼數據在各個層級之間是如何走的呢?
仍是那個熟悉的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看起
還記得最上面說到的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("<h1>Hi xiaomotong</h1> "))})
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 } }
看了實際的註冊路由實現仍是比較簡單,咱們先再也不深刻的網下看,要是感興趣的能夠接着這裏往下追具體的數據結構
目前的註冊路由的流程是:
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流程的整套服務,能夠說是很是的香了, 整個過程基本上是這個樣子的
關於底層是如何封包解包,字節是如何偏移的,ipv4,ipv6如何去處理的,有興趣的朋友們能夠順着代碼繼續追,歡迎多多溝通交流
好了,本次就到這裏,下一次是 gin的路由算法分享,
技術是開放的,咱們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。
我是小魔童哪吒,歡迎點贊關注收藏,下次見~