golang httpServer对于keepAlive长连接的处理方式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jeffrey11223/article/details/81222774

当下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对于长连接的处理,大致流程就是如此,查明白了,心里总算舒服了。

猜你喜欢

转载自blog.csdn.net/jeffrey11223/article/details/81222774