【golang】http.ListenAndServe源码解析

http.ListenAndServe

go 复制代码
func ListenAndServe(addr string, handler Handler) error

ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。handler参数一般会设为nil,此时会使用DefaultServeMux。

接下来我们看一下这个函数的主要源码流程。

一、通过ListenAndServe的参数创建Server结构体

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

Server 定义运行HTTP服务器的参数。Server的零值是一个有效的配置。

第一层相当于封装了一下创建Server结构体的过程,更易于用户使用,创建Server结构体之后,调用Server结构体的ListenAndServe方法。

二、定义监听信息(Listen)调用Serve处理请求

go 复制代码
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)
}

ListenAndServe监听TCP网络地址 srv.Addr,然后调用服务来处理传入连接的请求。

第二层首先根据srv中Addr定义了监听信息ln, err := net.Listen("tcp", addr),然后把监听对象ln作为srv(Server)对象调用Serve方法的参数。

三、监听器持续监听并Accept请求创建连接,处理连接

go 复制代码
func (srv *Server) Serve(l net.Listener) error {
	......
		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)  //开启单独协程处理连接
			}
		}

服务接收监听器上的传入连接,创建一个新的服务协程为每个连接。

Serve方法开启一个for循环,for循环中不断从监听器中接受每一个请求Accept,然后根据获取到的请求创建新的连接,最后开启一个新的协程服务go c.serve(connCtx)单独处理这个连接。

四、serve服务处理这个连接

go 复制代码
func (c *conn) serve(ctx context.Context) {
	......
	serverHandler{c.server}.ServeHTTP(w, w.req)
	......
}

第四层主要流程serve函数中会判断是否为https连接,如果是就升级为https连接。接着创建读写文本,最后通过serverHandler结构体调用相应的ServeHTTP方法处理。

五、ServeHttp具体处理逻辑

go 复制代码
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{}
	}

	handler.ServeHTTP(rw, req)
}

第五层主要流程是如果server结构中自带了相应的Handler对象的话,就调用handler自己实现的ServeHTTP函数,如果没有自带的话,就使用标准库中默认DefaultServeMux作为handler。

六、DefaultServeMux默认处理逻辑

go 复制代码
//部分源码
type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry 
	hosts bool       
}

type muxEntry struct {
	h       Handler
	pattern string
}

var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

......

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

可以看到DefaultServeMux就是ServeMux数据结构的实例对象,标准库中路由注册的默认数据结构是map,key是接口的字符串,value是处理器。

第六层的主要流程处理逻辑为先使用request为参数,通过调用ServeMux中Handler方法查询ServeMux对象中的map,得到相应的handler,handler调用ServeHTTP函数处理。

go 复制代码
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
  • 如果用户自定义实现了Handler,那么根据相应路径在map中查询到相对应的Handler,然后再调用用户自定义的ServeHTTP处理请求。

  • 如果用户没有自定义Handler,只注册了对应处理函数(使用了http.HandleFunc),那么就会根据默认DefaultServeMux去map查询到这个函数类型Handler,然后再调用ServeHTTP处理函数。下面就是对应部分源码,可以看到,调用的ServeHTTP中又回调了用户自定义的函数。

go 复制代码
type HandlerFunc func(ResponseWriter, *Request)

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

总结

以上就是当调用接口时候ListenAndServe的大致流程。而其实不管是Golang本身标准库或者是市面上许多go-web框架也好,都是先把对应接口请求和处理加入到某个数据结构中,然后监听请求,被调用时,再去这个数据结构中查询再进行处理。

相关推荐
落落落sss8 分钟前
MQ集群
java·服务器·开发语言·后端·elasticsearch·adb·ruby
2401_8532757329 分钟前
ArrayList 源码分析
java·开发语言
zyx没烦恼29 分钟前
【STL】set,multiset,map,multimap的介绍以及使用
开发语言·c++
lb363636363629 分钟前
整数储存形式(c基础)
c语言·开发语言
feifeikon31 分钟前
Python Day5 进阶语法(列表表达式/三元/断言/with-as/异常捕获/字符串方法/lambda函数
开发语言·python
大鲤余38 分钟前
Rust,删除cargo安装的可执行文件
开发语言·后端·rust
浪里个浪的102441 分钟前
【C语言】从3x5矩阵计算前三行平均值并扩展到4x5矩阵
c语言·开发语言·矩阵
她说彩礼65万1 小时前
Asp.NET Core Mvc中一个视图怎么设置多个强数据类型
后端·asp.net·mvc
MoFe11 小时前
【.net core】【sqlsugar】字符串拼接+内容去重
java·开发语言·.netcore
陈随易1 小时前
农村程序员-关于小孩教育的思考
前端·后端·程序员