golang学习之web服务流程分析

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

  对于golang而言,要搭建一个服务器端十分简单,仅仅只需几十行代码就可以实现:

代码引用自:
https://github.com/pmlpml/golang-learning/tree/master/web/basic

package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"
)

type MyMux struct {
    defaultMux *http.ServeMux
}

func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Println("do some extension here!")
    if p.defaultMux == nil {
        p.defaultMux = http.DefaultServeMux
    }
    p.defaultMux.ServeHTTP(w, r)
    return
}

func sayhelloName(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()       //解析参数,默认是不会解析的
    fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
    fmt.Println("path", r.URL.Path)
    fmt.Println("scheme", r.URL.Scheme)
    fmt.Println(r.Form["url_long"])
    for k, v := range r.Form {
        fmt.Println("key:", k)
        fmt.Println("val:", strings.Join(v, ""))
    }
    fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
}

func main() {
    http.HandleFunc("/", sayhelloName)            //设置访问的路由
    err := http.ListenAndServe(":9090", &MyMux{}) //设置监听的端口
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

  那么,它是在golang中是如何实现的呢?我们可以通过一些工具跟踪分析源码。代码架构图:

ListenAndServe(addr string, handler Handler)
  + server.ListenAndServe()
    | net.Listen("tcp", addr)
    + srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
      | srv.setupHTTP2_Serve()
      | baseCtx := context.Background()
      + for {}
        | l.Accept()
        |  + select ... //为什么
        | c := srv.newConn(rw)
        | c.setState(c.rwc, StateNew) // before Serve can return
        + go c.serve(ctx) // 新的链接 goroutine
          | ...  // 构建 w , r
          | serverHandler{c.server}.ServeHTTP(w, w.req)
          | ...  // after Serve

1. ListenAndServe函数

函数源码解读如下:

func ListenAndServe(addr string, handler Handler) error {
    //创建一个Server并初始化
    server := &Server{Addr: addr, Handler: handler}
    //返回error
    return server.ListenAndServe()
}

2. net.ListenAndServe

函数源码解读如下:

func (srv *Server) ListenAndServe() error {
    //根据Server结构体定义,addr为空,则赋值为":http"
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    //监听地址为addr的tcp网络,返回一个Listener接口及error
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    //建立一个TCP keep-alive监听器,处理请求
    //使用了类型断言,ln接口是否实现了net.TCPListener接口
    //返回error
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

3. net.Listen

源码解读如下:

func Listen(network, address string) (Listener, error) {
    //解析地址,返回地址列表
    //假如err = nil, 则addrs至少包含一个地址
    addrs, err := DefaultResolver.resolveAddrList(context.Background(), "listen", network, address, nil)
    if err != nil {
        return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
    }
    var l Listener
    //返回addrs中第一个满足IPv4的地址,并做类型断言
    //根据不同的地址类型,调用不同的监听方法
    //可以了解TCP socket与Unix socket区分
    switch la := addrs.first(isIPv4).(type) {
    case *TCPAddr:
        l, err = ListenTCP(network, la)
    case *UnixAddr:
        l, err = ListenUnix(network, la)
    default:
        return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
    }
    if err != nil {
        return nil, err // l is non-nil interface containing nil pointer
    }
    return l, nil
}

4. srv.Serve

源码及分析如下:

func (srv *Server) Serve(l net.Listener) error {
    //延迟关闭监听器l,防止内存泄漏
    defer l.Close()
    //设置srv的校验规则,如果设置,则执行。
    //fn为函数
    if fn := testHookServerServe; fn != nil {
        fn(srv, l)
    }
    //设置接收到错误后睡眠时间,也即超时
    var tempDelay time.Duration

    配置HTTP/2
    if err := srv.setupHTTP2_Serve(); err != nil {
        return err
    }
    //跟踪Listener
    srv.trackListener(l, true)
    defer srv.trackListener(l, false)

    //context包在下文详细介绍
    //context用于在不同的go goroutine进行传递数据、取消signal等
    //Context对象会形成一个树,Background为树的根;该树的根节点与叶节点为继承关系
    baseCtx := context.Background() // base is always background, per Issue 16220
    //将请求作用域的数据与 Context 对象建立关系
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        //等待并返回下一个listener(即l得下一个)
        rw, e := l.Accept()
        if e != nil {
            //出现错误,执行select
            //从srv.getDoneChan中读到数据,则返回ErrServerClosed
            //否则,继续执行
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            //类型断言。如果e为net.Error类型变量,且为临时错误
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                //tempDelay为0,则赋值为5毫秒
                //否则,乘2
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                //最大超时为1秒
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                //等待发生错误得连接重新尝试
                srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        //为下一个listener新创建一个连接
        c := srv.newConn(rw)
        //设置状态
        c.setState(c.rwc, StateNew)
        //新建一个线程(goroutine)处理该连接的一切请求
        go c.serve(ctx)
    }
}

关于context:

在 Go http包的Server中,每一个请求在都有一个对应的 goroutine 去处理。请求处理函数通常会启动额外的 goroutine 用来访问后端服务,比如数据库和RPC服务。用来处理一个请求的 goroutine 通常需要访问一些与请求特定的数据,比如终端用户的身份认证信息、验证相关的token、请求的截止时间。而context包则是为了让我们更好地处理goroutine之间的交互而实现的。

了解更多:Go语言并发模型:使用 context

关于select:

golang 的 select 的功能和 select, poll, epoll 相似, 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。

了解更多:【golang】Go语言学习-select用法

关于类型断言:

类型断言是interface的一种特殊用法。

了解更多:golang学习之Interface类型断言

5. c.serve

源码及分析如下:

func (c *conn) serve(ctx context.Context) {
    c.remoteAddr = c.rwc.RemoteAddr().String()
    //将请求作用域的数据与 Context 对象建立关系
    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)
        }
    }()

    //建立三次握手连接,构造w,r
    if tlsConn, ok := c.rwc.(*tls.Conn); ok {
        if d := c.server.ReadTimeout; d != 0 {
            c.rwc.SetReadDeadline(time.Now().Add(d))
        }
        if d := c.server.WriteTimeout; d != 0 {
            c.rwc.SetWriteDeadline(time.Now().Add(d))
        }
        if err := tlsConn.Handshake(); err != nil {
            c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
            return
        }
        c.tlsState = new(tls.ConnectionState)
        *c.tlsState = tlsConn.ConnectionState()
        if proto := c.tlsState.NegotiatedProtocol; validNPN(proto) {
            if fn := c.server.TLSNextProto[proto]; fn != nil {
                h := initNPNRequest{tlsConn, serverHandler{c.server}}
                fn(c.server, tlsConn, h)
            }
            return
        }
    }

    //解析http1.1协议
    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)
        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"

            if err == errTooLarge {
                const publicErr = "431 Request Header Fields Too Large"
                fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
                c.closeWriteAndWait()
                return
            }
            if isCommonNetReadError(err) {
                return // don't reply
            }

            publicErr := "400 Bad Request"
            if v, ok := err.(badRequestError); ok {
                publicErr = publicErr + ": " + string(v)
            }

            fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
            return
        }

        // Expect 100 Continue support
        req := w.req
        if req.expectsContinue() {
            if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
                // Wrap the Body reader with one that replies on the connection
                req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
            }
        } else if req.Header.get("Expect") != "" {
            w.sendExpectationFailed()
            return
        }

        c.curReq.Store(w)

        if requestBodyRemains(req.Body) {
            registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
        } else {
            if w.conn.bufr.Buffered() > 0 {
                w.conn.r.closeNotifyFromPipelinedRequest()
            }
            w.conn.r.startBackgroundRead()
        }

        //处理http请求。
        //由于http做了序列化处理,所以这里可以一次性处理完一个http请求
        serverHandler{c.server}.ServeHTTP(w, w.req)
        w.cancelCtx()
        if c.hijacked() {
            return
        }
        w.finishRequest()
        if !w.shouldReuseConnection() {
            if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
                c.closeWriteAndWait()
            }
            return
        }
        c.setState(c.rwc, StateIdle)
        c.curReq.Store((*response)(nil))

        if !w.conn.server.doKeepAlives() {
            // We're in shutdown mode. We might've replied
            // to the user without "Connection: close" and
            // they might think they can send another
            // request, but such is life with HTTP/1.1.
            return
        }

        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{})
    }
}

  以上,便是golang web服务流程的分析了,希望能够让大家对go搭建服务器的流程有一个更加深入的了解。

猜你喜欢

转载自blog.csdn.net/Kiloveyousmile/article/details/78736910
今日推荐