前面介紹了如何通過Go搭建一個Web服務(wù),我們可以看到簡單應(yīng)用一個net/http包就方便的搭建起來了。那么Go在底層到底是怎么做的呢?萬變不離其宗,Go的Web服務(wù)工作也離不開我們前面介紹的Web工作方式。
以下均是服務(wù)器端的幾個概念
Request:用戶請求的信息,用來解析用戶的請求信息,包括post、get、cookie、url等信息
Response:服務(wù)器需要反饋給客戶端的信息
Conn:用戶的每次請求鏈接
Handler:處理請求和生成返回信息的處理邏輯
如下圖所示,是Go實現(xiàn)Web服務(wù)的工作模式的流程圖
http包執(zhí)行流程:
這整個的過程里面我們只要了解清楚下面三個問題,也就知道Go是如何讓W(xué)eb運行起來了
前面小節(jié)的代碼里面我們可以看到,Go是通過一個函數(shù)ListenAndServe
來處理這些事情的,其實現(xiàn)源碼如下:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
ListenAndServe
會初始化一個sever
對象,然后調(diào)用了Server
對象的方法ListenAndServe
。其源碼如下:
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
?ListenAndServe
?調(diào)用了?net.Listen("tcp", addr)
?,也就是底層用TCP協(xié)議搭建了一個服務(wù),最后調(diào)用?src.Serve
?監(jiān)控我們設(shè)置的端口。監(jiān)控之后如何接收客戶端的請求呢?
?Serve
?的具體實現(xiàn)如下(為突出重點,僅展示關(guān)鍵代碼),通過下面的分析源碼我們可以看到客戶端請求的具體處理過程:
func (srv *Server) Serve(l net.Listener) error {
...
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
...
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)
}
}
這個函數(shù)里面起了一個?for{}
?,首先通過Listener接收請求:?l.Accept()
?,其次創(chuàng)建一個Conn:?c := srv.newConn(rw)
?,最后單獨開了一個goroutine,把這個請求的數(shù)據(jù)當(dāng)做參數(shù)扔給這個conn去服務(wù):?go c.serve(connCtx)
?。這個就是高并發(fā)體現(xiàn)了,用戶的每一次請求都是在一個新的goroutine去服務(wù),相互不影響。
那么如何具體分配到相應(yīng)的函數(shù)來處理請求呢?我們繼續(xù)分析conn的?serve
?方法,其源碼如下(為突出重點,僅展示關(guān)鍵代碼):
func (c *conn) serve(ctx context.Context) {
...
ctx, cancelCtx := context.WithCancel(ctx)
c.cancelCtx = cancelCtx
defer cancelCtx()
c.r = &connReader{conn: c}
c.bufr = newBufioReader(c.r)
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
for {
w, err := c.readRequest(ctx)
...
// 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.
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
...
}
}
conn首先會解析request:w, err := c.readRequest(ctx)
, 然后獲取相應(yīng)的handler去處理請求:serverHandler{c.server}.ServeHTTP(w, w.req)
,ServeHTTP
的具體實現(xiàn)如下:
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)
}
?sh.srv.Handler
?就是我們剛才在調(diào)用函數(shù)?ListenAndServe
?時候的第二個參數(shù),我們前面例子傳遞的是nil,也就是為空,那么默認(rèn)獲取?handler = DefaultServeMux
?,那么這個變量用來做什么的呢?對,這個變量就是一個路由器,它用來匹配url跳轉(zhuǎn)到其相應(yīng)的handle函數(shù),那么這個我們有設(shè)置過嗎?有,我們調(diào)用的代碼里面第一句不是調(diào)用了?http.HandleFunc("/", sayhelloName)
?嘛。這個作用就是注冊了請求/的路由規(guī)則,當(dāng)請求uri為"/",路由就會轉(zhuǎn)到函數(shù)sayhelloName,DefaultServeMux會調(diào)用ServeHTTP方法,這個方法內(nèi)部其實就是調(diào)用sayhelloName本身,最后通過寫入response的信息反饋到客戶端。
詳細(xì)的整個流程如下圖所示:
至此我們的三個問題已經(jīng)全部得到了解答,你現(xiàn)在對于Go如何讓W(xué)eb跑起來的是否已經(jīng)基本了解了呢?
更多建議: