Go 如何使得 web 工作

前面小節介紹了如何透過 Go 建立一個 Web 服務,我們可以看到簡單應用一個 net/http 套件就方便的建立起來了。那麼 Go 在底層到底是怎麼做的呢?萬變不離其宗,Go 的 Web 服務工作也離不開我們第一小節介紹的 Web 工作方式。

web 工作方式的幾個概念

以下均是伺服器端的幾個概念

Request:使用者請求的資訊,用來解析使用者的請求資訊,包括 post、get、cookie、url 等資訊

Response:伺服器需要反饋給客戶端的資訊

Conn:使用者的每次請求連結

Handler:處理請求和產生回傳資訊的處理邏輯

分析 http 套件執行機制

下圖是 Go 實現 Web 服務的工作模式的流程圖

圖 3.9 http 套件執行流程

  1. 建立 Listen Socket, 監聽指定的埠, 等待客戶端請求到來。

  2. Listen Socket 接受客戶端的請求, 得到 Client Socket, 接下來透過 Client Socket 與客戶端通訊。

  3. 處理客戶端的請求, 首先從 Client Socket 讀取 HTTP 請求的協議頭, 如果是 POST 方法, 還可能要讀取客戶端提交的資料, 然後交給相應的 handler 處理請求, handler 處理完畢準備好客戶端需要的資料, 透過 Client Socket 寫給客戶端。

這整個的過程裡面我們只要了解清楚下面三個問題,也就知道 Go 是如何讓 Web 執行起來了

  • 如何監聽埠?

  • 如何接收客戶端請求?

  • 如何分配 handler?

前面小節的程式碼裡面我們可以看到,Go 是透過一個函式 ListenAndServe 來處理這些事情的,這個底層其實這樣處理的:初始化一個 server 物件,然後呼叫了net.Listen("tcp", addr),也就是底層用 TCP 協議建立了一個服務,然後監聽我們設定的埠。

下面程式碼來自 Go 的 http 套件的原始碼,透過下面的程式碼我們可以看到整個的 http 處理過程:

func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    var tempDelay time.Duration // how long to sleep on accept failure
    for {
        rw, e := l.Accept()
        if e != nil {
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c, err := srv.newConn(rw)
        if err != nil {
            continue
        }
        go c.serve()
    }
}

監聽之後如何接收客戶端的請求呢?上面程式碼執行監聽埠之後,呼叫了srv.Serve(net.Listener)函式,這個函式就是處理接收客戶端的請求資訊。這個函式裡面起了一個for{},首先透過 Listener 接收請求,其次建立一個 Conn,最後單獨開了一個 goroutine,把這個請求的資料當做參數扔給這個 conn 去服務:go c.serve()。這個就是高併發體現了,使用者的每一次請求都是在一個新的 goroutine 去服務,相互不影響。

那麼如何具體分配到相應的函式來處理請求呢?conn 首先會解析 request:c.readRequest(),然後取得相應的 handler:handler := c.server.Handler,也就是我們剛才在呼叫函式 ListenAndServe 時候的第二個參數,我們前面例子傳遞的是 nil,也就是為空,那麼預設取得handler = DefaultServeMux,那麼這個變數用來做什麼的呢?對,這個變數就是一個路由器,它用來匹配 url 跳轉到其相應的 handle 函式,那麼這個我們有設定過嗎 ? 有,我們呼叫的程式碼裡面第一句不是呼叫了http.HandleFunc("/", sayhelloName)嘛。這個作用就是註冊了請求/的路由規則,當請求 uri 為"/",路由就會轉到函式 sayhelloName,DefaultServeMux 會呼叫 ServeHTTP 方法,這個方法內部其實就是呼叫 sayhelloName 本身,最後透過寫入 response 的資訊反饋到客戶端。

詳細的整個流程如下圖所示:

圖 3.10 一個 http 連線處理流程

至此我們的三個問題已經全部得到了解答,你現在對於 Go 如何讓 Web 跑起來的是否已經基本了解了呢?

Last updated