zoukankan      html  css  js  c++  java
  • Go http包执行流程

    Go 语言实现的 Web 服务工作方式与其他形式下的 Web 工作方式并没有什么不同,具体流程如下:

    —— http包执行流程

    Request:来自用户的请求信息,包括 post、get、Cookie、url 等。
    Response:服务器返回给客户端的信息。
    Connect:用户的每次的请求连接
    Handler:处理请求和生成返回信息的处理逻辑

    根据上图,Go 语言中的 http 包具体做了这么三个操作:

    1. 创建 Listen Socket,监听指定端口,等待客户端请求。
    2. Listen Socket 接受客户端请求,得到 Client Socket,接下来通过 Client Socket与客户端通信
    3. 处理客户端请求。首先从 Client Socket 获取 HTTP 请求数据,然后交给相应的 handler 处理请求,handler 处理完毕后再通过 Client Socket 返回给客户端。

    接着我们从代码的角度来看一下这三个操作是如何实现的:

    一,监听端口

    在 Go 语言中只需要通过调用 ListenAndServe 方法即可设置监听端口:

    func main() {
    	http.HandleFunc("/", sayhelloName)       //设置访问的路由
    	
    	err := http.ListenAndServe(":9090", nil) //设置监听的端口
    	if err != nil {
    		log.Fatal("ListenAndServe: ", err)
    	}
    }
    
    func sayhelloName(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello Wrold!") //这个写入到w的是输出到客户端的
    }
    

    运行结果如下:

    我们接着看一下 ListenAndServe 方法的具体实现,看一下它是如何实现监听端口的:

    // ListenAndServe listens on the TCP network address addr and then calls
    // Serve with handler to handle requests on incoming connections.
    // Accepted connections are configured to enable TCP keep-alives.
    //
    // The handler is typically nil, in which case the DefaultServeMux is used.
    //
    // ListenAndServe always returns a non-nil error.
    func ListenAndServe(addr string, handler Handler) error {
    	server := &Server{Addr: addr, Handler: handler}
    	return server.ListenAndServe()
    }
    

    通过查看该方法的源码,可以发现该方法初始化了一个 server 对象,并调用了该 server 对象的 ListenAndServe() 方法:

    // ListenAndServe listens on the TCP network address srv.Addr and then
    // calls Serve to handle requests on incoming connections.
    // Accepted connections are configured to enable TCP keep-alives.
    //
    // If srv.Addr is blank, ":http" is used.
    //
    // ListenAndServe always returns a non-nil error. After Shutdown or Close,
    // the returned error is ErrServerClosed.
    func (srv *Server) ListenAndServe() error {
    	if srv.shuttingDown() {
    		return ErrServerClosed
    	}
    	addr := srv.Addr
    	if addr == "" {
    		addr = ":http"
    	}
    	ln, err := net.Listen("tcp", addr) //底层使用tcp协议搭建了一个服务,然后监听所设置的端口
    	if err != nil {
    		return err
    	}
    	return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
    }
    

    在这个方法中调用了 net.Listen("tcp", addr) 来监听端口

    二,接收客户端请求

    在完成了对端口的监听之后,再通过调用 srv.Serve(net.Listener) 方法来处理接收客户端的请求消息。

    // Serve accepts incoming connections on the Listener l, creating a
    // new service goroutine for each. The service goroutines read requests and
    // then call srv.Handler to reply to them.
    //
    // HTTP/2 support is only enabled if the Listener returns *tls.Conn
    // connections and they were configured with "h2" in the TLS
    // Config.NextProtos.
    //
    // Serve always returns a non-nil error and closes l.
    // After Shutdown or Close, the returned error is ErrServerClosed.
    func (srv *Server) Serve(l net.Listener) error {
        ...
    
    	for {
    		rw, e := l.Accept() //在循环体中阻塞等待请求
    		if e != nil {
    			select {
    			case <-srv.getDoneChan():
    				return ErrServerClosed
    			default:
    			}
    			if ne, ok := e.(net.Error); ok && ne.Temporary() {
    				if tempDelay == 0 {
    					tempDelay = 5 * time.Millisecond
    				} else {
    					tempDelay *= 2
    				}
    				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
    		}
    
            ...
            c := srv.newConn(rw)
            c.setState(c.rwc, StateNew) // before Serve can return
    		go c.serve(ctx)
    	}
    }
    

    在这个方法中,启动了一个 for 循环使 Listener 不断地接收来自客户端的请求,并且对每一个请求都实例化一个 Conn,并开启一个 goroutine 来为这个请求进行服务 go c.serve()。用户的每一次请求都是在一个新的 goroutine中服务,互相不影响。

    三,为不同的请求分配处理逻辑

    对于来自客户端的不同请求,服务器端需要根据情况来分配相对应的函数进行处理。
    在之前的 main 方法中,我们调用了 http.ListenAndServe(":9090", nil) 来监听端口,而第二个参数传入的是 nil,那么这个参数是干嘛用的呢?

    func ListenAndServe(addr string, handler Handler) error {
        server := &Server{Addr: addr, Handler: handler}
        return server.ListenAndServe()
    }
    
    type Server struct {
        Addr    string      // 监听的地址和端口 默认为":http"
        Handler Handler     // 路由管理,如果为nil,则默认为http.DefaultServeMux
        ReadTimeout time.Duration //读的最大超时时间
        WriteTimeout time.Duration //写的最大超时时间
        MaxHeaderBytes int         //请求头的最大长度
        TLSConfig *tls.Config      // 配置TLS
        ...
    }
    

    从源码中的注释可以看出,当初始化 Server 时,如果不指定参数 Handler,则默认获取 Handler = http.DefaultServeMux,而 DefaultServeMux 又是一个默认创建的 ServeMux 类型的数据。

    // DefaultServeMux is the default ServeMux used by Serve.
    var DefaultServeMux = &defaultServeMux
    var defaultServeMux ServeMux
    

    从源码上看到,DefaultServeMux 又是一个默认创建的 ServeMux。那么这个 ServeMux 又是干嘛的呢?

    ServeMux

    ServeMux 是 Go 语言中默认的路由规则管理器,它维护了一个存放了路由信息的 map 表,key 是请求路径,而 value 则是该路径所对应的处理程序(Handler)。当有请求到来的时候,根据这个 map 路由表来判断将请求分发个哪个 Handler。

    type ServeMux struct {
        mu    sync.RWMutex //锁机制,用于处理并发
        m     map[string]muxEntry //路由表
        hosts bool // 是否为主机模式
    }
     
    type muxEntry struct {
        h       Handler
        pattern string
    }
    

    通过查看这两个结构体的源码,就能更容易的理解路由是如何处理请求的了:
    当接收到一个请求时,server 就会根据 ServeMux.m 中保存的请求路径(string)来匹配相对应的 muxEntry,接着就可以调用 muxEntry 中 h Handler 的ServeHttp 方法来处理请求了。

    type Handler interface {
    	ServeHTTP(ResponseWriter, *Request)
    }
    

    回到最开始的例子 main 方法中,我们为根路径 "/" 注册了一个 sayhelloName 函数,但是 sayhelloName 函数并没有实现 ServeHTTP 接口,那么为什么它能起到作用呢?这因为在 http 包内还定义了一个 HandleFunc 类型,这个类型默认就实现了 ServeHTTP 这个接口。

    // Handler that calls f.
    type HandlerFunc func(ResponseWriter, *Request)
    // ServeHTTP calls f(w, r).
    func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    	f(w, r)
    }
    

    而我们注册的自定义 sayhelloName 函数,经过 http.HandleFunc("/", sayhelloName) ,最后都会被转换成 HandlerFunc 类型。

    // HandleFunc registers the handler function for the given pattern
    // in the DefaultServeMux.
    // The documentation for ServeMux explains how patterns are matched.
    func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    	DefaultServeMux.HandleFunc(pattern, handler)
    }
    ...
    // HandleFunc registers the handler function for the given pattern.
    func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    	if handler == nil {
    		panic("http: nil handler")
    	}
    	mux.Handle(pattern, HandlerFunc(handler)) //转换自定义的func为HandlerFunc类型
    }
    

    在上面这段代码可以看到,因为调用了 HandlerFunc(handler) 方法进行了强制类型转换,我们自定义的函数被都转换为了 HandlerFunc 类型。而 HandlerFunc 又实现了 Handler 接口的 ServeHTTP 方法,所以 HandlerFunc 是 Handler 的具体实现类型,所以 mux 也就拥有了 ServeHTTP 方法了。

    到此,路由器就注册好了相应的路由规则了。我们接着来分析一下路由是如何分发处理逻辑的。

    路由分发

    路由器接收到请求之后就会调用 mux.handler(r).ServeHTTP(w, r),也就是调用相应路由的 handler 的 ServeHTTP 接口。

    // handler is the main implementation of Handler.
    // The path is known to be in canonical form, except for CONNECT methods.
    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 和路由器中的存储的 map 去匹配的,当匹配到路径之后,就会返回存储的 handler,接着就可以调用这个 handler 的 ServeHTTP 执行相应的函数了。

    路由匹配规则

    // Find a handler on a handler map given a path string.
    // Most-specific (longest) pattern wins.
    func (mux *ServeMux) match(path string) (h Handler, pattern string) {
        // Check for exact match first.
        v, ok := mux.m[path]
        if ok {
            return v.h, v.pattern
        }
     
        // Check for longest valid match.
        var n = 0
        for k, v := range mux.m {
            if !pathMatch(k, path) {
                continue
            }
            if h == nil || len(k) > n {
                n = len(k)
                h = v.h
                pattern = v.pattern
            }
        }
        return
    }
    // Does path match pattern?
    func pathMatch(pattern, path string) bool {
        if len(pattern) == 0 {
            // should not happen
            return false
        }
        n := len(pattern)
        if pattern[n-1] != '/' {
            return pattern == path
        }
        return len(path) >= n && path[0:n] == pattern
    }
    
    

    总结流程图:

    —— http处理连接流程

  • 相关阅读:
    洛谷 P1325 雷达安装 解题报告
    洛谷 P2184 贪婪大陆 解题报告
    洛谷 P3942 将军令 解题报告
    洛谷 P3698 [CQOI2017]小Q的棋盘 解题报告
    洛谷 P1436 棋盘分割 解题报告
    C++生成dump文件,调试dump文件
    判断机器大小端的两种实现方法
    判断机器大小端的两种实现方法
    Visual Studio 代码生成 运行时库的选择
    Visual Studio 代码生成 运行时库的选择
  • 原文地址:https://www.cnblogs.com/liyutian/p/10144584.html
Copyright © 2011-2022 走看看