github.com/code-reading/golang@v0.0.0-20220303082512-ba5bc0e589a3/docs/000-http.ListenAndServe机制源码分析.md (about) 1 <!-- vscode-markdown-toc --> 2 * 1. [说明](#) 3 * 2. [分析概要](#-1) 4 * 2.1. [ 名词解释](#-1) 5 * 2.2. [http包执行流程](#http) 6 * 2.3. [http执行流程小结](#http-1) 7 * 3. [源码分析](#-1) 8 * 3.1. [端口监听](#-1) 9 * 3.2. [接收请求](#-1) 10 * 3.3. [读取请求并解析](#-1) 11 * 3.4. [选择路由器](#-1) 12 * 3.5. [路由分配handler](#handler) 13 14 <!-- vscode-markdown-toc-config 15 numbering=true 16 autoSave=true 17 /vscode-markdown-toc-config --> 18 <!-- /vscode-markdown-toc --> 19 20 ## 1. <a name=''></a>说明 21 22 [分析示例:coding/net/00-listenAndServe.go](../coding/net/00-listenAndServe.go) 23 24 [源码位置:/src/net/http/server.go](../go/src/net/http/server.go) 25 26 ## 2. <a name='-1'></a>分析概要 27 28 ### 2.1. <a name='-1'></a> 名词解释 29 30 Request:用户请求的信息,用来解析用户的请求信息,包括method,Cookie,url等信息。 31 32 Response:服务器需要反馈给客户端的信息。 33 34 Conn:用户的每次请求链接。 35 36 Handle:处理请求和生成返回信息的处理逻辑。 37 38 ### 2.2. <a name='http'></a>http包执行流程 39 40  41 42 *golang 实现web服务的流程* 43 44 创建Listen Socket,监听指定的端口,等待客户端请求到来。 45 Listen Socket接受客户端的请求,得到Client Socket,接下来通过Client Socket与客户端通信。 46 处理客户端请求,首先从Client Socket读取HTTP请求的协议头,如果是POST方法,还可能要读取客户端提交的数据,然后交给相应的handler处理请求,handler处理完,将数据通过Client Socket返回给客户端。 47 48  49 ### 2.3. <a name='http-1'></a>http执行流程小结 50 51 <strong>1、启动监听服务</strong> 52 实例化Server。 53 调用Server的ListenAndServe()。 54 调用net.Listen("tcp",addr)监听端口。 55 启动一个for循环,在循环体中Accept请求。 56 57 <strong>2、注册路由处理器</strong> 58 a. 首先调用Http.HandleFunc,按如下顺序执行: 59 - 调用了DefaultServerMux的HandleFunc。 60 - 调用了DefaultServerMux的Handle。 61 - 往DefaultServerMux的map[string] muxEntry中增加对应的handler和路由规则。 62 63 b. 调用http.ListenAndServe(":9090",nil),按如下顺序执行: 64 65 <strong>3、响应请求并处理逻辑</strong> 66 a.对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()。 67 b.读取每个请求的内容w,err:=c.readRequest()。 68 c.判断handler是否为空,如果没有设置handler,handler默认设置为DefaultServeMux。 69 d.调用handler的ServeHttp。 70 e.根据request选择handler,并且进入到这个handler的ServeHTTP, mux.handler(r).ServeHTTP(w,r) 71 f.选择handler 72 - 判断是否有路由能满足这个request(循环遍历ServeMux的muxEntry)。 73 - 如果有路由满足,调用这个路由handler的ServeHttp。 74 - 如果没有路由满足,调用NotFoundHandler的ServeHttp。 75 76  77 78 ## 3. <a name='-1'></a>源码分析 79 80 ### 3.1. <a name='-1'></a>端口监听 81 搭建 web 服务器的时候有行代码:http.ListenAndServe(":9999", nil) 82 83 http.ListenAndServe 实际上,初始化一个server对象,调用了 server 的 ListenAndServe 方法 84 85 ```go 86 func ListenAndServe(addr string, handler Handler) error { 87 server := &Server{Addr: addr, Handler: handler} 88 return server.ListenAndServe() 89 } 90 ``` 91 然后是 ln, err := net.Listen("tcp", addr) ,用TCP协议搭建了一个服务,监听着设置的端口 92 93 ```go 94 func (srv *Server) ListenAndServe() error { 95 if srv.shuttingDown() { 96 return ErrServerClosed 97 } 98 addr := srv.Addr 99 if addr == "" { 100 addr = ":http" 101 } 102 ln, err := net.Listen("tcp", addr) 103 if err != nil { 104 return err 105 } 106 return srv.Serve(ln) 107 } 108 ``` 109 110 ### 3.2. <a name='-1'></a>接收请求 111 112 ```go 113 for { 114 // 接受监听器listener的请求 115 rw, e := l.Accept() 116 if e != nil { 117 // 监听是否关闭信号 118 select { 119 case <-srv.getDoneChan(): 120 return ErrServerClosed 121 default: 122 } 123 } 124 …… 125 // 创建新连接 126 c := srv.newConn(rw) 127 // 再返回之前,设置连接状态 128 c.setState(c.rwc, StateNew) // before Serve can return 129 // 创建goroutine,真正处理连接 130 go c.serve(ctx) 131 } 132 ``` 133 134 如上, srv.Serve(ln) 中 使用一个for{} 循环接收请求, 135 > 1.首先通过 listener.Accept 接受请求,是不是跟上面提到的socket的运行机制很像了。 136 > 2.接着用接受到的请求创建一个新的 Conn,并设置为New状态。 137 > 3.最后创建 goroutine,真正的处理连接。 138 139 <strong>补充</strong>:每个请求都会创建一个对应的goroutine去处理,所以各个请求之间是相互不影响的,同时提高并发性能。 140 141 ### 3.3. <a name='-1'></a>读取请求并解析 142 143 ```go 144 for { 145 …… 146 147 // 读request请求 148 w, err := c.readRequest(ctx) 149 150 …… 151 // 调用业务层定义的路由 152 serverHandler{c.server}.ServeHTTP(w, w.req) 153 154 …… 155 // flush刷io buffer的数据 156 w.finishRequest() 157 ``` 158 159 go c.serve(ctx) 响应并处理请求, 160 readRequest 便是读取数据,解析请求的地方,包括解析请求的header、body,和一些基本的校验,比如header头信息,请求method等。 161 162 最后将请求的数据赋值到Request,并初始化Response对象,供业务层调用。 163 164 ### 3.4. <a name='-1'></a>选择路由器 165 上面关键流程已经看到了serverHandler{c.server}.ServeHTTP(w, w.req),这个实际上就是调用最开始在main函数定义的handler,并将处理好的Request、Response对象作为参数传入。 166 167 ```go 168 type serverHandler struct { 169 srv *Server 170 } 171 172 func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { 173 handler := sh.srv.Handler 174 if handler == nil { 175 handler = DefaultServeMux 176 } 177 if req.RequestURI == "*" && req.Method == "OPTIONS" { 178 handler = globalOptionsHandler{} 179 } 180 handler.ServeHTTP(rw, req) 181 } 182 ``` 183 184 还记得吗,最上面是这样调用的 http.ListenAndServe(":9999", nil),第二个参数是nil。 185 186 所以,你看在ServeHTTP中,handler = DefaultServeMux,我们使用了默认的路由器,如果 ListenAndServe 不是传nil的话,那就会使用你自己定义的路由器。 187 188 ### 3.5. <a name='handler'></a>路由分配handler 189 好了,我们知道了使用默认的路由器(DefaultServeMux),再看看它是怎么根据路径找对应handler的吧 190 191 路由的过程里面只要弄懂下面的三个问题,就知道 Go 自带的路由是怎么运行的了: 192 193 1.什么时候注册的路由? 194 2.如何根据注册的路由找对应的handler? 195 3.如果没注册路由访问会返回什么? 196 197 先来看看默认路由器(DefaultServeMux)的结构定义: 198 199 ```go 200 type ServeMux struct { 201 mu sync.RWMutex 202 m map[string]muxEntry 203 es []muxEntry 204 hosts bool 205 } 206 ``` 207 其中 m 是一个map,用来存储路由pattern与handler的关系;es 是一个slice,将路由按长度从大到小排序存储起来。 208 209 匹配规则:首先精确匹配 m 中的pattern;如果在 m 不能精确匹配路径时,会在 es 中找到最接近的路由规则:比如注册了两个路径 /a/b/ /a/ ,当请求URL是 /a/b/c时,会匹配到 /a/b/ 而不是 /a/。 210 211 <strong>1. 什么时候注册的路由?</strong> 212 213 还记得吧,在 ListenAndServe 之前,有这么一行代码,http.HandleFunc("/", indexHandler),这个便是在注册路由。来我们把一些检查代码去掉,看看关键代码: 214 215 ```go 216 func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { 217 DefaultServeMux.HandleFunc(pattern, handler) 218 } 219 220 func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { 221 …… 222 mux.Handle(pattern, HandlerFunc(handler)) 223 } 224 225 func (mux *ServeMux) Handle(pattern string, handler Handler) { 226 …… 227 e := muxEntry{h: handler, pattern: pattern} 228 mux.m[pattern] = e 229 if pattern[len(pattern)-1] == '/' { 230 mux.es = appendSorted(mux.es, e) 231 } 232 233 if pattern[0] != '/' { 234 mux.hosts = true 235 } 236 } 237 ``` 238 这就是把路由表往 ServeMux.m 和 ServeMux.es 写的全过程 239 240 <strong>2. 如何根据注册的路由找对应的handler?</strong> 241 242 其实知道怎么写路由表,大概也能猜到是怎么进行找了,无非就是从上面的 m,es 进行匹配。 243 244 我们上面从端口监听,接受请求,读取请求并解析,再到路由分配handler,一路追到 handler.ServeHTTP(rw, req),现在再看看这段代码的实现 245 246 ```go 247 // 根据预设的pattern,将request分配最匹配的handler处理 248 func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { 249 …… 250 h, _ := mux.Handler(r) 251 h.ServeHTTP(w, r) 252 } 253 254 …… 255 256 func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { 257 if mux.hosts { 258 h, pattern = mux.match(host + path) 259 } 260 if h == nil { 261 h, pattern = mux.match(path) 262 } 263 if h == nil { 264 h, pattern = NotFoundHandler(), "" 265 } 266 return 267 } 268 269 func (mux *ServeMux) match(path string) (h Handler, pattern string) { 270 // 优先查找m表 271 v, ok := mux.m[path] 272 if ok { 273 return v.h, v.pattern 274 } 275 276 // 未精确匹配成功,查询es(已排序),路径长的优先匹配 277 for _, e := range mux.es { 278 if strings.HasPrefix(path, e.pattern) { 279 return e.h, e.pattern 280 } 281 } 282 return nil, "" 283 } 284 ``` 285 286 <strong>3. 如果没注册路由访问会返回什么?</strong> 287 288 这个问题其实上面代码已经看到了,Go 内置了一个 NotFoundHandler(),返回 "404 page not found"