golang gin框架源码分析(三)---- 问其根本 net/http包需要深究才能有始有终


全系列总结博客链接


golang Gin框架源码分析 全系列总结博客


前引


本意是没有打算写这一篇的 但是这两天在刷面经的时候发现 还是有问这个net/http包的
这个看样子是逃不掉了 还是需要深挖一下

发现要做的事情太多 太忙 但是想到如果能回到自己的hometown 今年9月份就要定正式工作了
这个offer起码决定了后面好几年的日子 也不得不这几个月再硬着头皮冲一下了


golang gin框架源码分析(三)---- 问其根本 net/http包需要深究才能有始有终


1、从示例代码入手


刚刚也才基本把net/http的框架给梳理了一遍 还是很有收获的^^
这里还是简单推荐一下 一篇对于net/http解析写的挺好的帖子 我下面贴一下链接 大家可以先去看看这篇
Introduction 万字手撕Go http源码server.go

还是从示例代码入手 下面是非常常见的用net/http写一个高并发的web服务器的代码 我们注册url相对应的处理函数就是以下面的方式来注册的 那我们就可以顺藤摸瓜的去看一下 ListenAndServe函数 和 HandleFunc函数是怎么运行的

package main

import (
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
)

func main() {
	http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`hello world`))
	})
	http.HandleFunc("/go/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`go`))
	})
	http.ListenAndServe(":8002", nil)
}

访问url的实际效果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


2、ListenAndServe(addr string, handler Handler) error


下面可以仔细看看 我贴出来了Server的结构体 如我们的示例代码所示 其实结构体中只赋值了addr 对于handler部分我们是填写的nil 那么对于该Server的话 之后找我们的handler只会去我们的默认多路复用器 也就是DefaultServerMux里面去找我们所注册的handler了 也就是我们调用了两个http.HandleFunc 其实也就是往我们默认的DefaultServerMux注册处理函数了 当然这是后话了 我们可以顺藤摸瓜下去

// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
	Addr string
	Handler Handler // handler to invoke, http.DefaultServeMux if nil
	
	DisableGeneralOptionsHandler bool
	TLSConfig *tls.Config
	ReadTimeout time.Duration
	ReadHeaderTimeout time.Duration
	WriteTimeout time.Duration
	IdleTimeout time.Duration
	MaxHeaderBytes int
	TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
	ConnState func(net.Conn, ConnState)
	ErrorLog *log.Logger
	BaseContext func(net.Listener) context.Context
	ConnContext func(ctx context.Context, c net.Conn) context.Context

	inShutdown atomic.Bool // true when server is in shutdown

	disableKeepAlives atomic.Bool
	nextProtoOnce     sync.Once // guards setupHTTP2_* init
	nextProtoErr      error     // result of http2.ConfigureServer if used

	mu         sync.Mutex
	listeners  map[*net.Listener]struct{}
	activeConn map[*conn]struct{}
	onShutdown []func()

	listenerGroup sync.WaitGroup
}

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

1、server.ListenAndServe()

这里前面可以省略 处理shutdown情况 取出来addr
而后面重点就是这两句话 ln, err := net.Listen("tcp", addr) return srv.Serve(ln)
对于服务器 常见的处理框架也就是 监听端口->取出新连接->工作线程/子线程处理/多路复用处理新连接
而这里的ln其实就是一个接口 当然递归的去看看listen工作其实也就是包装了一下相关资源 对于不同类型返回不同的结构体而已 也就可以当作是监听结构体

然后我们得到了后 就去调用serve 开始服务了 我们往下继续看看

ln, err := net.Listen("tcp", addr)
return srv.Serve(ln)

-----------
// A Listener is a generic network listener for stream-oriented protocols.
//
// Multiple goroutines may invoke methods on a Listener simultaneously.
type Listener interface {
	// Accept waits for and returns the next connection to the listener.
	Accept() (Conn, error)

	// Close closes the listener.
	// Any blocked Accept operations will be unblocked and return errors.
	Close() error

	// Addr returns the listener's network address.
	Addr() Addr
}

// Listen announces on the local network address.
//
// See func Listen for a description of the network and address
// parameters.
func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) {
	addrs, err := DefaultResolver.resolveAddrList(ctx, "listen", network, address, nil)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
	}
	sl := &sysListener{
		ListenConfig: *lc,
		network:      network,
		address:      address,
	}
	var l Listener
	la := addrs.first(isIPv4)
	switch la := la.(type) {
	case *TCPAddr:
		l, err = sl.listenTCP(ctx, la)
	case *UnixAddr:
		l, err = sl.listenUnix(ctx, la)
	default:
		return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
	}
	if err != nil {
		return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: err} // l is non-nil interface containing nil pointer
	}
	return l, nil
}

------------------
// 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)
	if err != nil {
		return err
	}
	return srv.Serve(ln)
}

2、server.Serve(l net.Listener) error

前面其实也是不是很重要的 可以抓一下关键 直接看到for循环内部
我已经把最核心的地方节省出来了 Accept获取新连接 然后获取新连接结构体server + net.Conn 返回
启用go协程去处理新连接

这个可以说是就是 把例如c++的网络库中 例如Epoll/Select 处理服务器的多路复用 改用为用go协程去处理新连接
相当于高并发核心应该就是来自于这里 则go协程的快捷以及切换快速 也让go web高并发得到了实现

我们可以继续往下看看

// Create new connection from rwc.
func (srv *Server) newConn(rwc net.Conn) *conn {
	c := &conn{
		server: srv,
		rwc:    rwc,
	}
	if debugServerConnections {
		c.rwc = newLoggingConn("server", c.rwc)
	}
	return c
}

for {
		rw, err := l.Accept()
		.....
		.....
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew, runHooks) // before Serve can return
		go c.serve(connCtx)
}

----
// 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 {
	if fn := testHookServerServe; fn != nil {
		fn(srv, l) // call hook with unwrapped listener
	}

	origListener := l
	l = &onceCloseListener{Listener: l}
	defer l.Close()

	if err := srv.setupHTTP2_Serve(); err != nil {
		return err
	}

	if !srv.trackListener(&l, true) {
		return ErrServerClosed
	}
	defer srv.trackListener(&l, false)

	baseCtx := context.Background()
	if srv.BaseContext != nil {
		baseCtx = srv.BaseContext(origListener)
		if baseCtx == nil {
			panic("BaseContext returned a nil context")
		}
	}

	var tempDelay time.Duration // how long to sleep on accept failure

	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		rw, err := l.Accept()
		if err != nil {
			if srv.shuttingDown() {
				return ErrServerClosed
			}
			if ne, ok := err.(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", err, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return err
		}
		connCtx := ctx
		if cc := srv.ConnContext; cc != nil {
			connCtx = cc(connCtx, rw)
			if connCtx == nil {
				panic("ConnContext returned nil")
			}
		}
		tempDelay = 0
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew, runHooks) // before Serve can return
		go c.serve(connCtx)
	}
}

3、conn.serve(ctx context.Context) {

我对下面的代码进行了抛肥捡瘦 for循环我理解的应该是长连接 需要多次request response 一旦请求终止即return
而这里面最核心的一句话就是serverHandler{c.server}.ServeHTTP(w, w.req)

serveHTTP 这个是内部的函数 我贴在了下面 是最重要的语句 我们详细来看看
首先先赋值handler 这个handler 对于我们的示例代码还记得吗 我们填写的是nil 如果我们自己写了类 实现了ServeHTTP的话 则响应函数只会触发我们自己写的了 而不是我们用HandlerFunc默认的多路复用器我们添加的那部分函数了

最后这个net/http 最核心的一句话 处理函数就是在handler.ServeHTTP(rw, req) 调用我们的handler
那现在明了了 逻辑基本理清了 那这个时候我们可以最后再去看看我们的DefaultServeMux是如何存储我们的多个url对应的响应函数的

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler
	if handler == nil {
		handler = DefaultServeMux
	}
	if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" {
		handler = globalOptionsHandler{}
	}

	if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") {
		var allowQuerySemicolonsInUse atomic.Bool
		req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() {
			allowQuerySemicolonsInUse.Store(true)
		}))
		defer func() {
			if !allowQuerySemicolonsInUse.Load() {
				sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192")
			}
		}()
	}

	handler.ServeHTTP(rw, req)
}

------------------------------------------------

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
	......
	......
	// 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 {
		w, err := c.readRequest(ctx)
		.....
		.....
		.....

		// Expect 100 Continue support
		req := 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.
		inFlightResponse = w
		serverHandler{c.server}.ServeHTTP(w, w.req)
		inFlightResponse = nil
		w.cancelCtx()
		if c.hijacked() {
			return
		}
		w.finishRequest()
		........
	}
}

3、HandleFunc(pattern string, handler func(ResponseWriter, *Request))


下面我把相关的结构体贴出来了
我可以这里就简单介绍一下
mu是读写锁控制竞态环境的 m则是存相关url的map 我们示例代码/go /hello也就被存储在了这里面
es则是 我们存储path末尾是/的匹配模式串 而且会按照长度从长到短按照顺序来存储 这个都会后面的讲

我们可以递归的去看看mux.HandleFunc 我们是如何写入map

type muxEntry struct {
	h       Handler
	pattern string
}

type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry // slice of entries sorted from longest to shortest.
	hosts bool       // whether any patterns contain hostnames
}

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

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

1、mtx.HandleFunc(pattern string, handler func(ResponseWriter, *Request))

最核心的两个函数我已经贴在了下方
这里其实之前贴的那个帖子说的挺好的 这里有点像是语法糖 传入 其实本质上做了类型转换 我们只需要传入这个类型的函数 而不需要自己去定义新的类然后实现ServeHTTP这个函数

然后我们仔细看看Handle 函数 其实很简单
没有m 初始化一个 然后把我们的url对应的函数写入结构体 对于末尾是/的模式串单独写入es
那最后我们看看ServeMuxServeHTTP 默认的多路复用器是怎么复用的

type HandlerFunc func(ResponseWriter, *Request)

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

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	if pattern == "" {
		panic("http: invalid pattern")
	}
	if handler == nil {
		panic("http: nil handler")
	}
	if _, exist := mux.m[pattern]; exist {
		panic("http: multiple registrations for " + pattern)
	}

	if mux.m == nil {
		mux.m = make(map[string]muxEntry)
	}
	e := muxEntry{h: handler, pattern: pattern}
	mux.m[pattern] = e
	if pattern[len(pattern)-1] == '/' {
		mux.es = appendSorted(mux.es, e)
	}

	if pattern[0] != '/' {
		mux.hosts = true
	}
}

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

2、mux.ServeHTTP(w ResponseWriter, r *Request)

长话短说 h.ServeHTTP(w, r) 是真正的执行函数 而找到handler是在mux.Handler(r)内部 递归的看看 贴在了下面
最后找handler 又是递归到了mux.handler(host, path string)

然后就发现 我们如何找到handler 就是mutex.match来找 正常匹配的话 则是内部mux.match 递归再看看
ok 显然易见了 直接从map中取 如果没有找到 则是去es 去模糊匹配 而且es内部的字符串是从长到短进行排序 这个可以从上一篇mtx.HandleFunc()去找 mux.es = appendSorted(mux.es, e)

基本上 这个net/http已经被我们梳理了出来了 文章基本到这里就差不多了

// 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.  mux.es contains all patterns
	// that end in / sorted from longest to shortest.
	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil, ""
}

-------------------------------

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
}

--------------------------

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

	if r.Method == "CONNECT" {
		if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
			return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
		}

		return mux.handler(r.Host, r.URL.Path)
	}

	host := stripHostPort(r.Host)
	path := cleanPath(r.URL.Path)

	if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
		return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
	}

	if path != r.URL.Path {
		_, pattern = mux.handler(host, path)
		u := &url.URL{Path: path, RawQuery: r.URL.RawQuery}
		return RedirectHandler(u.String(), StatusMovedPermanently), pattern
	}

	return mux.handler(host, r.URL.Path)
}

------------------------

// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	if r.RequestURI == "*" {
		if r.ProtoAtLeast(1, 1) {
			w.Header().Set("Connection", "close")
		}
		w.WriteHeader(StatusBadRequest)
		return
	}
	h, _ := mux.Handler(r)
	h.ServeHTTP(w, r)
}

4、简单总结一下


这里面博客写的挺全的 想下楼转一转了
后面面试的时候 再想想咋总结了 = = 晚上回来还要再学一学GRPC 那就这样啦

想看点更相信的可以烦请跳转到下面链接啦~
Introduction 万字手撕Go http源码server.go