[源码学习]net/http的server端

先创建一个简单的demo(HTTP/1.1),执行后的调用链路在这里

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("X-Debug", "1")
        w.Write([]byte("Hello World"))
    })

    if err := http.ListenAndServe(":8000", nil); err != nil {
        fmt.Println("start http server fail:", err)
    }
}

我们知道用c语言实现一个tcp服务,用到的系统调用有socket,bind,listen,我们看看在go中是如何做的。
go编译运行后,程序会阻塞监听在8080端口上,这个阶段的调用链路如下:

|-goroutine-1 created by runtime.main
  |-main.main
    |-net/http.(*ServeMux).Handle       // 路由注册到net/http.DefaultServeMux的map中
      |-net/http.appendSorted
    |-net/http.(*Server).ListenAndServe
      |-net.Listen
        |-net.(*ListenConfig).Listen
          |-net.(*sysListener).listenTCP
            |-net.internetSocket
              |-net.socket
                |-net.sysSocket
                  |-runtime/internal/syscall.Syscall6 // 系统调用socket
                |-net.setDefaultSockopts
                |-net.listenerBacklog
                |-net.(*netFD).listenStream
                  |-net.setDefaultListenerSockopts
                  |-net.(*TCPAddr).sockaddr
                  |-syscall.Bind                      // 系统调用bind
                  |-listenFunc(fd.pfd.Sysfd, backlog) // 系统调用listen
                  |-internal/poll.(*FD).Init
                  |-net.sockaddrToTCP
      |-net/http.(*Server).Serve
        |-net/http.(*onceCloseListener).Accept
          |-net.(*TCPListener).Accept
            |-net.(*TCPListener).accept
              |-net.(*netFD).accept
                |-internal/poll.(*FD).Accept
                  |-internal/poll.(*pollDesc).wait
                    |-internal/poll.runtime_pollWait
                      |-netpollblock                 // 协程挂起,等有新连接了,netpoll模型唤醒它
        |-net/http.(*Server).Serve.func3             // 新连接用新协程来服务它
          |-go c.serve(connCtx)

模拟请求一下8000端口,看看新协程的服务流程:

|-goroutine-18 created by net/http.(*Server).Serve.func3
  |-net/http.(*conn).serve
    |-net/http.(*conn).readRequest       // 读取请求
    |-net/http.serverHandler.ServeHTTP   // 这里如果没有serverMux,则用net/http.DefaultServeMux
      |-net/http.(*ServeMux).ServeHTTP
        |-net/http.(*ServeMux).Handler
          |-net/http.(*ServeMux).handler
            |-net/http.(*ServeMux).match // 这里通过请求的path匹配对应的handle(全字匹配/前缀匹配)
        |-net/http.HandlerFunc.ServeHTTP 
          |-main.main.func1              // handle方法就是demo代码里面定义的
    |-net/http.(*conn).hijacked          // 检查是否有劫持,参考http://u301.co/am
    |-net/http.(*response).finishRequest // 返回响应内容

我们知道发起http请求,服务端返回对应的http响应文本,那这些文本产生的时机是在什么时候呢,我们进一步分析goroutine-18的执行栈如下:

|-goroutine-18 created by net/http.(*Server).Serve.func3
  |-net/http.(*conn).serve
    |-w = net/http.(*conn).readRequest
      |-w.w = newBufioWriterSize(&w.cw, ...)
    |-net/http.serverHandler.ServeHTTP(w, w.req)
      |-w.Write("Hello World")
        |-w.WriteHeader(200)
        |-w.w.Write("Hello World")   // 存入w.w.buf(内存不够就提前调用w.w.wr.Write)
    |-w.finishRequest()
      |-w.w.Flush()
        |-w.w.wr.Write(p)            // p即是w.w.buf
          |-w.w.wr.writeHeader(p)          
            |-writeStatusLine(w.w.wr.res.conn.bufw, is11, 200, ...) // 这里写:HTTP/1.1 200 OK\r\n
            |-WriteSubset(w.w.wr.res.conn.bufw, excludeHeader)      // 这里写:自定义header X-Debug
            |-setHeader.Write(w.w.wr.res.conn.bufw)                 // 这里写:常规的header
            |-w.w.wr.res.conn.bufw.Write(crlf)                      // 多一个空行
          |-w.w.wr.res.conn.bufw.Write(p)                           // 这里写:Hello World

// 涉及到的两个重要的结构体如下:
w.cw = net/http.chunkWriter {
    res *response
    header Header
    wroteHeader bool
    chunking bool
}
w.w = bufio.Writer {
    buf []byte
    wr  io.Writer
}

至此大概的流程分析完成,可以看到官方的net包创建的server服务有几个问题:

  1. 每个新连接都会启动一个新协程,连接关闭后协程也销毁了,不能复用
  2. 路由匹配逻辑过于简单,也不高效
  3. 路由没有区分请求类型,如GET/POST
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!