11# 3.3 Go如何使得Web工作
2+
23前面小节介绍了如何通过Go搭建一个Web服务,我们可以看到简单应用一个net/http包就方便的搭建起来了。那么Go在底层到底是怎么做的呢?万变不离其宗,Go的Web服务工作也离不开我们第一小节介绍的Web工作方式。
34
45## web工作方式的几个概念
@@ -33,46 +34,117 @@ Handler:处理请求和生成返回信息的处理逻辑
3334- 如何接收客户端请求?
3435- 如何分配handler?
3536
36- 前面小节的代码里面我们可以看到,Go是通过一个函数` ListenAndServe ` 来处理这些事情的,这个底层其实这样处理的:初始化一个server对象,然后调用了` net.Listen("tcp", addr) ` ,也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。
37+ 前面小节的代码里面我们可以看到,Go是通过一个函数` ListenAndServe ` 来处理这些事情的,其实现源码如下:
38+
39+ ``` Go
40+ func ListenAndServe (addr string , handler Handler ) error {
41+ server := &Server{Addr: addr, Handler: handler}
42+ return server.ListenAndServe ()
43+ }
44+
45+ ```
46+
47+ ` ListenAndServe ` 会初始化一个` sever ` 对象,然后调用了` Server ` 对象的方法` ListenAndServe ` 。其源码如下:
48+
49+ ``` Go
50+ func (srv *Server ) ListenAndServe () error {
51+ if srv.shuttingDown () {
52+ return ErrServerClosed
53+ }
54+ addr := srv.Addr
55+ if addr == " " {
56+ addr = " :http"
57+ }
58+ ln , err := net.Listen (" tcp" , addr)
59+ if err != nil {
60+ return err
61+ }
62+ return srv.Serve (ln)
63+ }
64+
65+ ```
66+
67+ ` ListenAndServe ` 调用了` net.Listen("tcp", addr) ` ,也就是底层用TCP协议搭建了一个服务,最后调用` src.Serve ` 监控我们设置的端口。监控之后如何接收客户端的请求呢?
68+
69+ ` Serve ` 的具体实现如下(为突出重点,仅展示关键代码),通过下面的分析源码我们可以看到客户端请求的具体处理过程:
3770
38- 下面代码来自Go的http包的源码,通过下面的代码我们可以看到整个的http处理过程:
3971``` Go
4072
4173func (srv *Server ) Serve (l net .Listener ) error {
42- defer l.Close ()
43- var tempDelay time.Duration // how long to sleep on accept failure
74+ ...
75+
76+ ctx := context.WithValue (baseCtx, ServerContextKey, srv)
4477 for {
45- rw , e := l.Accept ()
46- if e != nil {
47- if ne , ok := e.(net.Error ); ok && ne.Temporary () {
48- if tempDelay == 0 {
49- tempDelay = 5 * time.Millisecond
50- } else {
51- tempDelay *= 2
52- }
53- if max := 1 * time.Second ; tempDelay > max {
54- tempDelay = max
55- }
56- log.Printf (" http: Accept error: %v ; retrying in %v " , e, tempDelay)
57- time.Sleep (tempDelay)
58- continue
78+ rw , err := l.Accept ()
79+ ...
80+
81+ connCtx := ctx
82+ if cc := srv.ConnContext ; cc != nil {
83+ connCtx = cc (connCtx, rw)
84+ if connCtx == nil {
85+ panic (" ConnContext returned nil" )
5986 }
60- return e
6187 }
6288 tempDelay = 0
63- c , err := srv.newConn (rw)
64- if err != nil {
65- continue
66- }
67- go c.serve ()
89+ c := srv.newConn (rw)
90+ c.setState (c.rwc , StateNew, runHooks) // before Serve can return
91+ go c.serve (connCtx)
6892 }
6993}
7094
7195```
72- 监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了` srv.Serve(net.Listener) ` 函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个` for{} ` ,首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:` go c.serve() ` 。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。
7396
74- 那么如何具体分配到相应的函数来处理请求呢?conn首先会解析request: ` c.readRequest ()` ,然后获取相应的handler: ` handler := c.server.Handler ` ,也就是我们刚才在调用函数 ` ListenAndServe ` 时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取 ` handler = DefaultServeMux ` ,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了 ` http.HandleFunc("/", sayhelloName) ` 嘛。这个作用就是注册了请求 ` / ` 的路由规则,当请求uri为"/",路由就会转到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端 。
97+ 这个函数里面起了一个 ` for{} ` ,首先通过Listener接收请求: ` l.Accept ()` ,其次创建一个Conn: ` c := srv.newConn(rw) ` ,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务: ` go c.serve(connCtx) ` 。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响 。
7598
99+ 那么如何具体分配到相应的函数来处理请求呢?我们继续分析conn的` serve ` 方法,其源码如下(为突出重点,仅展示关键代码):
100+
101+ ``` Go
102+ func (c *conn ) serve (ctx context .Context ) {
103+ ...
104+
105+ ctx , cancelCtx := context.WithCancel (ctx)
106+ c.cancelCtx = cancelCtx
107+ defer cancelCtx ()
108+
109+ c.r = &connReader{conn: c}
110+ c.bufr = newBufioReader (c.r )
111+ c.bufw = newBufioWriterSize (checkConnErrorWriter{c}, 4 <<10 )
112+
113+ for {
114+ w , err := c.readRequest (ctx)
115+ ...
116+
117+ // HTTP cannot have multiple simultaneous active requests.[*]
118+ // Until the server replies to this request, it can't read another,
119+ // so we might as well run the handler in this goroutine.
120+ // [*] Not strictly true: HTTP pipelining. We could let them all process
121+ // in parallel even if their responses need to be serialized.
122+ // But we're not going to implement HTTP pipelining because it
123+ // was never deployed in the wild and the answer is HTTP/2.
124+ serverHandler{c.server }.ServeHTTP (w, w.req )
125+ w.cancelCtx ()
126+ ...
127+
128+ }
129+ }
130+ ```
131+
132+ conn首先会解析request:` w, err := c.readRequest(ctx) ` , 然后获取相应的handler去处理请求:` serverHandler{c.server}.ServeHTTP(w, w.req) ` ,` ServeHTTP ` 的具体实现如下:
133+
134+ ``` Go
135+ func (sh serverHandler ) ServeHTTP (rw ResponseWriter , req *Request ) {
136+ handler := sh.srv .Handler
137+ if handler == nil {
138+ handler = DefaultServeMux
139+ }
140+ if req.RequestURI == " *" && req.Method == " OPTIONS" {
141+ handler = globalOptionsHandler{}
142+ }
143+ handler.ServeHTTP (rw, req)
144+ }
145+ ```
146+
147+ ` sh.srv.Handler ` 就是我们刚才在调用函数` ListenAndServe ` 时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取` handler = DefaultServeMux ` ,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了` http.HandleFunc("/", sayhelloName) ` 嘛。这个作用就是注册了请求` / ` 的路由规则,当请求uri为"/",路由就会转到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。
76148
77149详细的整个流程如下图所示:
78150
@@ -82,8 +154,8 @@ func (srv *Server) Serve(l net.Listener) error {
82154
83155至此我们的三个问题已经全部得到了解答,你现在对于Go如何让Web跑起来的是否已经基本了解了呢?
84156
85-
86157## links
87- * [ 目录] ( < preface.md > )
88- * 上一节: [ GO搭建一个简单的web服务] ( < 03.2.md > )
89- * 下一节: [ Go的http包详解] ( < 03.4.md > )
158+
159+ * [ 目录] ( < preface.md > )
160+ * 上一节: [ GO搭建一个简单的web服务] ( < 03.2.md > )
161+ * 下一节: [ Go的http包详解] ( < 03.4.md > )
0 commit comments