当下http1.1对于长连接是默认开启的,golang的内置httpServer也很好的支持了这一点。今天查阅资料时,发现自己写了这么多的接口,但是对于httpServer是如何实现长连接的,却一时说不上来。于是就去go的src源码里面翻了翻。
client.Do()
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)
// 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对于长连接的处理,大致流程就是如此,查明白了,心里总算舒服了。