Go 如何使得Web工作

2022-05-13 17:51 更新

前面介紹了如何通過Go搭建一個(gè)Web服務(wù),我們可以看到簡(jiǎn)單應(yīng)用一個(gè)net/http包就方便的搭建起來了。那么Go在底層到底是怎么做的呢?萬變不離其宗,Go的Web服務(wù)工作也離不開我們前面介紹的Web工作方式。

web工作方式的幾個(gè)概念

以下均是服務(wù)器端的幾個(gè)概念

 Request:用戶請(qǐng)求的信息,用來解析用戶的請(qǐng)求信息,包括post、get、cookie、url等信息

Response:服務(wù)器需要反饋給客戶端的信息

Conn:用戶的每次請(qǐng)求鏈接

Handler:處理請(qǐng)求和生成返回信息的處理邏輯

分析http包運(yùn)行機(jī)制

如下圖所示,是Go實(shí)現(xiàn)Web服務(wù)的工作模式的流程圖

GO WEB流程圖

http包執(zhí)行流程:

  1. 創(chuàng)建Listen Socket, 監(jiān)聽指定的端口, 等待客戶端請(qǐng)求到來。
  2. Listen Socket接受客戶端的請(qǐng)求, 得到Client Socket, 接下來通過Client Socket與客戶端通信。
  3. 處理客戶端的請(qǐng)求, 首先從Client Socket讀取HTTP請(qǐng)求的協(xié)議頭, 如果是POST方法, 還可能要讀取客戶端提交的數(shù)據(jù), 然后交給相應(yīng)的handler處理請(qǐng)求, handler處理完畢準(zhǔn)備好客戶端需要的數(shù)據(jù), 通過Client Socket寫給客戶端。

這整個(gè)的過程里面我們只要了解清楚下面三個(gè)問題,也就知道Go是如何讓W(xué)eb運(yùn)行起來了

  • 如何監(jiān)聽端口?
  • 如何接收客戶端請(qǐng)求?
  • 如何分配handler?

前面小節(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)基本了解了呢?


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)