golang httpServer對於keepAlive長連線的處理方式
阿新 • • 發佈:2019-01-01
當下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對於長連線的處理,大致流程就是如此,查明白了,心裡總算舒服了。