net/http的server端的源码学习
先创建一个简单的demo,执行后的调用链路在这里
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
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.(*response).finishRequest // 返回响应内容
至此大概的流程分析完成,可以看到官方的net包创建的server服务有几个问题:
- 每个新连接都会启动一个新协程,连接关闭后协程也销毁了,不能复用
- 路由匹配逻辑过于简单,也不高效
- 路由没有区分请求类型,如GET/POST
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: