來咱們探究一下net/http的代碼流程

[TOC]html

這是我參與更文挑戰的第 3 天,活動詳情查看: 更文挑戰

探究一下net/http 的代碼流程

net/http 是什麼?

是GO的其中一個標準庫,用於Web應用的開發,使用這個庫,可讓開發變得更加迅速和簡便,且易於上手。web

那麼問題來了算法

使用庫,確實方便,無腦調接口,拼拼湊湊能跑就行,管他效率性能,出了問題,刪庫跑路就好了。。。瀏覽器

實際真的是這個樣子嗎?做爲一個開發,必定要想辦法弄明白不清楚的事情,要弄明白用到工具的原理,更須要清晰的知道本身開發產品的運做原理,正所謂服務器

知其然,而不知其因此然,欲摹寫其情狀,而心不能自喻,口不能自宣,筆不能自傳。

咱們對於技術要有探索精神,對代碼要有敬畏之心,那今天我們就來看看net/http的代碼流程吧網絡

使用框架/庫,必要要接受其自身的一套約定和模式,咱們必需要了解和熟悉這些約定和模式的用法,不然就會陷入用錯了都不知道的境地。數據結構

在GOLANG中,net/http的組成部分有客戶端服務端併發

庫中的結構和函數有的只支持客戶端和服務器這二者中的一個,有的同時支持客戶端和服務器,用圖說話:app

  • 只支持客戶端的

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("<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
   }
}

看了實際的註冊路由實現仍是比較簡單,咱們先再也不深刻的網下看,要是感興趣的能夠接着這裏往下追具體的數據結構

目前的註冊路由的流程是:

  • 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的路由算法分享

技術是開放的,咱們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

我是小魔童哪吒,歡迎點贊關注收藏,下次見~

相關文章
相關標籤/搜索