golang http server 探究(下)

Tudo · · 2405 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

在上一篇里面我们通过:

func main() {
    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "hello")
    })
    http.ListenAndServe(":9010", nil)
}

分析了 http.handleFunc 的路由调用之间的关系。这次。我们分析一下 http.ListenAndServe():

http.ListenAndServe(addr string, handler Handler)

函数的内部实现:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

发现这个http.ListenAndServe 其实调用的是 Server.ListenAndServe。我们先来看看 Server的结构:

type Server struct {
    Addr         string        // TCP address to listen on, ":http" if empty
    Handler      Handler       //处理器,如果为空则使用 http.DefaultServeMux 
    ReadTimeout  time.Duration 
    WriteTimeout time.Duration 
    ....
}

看到这个Handler,再联系上次我们分析的http.HandleFunc。我们发现他们默认都使用了 http.DefaultServeMux 这个路由处理器。而在这个处理器里面 刚好保存了我们注册的一个路由。/hello。然后我们看看 server.ListenAndServe 是怎么监听和分发路由的。

func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

在 server.ListenAndServe 里面注册了一个tcp的监听器,监听我们注册的网络端口。接着继续调用

server.Serve(l net.Listener) error

进行服务。

func (srv *Server) Serve(l net.Listener) error {
      .......
    for {
        rw, e := l.Accept()
        ....
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}

开启一个for循环,接受net.accept()。接着使用了 srv.newConn(net.conn) 把一个tcp的conn转换成一个http.server#conn:

func (srv *Server) newConn(rwc net.Conn) *conn {
    c := &conn{
        server: srv,
        rwc:    rwc,
    }
    ....
    return c
}

最后开启一个go协程对每个请求进行处理。

func (c *conn) serve(ctx context.Context) {
       // 客户端主机ip
    c.remoteAddr = c.rwc.RemoteAddr().String()
        ....

    // HTTP/1.x from here on.
        //读取请求的数据
    c.r = &connReader{r: c.rwc}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

    ctx, cancelCtx := context.WithCancel(ctx)
    defer cancelCtx()

    for {
        w, err := c.readRequest(ctx)
        ......
        serverHandler{c.server}.ServeHTTP(w, w.req)
        ...
    }
}

请求经过七倒八倒,最后进入了:

serverHandler{c.server}.ServeHTTP(w, w.req)

并且调用了它里面的ServerHandler.serveHTTP。果断点开这个函数,豁然开朗,原来是调用了最上层:

http.DefaultServeMux.Handle

不信看这里:

type serverHandler struct {
    srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
       //sh.srv.Handler =server.handler 
    handler := sh.srv.Handler
    if handler == nil {
               //我们最初传的参数就是 nil

        handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }

    handler.ServeHTTP(rw, req)
}

所以啊,最后的处理是函数是 路由里面的 ServeMux.ServeHTTP,昨晚我们已经分析了ServeMux.ServeHTTP执行的是 我们自己传进去的函数。 服务器 开启以后一个请求 进来,首先调用的是 Server.ServeHTTP(rw ResponseWriter, req *Request).

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    .....
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}

上面的函数 再经过倒腾,最后转到这到ServeMux#handler函数里面:

func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
    mux.mu.RLock()
    defer mux.mu.RUnlock()

    // Host-specific pattern takes precedence over generic ones
    if mux.hosts {
        h, pattern = mux.match(host + path)
    }
    if h == nil {
        h, pattern = mux.match(path)
    }
    if h == nil {
        h, pattern = NotFoundHandler(), ""
    }
    return
}

在这个方法 里面就对 URL进行了匹配。匹配上就返回对应的URL的handle,否则就是 调用 NotFoundHandler。然后调用muxEntry.h,就是我们自定义处理的逻辑函数。

这样一个完整的 请求 http请求在golang里面的流通过程已经非常的清晰了是不是 ?

关注程序猿公众账号:


有疑问加站长微信联系(非本文作者)

本文来自:开源中国博客

感谢作者:Tudo

查看原文:golang http server 探究(下)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2405 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传