深入理解Golang之http server的实现

2020-01-28 14:26:07王旭

ServerListenAndServe 方法中,会初始化监听地址 Addr ,同时调用 Listen 方法设置监听。最后将监听的TCP对象传入 Serve 方法:


func (srv *Server) Serve(l net.Listener) error {
 ...

 baseCtx := context.Background() // base is always background, per Issue 16220
 ctx := context.WithValue(baseCtx, ServerContextKey, srv)
 for {
  rw, e := l.Accept() // 等待新的连接建立

  ...

  c := srv.newConn(rw)
  c.setState(c.rwc, StateNew) // before Serve can return
  go c.serve(ctx) // 创建新的协程处理请求
 }
}

这里隐去了一些细节,以便了解 Serve 方法的主要逻辑。首先创建一个上下文对象,然后调用 ListenerAccept() 等待新的连接建立;一旦有新的连接建立,则调用 ServernewConn() 创建新的连接对象,并将连接的状态标志为 StateNew ,然后开启一个新的 goroutine 处理连接请求。

处理连接

我们继续探索 connserve() 方法,这个方法同样很长,我们同样只看关键逻辑。坚持一下,马上就要看见大海了。


func (c *conn) serve(ctx context.Context) {

 ...

 for {
  w, err := c.readRequest(ctx)
  if c.r.remain != c.server.initialReadLimitSize() {
   // If we read any bytes off the wire, we're active.
   c.setState(c.rwc, StateActive)
  }

  ...

  // 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.
  serverHandler{c.server}.ServeHTTP(w, w.req)
  w.cancelCtx()
  if c.hijacked() {
   return
  }
  w.finishRequest()
  if !w.shouldReuseConnection() {
   if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
    c.closeWriteAndWait()
   }
   return
  }
  c.setState(c.rwc, StateIdle) // 请求处理结束后,将连接状态置为空闲
  c.curReq.Store((*response)(nil))// 将当前请求置为空

  ...
 }
}

当一个连接建立之后,该连接中所有的请求都将在这个协程中进行处理,直到连接被关闭。在 serve() 方法中会循环调用 readRequest() 方法读取下一个请求进行处理,其中最关键的逻辑就是一行代码:


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

进一步解释 serverHandler


type serverHandler struct {
 srv *Server
}

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