W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
前面介紹了如何通過Go搭建一個(gè)Web服務(wù),我們可以看到簡(jiǎn)單應(yīng)用一個(gè)net/http包就方便的搭建起來了。那么Go在底層到底是怎么做的呢?萬變不離其宗,Go的Web服務(wù)工作也離不開我們前面介紹的Web工作方式。
以下均是服務(wù)器端的幾個(gè)概念
Request:用戶請(qǐng)求的信息,用來解析用戶的請(qǐng)求信息,包括post、get、cookie、url等信息
Response:服務(wù)器需要反饋給客戶端的信息
Conn:用戶的每次請(qǐng)求鏈接
Handler:處理請(qǐng)求和生成返回信息的處理邏輯
如下圖所示,是Go實(shí)現(xiàn)Web服務(wù)的工作模式的流程圖
http包執(zhí)行流程:
這整個(gè)的過程里面我們只要了解清楚下面三個(gè)問題,也就知道Go是如何讓W(xué)eb運(yùn)行起來了
前面小節(jié)的代碼里面我們可以看到,Go是通過一個(gè)函數(shù)ListenAndServe
來處理這些事情的,其實(shí)現(xiàn)源碼如下:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
ListenAndServe
會(huì)初始化一個(gè)sever
對(duì)象,然后調(diào)用了Server
對(duì)象的方法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é)議搭建了一個(gè)服務(wù),最后調(diào)用?src.Serve
?監(jiān)控我們?cè)O(shè)置的端口。監(jiān)控之后如何接收客戶端的請(qǐng)求呢?
?Serve
?的具體實(shí)現(xiàn)如下(為突出重點(diǎn),僅展示關(guān)鍵代碼),通過下面的分析源碼我們可以看到客戶端請(qǐng)求的具體處理過程:
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)
}
}
這個(gè)函數(shù)里面起了一個(gè)?for{}
?,首先通過Listener接收請(qǐng)求:?l.Accept()
?,其次創(chuàng)建一個(gè)Conn:?c := srv.newConn(rw)
?,最后單獨(dú)開了一個(gè)goroutine,把這個(gè)請(qǐng)求的數(shù)據(jù)當(dāng)做參數(shù)扔給這個(gè)conn去服務(wù):?go c.serve(connCtx)
?。這個(gè)就是高并發(fā)體現(xiàn)了,用戶的每一次請(qǐng)求都是在一個(gè)新的goroutine去服務(wù),相互不影響。
那么如何具體分配到相應(yīng)的函數(shù)來處理請(qǐng)求呢?我們繼續(xù)分析conn的?serve
?方法,其源碼如下(為突出重點(diǎn),僅展示關(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首先會(huì)解析request:w, err := c.readRequest(ctx)
, 然后獲取相應(yīng)的handler去處理請(qǐng)求:serverHandler{c.server}.ServeHTTP(w, w.req)
,ServeHTTP
的具體實(shí)現(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í)候的第二個(gè)參數(shù),我們前面例子傳遞的是nil,也就是為空,那么默認(rèn)獲取?handler = DefaultServeMux
?,那么這個(gè)變量用來做什么的呢?對(duì),這個(gè)變量就是一個(gè)路由器,它用來匹配url跳轉(zhuǎn)到其相應(yīng)的handle函數(shù),那么這個(gè)我們有設(shè)置過嗎?有,我們調(diào)用的代碼里面第一句不是調(diào)用了?http.HandleFunc("/", sayhelloName)
?嘛。這個(gè)作用就是注冊(cè)了請(qǐng)求/的路由規(guī)則,當(dāng)請(qǐng)求uri為"/",路由就會(huì)轉(zhuǎn)到函數(shù)sayhelloName,DefaultServeMux會(huì)調(diào)用ServeHTTP方法,這個(gè)方法內(nèi)部其實(shí)就是調(diào)用sayhelloName本身,最后通過寫入response的信息反饋到客戶端。
詳細(xì)的整個(gè)流程如下圖所示:
至此我們的三個(gè)問題已經(jīng)全部得到了解答,你現(xiàn)在對(duì)于Go如何讓W(xué)eb跑起來的是否已經(jīng)基本了解了呢?
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: