<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          圖文講透Golang標(biāo)準(zhǔn)庫 net/http實(shí)現(xiàn)原理 -- 服務(wù)端

          共 13569字,需瀏覽 28分鐘

           ·

          2024-04-10 15:16

          前言

          今天分享下Go語言net/http標(biāo)準(zhǔn)庫的內(nèi)部實(shí)現(xiàn)邏輯,文章將從客戶端(Client)--服務(wù)端(Server)兩個(gè)方向作為切入點(diǎn),進(jìn)而一步步分析http標(biāo)準(zhǔn)庫內(nèi)部是如何運(yùn)作的。

          d4e403e1704d476a2cf7dbb6e503cd9f.webp

          由于會(huì)涉及到不少的代碼流程的走讀,寫完后覺得放在一篇文章中會(huì)過于長,可能在閱讀感受上會(huì)不算很好,因此分為【Server--Client兩個(gè)篇文章】進(jìn)行發(fā)布。

          本文內(nèi)容是【服務(wù)端Server部分】,文章代碼版本是Golang 1.19,文中會(huì)涉及較多的代碼,需要耐心閱讀,不過我會(huì)在盡量將注釋也邏輯闡述清楚。先看下所有內(nèi)容的大綱:

          050b3a6c4d4e7a773ff4ad8d7c987462.webp

          Go 語言的 net/http 中同時(shí)封裝好了 HTTP 客戶端和服務(wù)端的實(shí)現(xiàn),這里分別舉一個(gè)簡單的使用示例。

          Server啟動(dòng)示例

          Server和Client端的代碼實(shí)現(xiàn)來自net/http標(biāo)準(zhǔn)庫的文檔,都是簡單的使用,而且用很少的代碼就可以啟動(dòng)一個(gè)服務(wù)!

              
              
                http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
              fmt.Fprintf(w, "xiaoxu code")
          })
          http.ListenAndServe(":8080", nil)

          上面代碼中:

          HandleFunc 方法注冊了一個(gè)請求路徑 /hello 的 handler 函數(shù)

          ListenAndServe指定了8080端口進(jìn)行監(jiān)聽和啟動(dòng)一個(gè)HTTP服務(wù)端

          Client發(fā)送請求示例

          HTTP 包一樣可以發(fā)送請求,我們以Get方法來發(fā)起請求,這里同樣也舉一個(gè)簡單例子:

              
              
                resp, err := http.Get("http://example.com/")
          if err != nil {
              fmt.Println(err)
              return
          }
          defer resp.Body.Close()
          body, _ := ioutil.ReadAll(resp.Body)
          fmt.Println(string(body))

          是不是感覺使用起來還是很簡單的,短短幾行代碼就完成了http服務(wù)的啟動(dòng)和發(fā)送http請求,其背后是如何進(jìn)行封裝的,在接下的章節(jié)會(huì)講清楚!

          服務(wù)端 Server

          我們先預(yù)覽下圖過程,對(duì)整個(gè)服務(wù)端做的事情有個(gè)了解

          0fe62e0f9836e551dbc40cde37d64c6f.webp

          從圖中大致可以看出主要有這些流程:

          1. 1. 注冊handler到map中,map的key是鍵值路由

          2. 2. handler注冊完之后就開啟循環(huán)監(jiān)聽,監(jiān)聽到一個(gè)連接就會(huì)異步創(chuàng)建一個(gè) Goroutine

          3. 3. 在創(chuàng)建好的 Goroutine 內(nèi)部會(huì)循環(huán)的等待接收請求數(shù)據(jù)

          4. 4. 接受到請求后,根據(jù)請求的地址去處理器路由表map中匹配對(duì)應(yīng)的handler,然后執(zhí)行handler

          Server結(jié)構(gòu)體

              
              
                type Server struct {
              Addr string
              Handler Handler 
              mu         sync.Mutex
              ReadTimeout time.Duration
              WriteTimeout time.Duration
              IdleTimeout time.Duration
              TLSConfig *tls.Config
              ConnState func(net.Conn, ConnState)
              activeConn map[*conn]struct{}
              doneChan   chan struct{}
              listeners  map[*net.Listener]struct{}
              ...
          }

          我們在下圖中解釋了部分字段代表的意思

          93f10c367c4e49f44b87e07649351835.webp

          ServeMux結(jié)構(gòu)體

              
              
                type ServeMux struct {
              mu sync.RWMutex   
              m map[string]muxEntry 
              es []muxEntry    
              hosts bool     
          }

          字段說明:

          • ? sync.RWMutex:這是讀寫互斥鎖,允許goroutine 并發(fā)讀取路由表,在修改路由map時(shí)獨(dú)占

          • ? map[string]muxEntry:map結(jié)構(gòu)維護(hù)pattern (路由) 到 handler (處理函數(shù)) 的映射關(guān)系,精準(zhǔn)匹配

          • ? []muxEntry:存儲(chǔ) "/" 結(jié)尾的路由,切片內(nèi)按從最長到最短的順序排列,用作模糊匹配patter的muxEntry

          • ? hosts:是否有任何模式包含主機(jī)名

          Mux是【多路復(fù)用器】的意思,ServeMux就是服務(wù)端路由http請求的多路復(fù)用器。

          ?? 作用: 管理和處理程序來處理傳入的HTTP請求

          ?? 原理:內(nèi)部通過一個(gè) map類型 維護(hù)了從 pattern (路由) 到 handler (處理函數(shù)) 的映射關(guān)系,收到請求后根據(jù)路徑匹配找到對(duì)應(yīng)的處理函數(shù)handler,處理函數(shù)進(jìn)行邏輯處理。

          d561a224f1a5155eab081468807d359b.webp

          路由注冊

          通過對(duì)HandleFunc的調(diào)用追蹤,內(nèi)部的調(diào)用核心實(shí)現(xiàn)如下:

          808e5c40eeb8d287d13ec639410d0db1.webp

          了解完流程之后接下來繼續(xù)追函數(shù)看代碼

              
              
                var DefaultServeMux = &defaultServeMux
          // 默認(rèn)的ServeMux
          var defaultServeMux ServeMux

          // HandleFunc注冊函數(shù)
          func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
              DefaultServeMux.HandleFunc(pattern, handler)
          }

          DefaultServeMux是ServeMux的默認(rèn)實(shí)例。

              
              
                //接口
          type Handler interface {
              ServeHTTP(ResponseWriter, *Request)
          }

          //HandlerFunc為函數(shù)類型
          type HandlerFunc func(ResponseWriter, *Request)
          //實(shí)現(xiàn)了Handler接口
          func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
              f(w, r)
          }


          func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
              ...
              // handler是真正處理請求的函數(shù)
              mux.Handle(pattern, HandlerFunc(handler))
          }

          HandlerFunc函數(shù)類型是一個(gè)適配器,是Handler接口的具體實(shí)現(xiàn)類型,因?yàn)樗鼘?shí)現(xiàn)了ServeHTTP方法。

          ?? HandlerFunc(handler), 通過類型轉(zhuǎn)換的方式【handler -->HandlerFunc】將一個(gè)出入?yún)⑿问綖閒unc(ResponseWriter, *Request)的函數(shù)轉(zhuǎn)換為HandlerFunc類型,而HandlerFunc實(shí)現(xiàn)了Handler接口,所以這個(gè)被轉(zhuǎn)換的函數(shù)handler可以被當(dāng)做一個(gè)Handler對(duì)象進(jìn)行賦值。

          ?? 好處:HandlerFunc(handler)方式實(shí)現(xiàn)靈活的路由功能,方便的將普通函數(shù)轉(zhuǎn)換為Http處理程序,兼容注冊不同具體的業(yè)務(wù)邏輯的處理請求。

          你看,mux.Handle的第二個(gè)參數(shù)Handler就是個(gè)接口,ServeMux.Handle就是路由模式和處理函數(shù)在map中進(jìn)行關(guān)系映射。

          ServeMux.Handle

              
              func (mux *ServeMux) Handle(pattern string, handler Handler) {
              mux.mu.Lock()
              defer mux.mu.Unlock()
              // 檢查路由和處理函數(shù)
              ...
              //檢查pattern是否存在
              ...
              //如果 mux.m 為nil 進(jìn)行make初始化 map
              if mux.m == nil {
                  mux.m = make(map[string]muxEntry)
              }
              e := muxEntry{h: handler, pattern: pattern}
              //注冊好路由都會(huì)存放到mux.m里面
              mux.m[pattern] = e
              //patterm以'/'結(jié)尾
              if pattern[len(pattern)-1] == '/' {
                  mux.es = appendSorted(mux.es, e)
              }

              if pattern[0] != '/' {
                  mux.hosts = true
              }
          }

          Handle的實(shí)現(xiàn)主要是將傳進(jìn)來的pattern和handler保存在muxEntry結(jié)構(gòu)中,然后將pattern作為key,把muxEntry添加到DefaultServeMux的Map里。

          如果路由表達(dá)式以 '/' 結(jié)尾,則將對(duì)應(yīng)的muxEntry對(duì)象加入到[]muxEntry切片中,然后通過appendSorted對(duì)路由按從長到短進(jìn)行排序。

          ?? 注:

          map[string]muxEntry 的map使用哈希表是用于路由精確匹配

          []muxEntry用于部分匹配模式

          到這里就完成了路由和handle的綁定注冊了,至于為什么分了兩個(gè)模式,在后面會(huì)說到,接下來就是啟動(dòng)服務(wù)進(jìn)行監(jiān)聽的過程。

          監(jiān)聽和服務(wù)啟動(dòng)

          同樣的我用圖的方式監(jiān)聽和服務(wù)啟動(dòng)的函數(shù)調(diào)用鏈路畫出來,讓大家先有個(gè)印象。

          結(jié)合圖會(huì)對(duì)后續(xù)結(jié)合代碼邏輯更清晰,知道這塊代碼調(diào)用屬于哪個(gè)階段!

          0fe913e23d1e4bfaf60cf5030c041d53.webp

          ListenAndServe啟動(dòng)服務(wù):

              
              
                func (srv *Server) ListenAndServe() error {
              if srv.shuttingDown() {
                  return ErrServerClosed
              }
              addr := srv.Addr
              if addr == "" {
                  addr = ":http"
              }
              // 指定網(wǎng)絡(luò)地址并監(jiān)聽
              ln, err := net.Listen("tcp", addr)
              if err != nil {
                  return err
              }
              // 接收處理請求
              return srv.Serve(ln)
          }

          net.Listen 實(shí)現(xiàn)了TCP協(xié)議上監(jiān)聽本地的端口8080 (ListenAndServe()中傳過來的),Server.Serve接受 net.Listener實(shí)例傳入,然后為每個(gè)連接創(chuàng)建一個(gè)新的服務(wù)goroutine

          使用net.Listen函數(shù)實(shí)現(xiàn)網(wǎng)絡(luò)監(jiān)聽需要經(jīng)過以下幾個(gè)步驟:

          1. 1. 調(diào)用net.Listen函數(shù),指定網(wǎng)絡(luò)類型和監(jiān)聽地址。

          2. 2. 使用listener.Accept函數(shù)接受客戶端的連接請求。

          3. 3. 在一個(gè)獨(dú)立的goroutine中處理每個(gè)連接。

          4. 4. 在處理完連接后,調(diào)用conn.Close()來關(guān)閉連接

          Server.Serve:

              
              
                func (srv *Server) Serve(l net.Listener) error {
              origListener := l
              //內(nèi)部實(shí)現(xiàn)Once是只執(zhí)行一次動(dòng)作的對(duì)象
              l = &onceCloseListener{Listener: l}
              defer l.Close()
              ...
              ctx := context.WithValue(baseCtx, ServerContextKey, srv)
              for {
                  //rw為可理解為tcp連接
                  rw, err := l.Accept()
                  ...
                  connCtx := ctx
                  ...
                  c := srv.newConn(rw)
                  //
                  go c.serve(connCtx)
              }
          }

          使用 for + listener.accept 處理客戶端請求

          • ? 在for 循環(huán)調(diào)用 Listener.Accept 方法循環(huán)讀取新連接

          • ? 讀取到客戶端請求后會(huì)創(chuàng)建一個(gè) goroutine 異步執(zhí)行 conn.serve 方法負(fù)責(zé)處理

              
              
                type onceCloseListener struct {
              net.Listener
              once     sync.Once
              closeErr error
          }

          onceCloseListener 是sync.Once的一次執(zhí)行對(duì)象,當(dāng)且僅當(dāng)?shù)谝淮伪徽{(diào)用時(shí)才執(zhí)行函數(shù)。

          *conn.serve():

              
              
                func (c *conn) serve(ctx context.Context) {
              ...
              // 初始化conn的一些參數(shù)
              c.remoteAddr = c.rwc.RemoteAddr().String()
              c.r = &connReader{conn: c}
              c.bufr = newBufioReader(c.r)
              c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
              for {
                  // 讀取客戶端請求
                  w, err := c.readRequest(ctx)
                  ...
                  // 調(diào)用ServeHTTP來處理請求
                  serverHandler{c.server}.ServeHTTP(w, w.req)
              }
          }

          conn.serve是處理客戶端連接的核心方法,主要是通過for循環(huán)不斷循環(huán)讀取客戶端請求,然后根據(jù)請求調(diào)用相應(yīng)的處理函數(shù)。

          c.readRequest(ctx)方法是用來讀取客戶端的請求,然后返回一個(gè)response類型的w和一個(gè)錯(cuò)誤err

          最終是通過serverHandler{c.server}.ServeHTTP(w, w.req) 調(diào)用ServeHTTP處理連接客戶端發(fā)送的請求。

          OK,經(jīng)歷了前面監(jiān)聽的過程,現(xiàn)在客戶端請求已經(jīng)拿到了,接下來就是到了核心的處理請求的邏輯了,打起十二分精神哦!????

          請求處理

          serverHandler.ServeHTTP:

          上面說到的 serverHandler{c.server}.ServeHTTP(w, w.req) 其實(shí)就是下面函數(shù)的實(shí)現(xiàn)。

              
              
                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傳的是nil就執(zhí)行 DefaultServeMux.ServeHTTP() 方法
              handler.ServeHTTP(rw, req)
          }

          獲取Server的handler流程:

          1. 1. 先獲取 sh.srv.Handler 的值,判斷是否為nil

          2. 2. 如果為nil則取全局單例 DefaultServeMux這個(gè)handler

          3. 3. PTIONS Method 請求且 URI 是 *,就使用globalOptionsHandler

          ?? 注:這個(gè)handler其實(shí)就是在ListenAndServe()中的第二個(gè)參數(shù)

          ServeMux.ServeHTTP

              
              
                func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
              ....
              h, _ := mux.Handler(r)
              // 執(zhí)行匹配到的路由的ServeHTTP方法
              h.ServeHTTP(w, r)
          }

          ServeMux.ServeHTTP()方法主要代碼可以分為兩步:

          1. 1. 通過 ServerMux.Handler() 方法獲取到匹配的處理函數(shù) h

          2. 2. 調(diào)用 Handler.ServeHTTP() 執(zhí)行匹配到該路由的函數(shù)來處理請求 (h實(shí)現(xiàn)了ServeHTTP方法)

          635183f5bb157c6a15c616f0a98b9779.webp

          ServerMux.Handler():

              
              
                func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
              ...
              //在mux.m和mux.es中
              //根據(jù)host/url.path尋找對(duì)應(yīng)的handler
              return mux.handler(host, r.URL.Path)
          }

          在 ServeMux.Handler() 方法內(nèi)部,會(huì)調(diào)用 ServerMux.handler(host, r.URL.Path) 方法來查找匹配的處理函數(shù)。

          ServeMux.match

          ServeMux.match()方法用于根據(jù)給定的具體路徑 path 找到最佳匹配的路由,并返回Handler和路徑。

          值得一提的是,如果 mux.m 中不存在 path 完全匹配的路由時(shí),會(huì)繼續(xù)遍歷 mux.es 字段中保存的模糊匹配路由。

              
              
                func (mux *ServeMux) match(path string) (h Handler, pattern string) {
              // 是否完全匹配
              v, ok := mux.m[path]
              if ok {
                  return v.h, v.pattern
              }
              // mux.es是按pattern從長到短排列
              for _, e := range mux.es {
                  if strings.HasPrefix(path, e.pattern) {
                      return e.h, e.pattern
                  }
              }
              return nil, ""
          }

          最后調(diào)用 handler.ServeHTTP 方法進(jìn)行請求的處理和響應(yīng),而這個(gè)被調(diào)用的函數(shù)就是我們之前在路由注冊時(shí)對(duì)應(yīng)的函數(shù)。

              
              
                type HandlerFunc func(ResponseWriter, *Request)

          func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
              f(w, r)
          }

          到這里整個(gè)服務(wù)的流程就到這里了,現(xiàn)在有對(duì)這塊有印象了嗎?

          關(guān)于Client篇的文章會(huì)馬上更新,敬請期待,希望看完之后會(huì)對(duì)net/http有個(gè)新認(rèn)識(shí)!



          推薦閱讀


          福利
          我為大家整理了一份 從入門到進(jìn)階的Go學(xué)習(xí)資料禮包 ,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。 關(guān)注公眾號(hào) 「polarisxu」,回復(fù)  ebook  獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 56
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产成人短视频 | 风流老熟女一区二区三区 | 这里只有99精品 | 爱操综合 | 人人干人人摸 |