1. 程式人生 > >golang httpServer對於keepAlive長連線的處理方式

golang httpServer對於keepAlive長連線的處理方式

當下http1.1對於長連線是預設開啟的,golang的內建httpServer也很好的支援了這一點。今天查閱資料時,發現自己寫了這麼多的介面,但是對於httpServer是如何實現長連線的,卻一時說不上來。於是就去go的src原始碼裡面翻了翻。

本文只討論server端是如何確保長連線的,client端如何確保呢,這裡先簡要說一下,當呼叫client.Do()函式後,通過transport去獲取快取的連線,詳細的暫不討論。

server端我們結合原始碼來看,httpServer啟動之後,會為每一個到來的請求去建立一個goroutine,這點沒問題,之前我也是這麼想的,實則並不一定是這樣。確切的說,應該是為每一個新的tcp連線去建立一個goroutine,為什麼這樣講呢,看原始碼:

func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    if fn := testHookServerServe; fn != nil {
        fn(srv, l)
    }
            .
            .
            .
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx) //新建連線
} }

這段程式碼的最後會依靠新建的連線去起一個goroutine,繼續往下看:

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
    c.remoteAddr = c.rwc.RemoteAddr().String()
    ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
    defer func() {
        if err := recover(); err != nil && err != ErrAbortHandler {
            const
size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf) } if !c.hijacked() { c.close() //關閉連線 c.setState(c.rwc, StateClosed) } }() . . . // HTTP/1.x from here on. 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 { //迴圈讀取請求,也就是說,這個goroutine可以重複接受多次請求,除非出錯或者超時等等因素,才會走到上面的defer去關閉連線 w, err := c.readRequest(ctx) if c.r.remain != c.server.initialReadLimitSize() { // If we read any bytes off the wire, we're active. c.setState(c.rwc, StateActive) } if err != nil { const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n" . . . // 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) //這裡是handler去執行的地方,也就是我們的業務邏輯函式 w.cancelCtx() if c.hijacked() { return } w.finishRequest() . . . if d := c.server.idleTimeout(); d != 0 { c.rwc.SetReadDeadline(time.Now().Add(d)) if _, err := c.bufr.Peek(4); err != nil { return } } c.rwc.SetReadDeadline(time.Time{}) } }

上面的我後加的中文註釋其實已經挺清楚了,for迴圈不斷的讀取請求,也就是說,這個goroutine可以重複接受對面那個client的多次請求,除非出錯或者超時等等因素,才會走到defer去關閉連線,否則這個連線(goroutine)將會一直存在。

serverHandler{c.server}.ServeHTTP(w, w.req)這裡是handler去執行的地方,也就是我們的業務邏輯函式。同時上面有一段官方的註釋:

        // 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.

意思就是說,一個tcp連線裡面只能同時處理一個請求執行一個handler,而http2的多路複用則可以在一個連線內同時處理多個請求。

golang的httpServer對於長連線的處理,大致流程就是如此,查明白了,心裡總算舒服了。