前面小節介紹了如何透過 Go 建立一個 Web 服務,我們可以看到簡單應用一個 net/http 套件就方便的建立起來了。那麼 Go 在底層到底是怎麼做的呢?萬變不離其宗,Go 的 Web 服務工作也離不開我們第一小節介紹的 Web 工作方式。
以下均是伺服器端的幾個概念
Request:使用者請求的資訊,用來解析使用者的請求資訊,包括 post、get、cookie、url 等資訊
Response:伺服器需要反饋給客戶端的資訊
Conn:使用者的每次請求連結
Handler:處理請求和產生回傳資訊的處理邏輯
下圖是 Go 實現 Web 服務的工作模式的流程圖
圖 3.9 http 套件執行流程
-
建立 Listen Socket, 監聽指定的埠, 等待客戶端請求到來。
-
Listen Socket 接受客戶端的請求, 得到 Client Socket, 接下來透過 Client Socket 與客戶端通訊。
-
處理客戶端的請求, 首先從 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 跑起來的是否已經基本了解了呢?
- 目錄
- 上一節:GO 建立一個簡單的 web 服務
- 下一節:Go 的 http 套件詳解