1
1
# 3.3 Go如何使得Web工作
2
+
2
3
前面小节介绍了如何通过Go搭建一个Web服务,我们可以看到简单应用一个net/http包就方便的搭建起来了。那么Go在底层到底是怎么做的呢?万变不离其宗,Go的Web服务工作也离不开我们第一小节介绍的Web工作方式。
3
4
4
5
## web工作方式的几个概念
@@ -33,46 +34,117 @@ Handler:处理请求和生成返回信息的处理逻辑
33
34
- 如何接收客户端请求?
34
35
- 如何分配handler?
35
36
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 ` 的具体实现如下(为突出重点,仅展示关键代码),通过下面的分析源码我们可以看到客户端请求的具体处理过程:
37
70
38
- 下面代码来自Go的http包的源码,通过下面的代码我们可以看到整个的http处理过程:
39
71
``` Go
40
72
41
73
func (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)
44
77
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" )
59
86
}
60
- return e
61
87
}
62
88
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)
68
92
}
69
93
}
70
94
71
95
```
72
- 监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了` srv.Serve(net.Listener) ` 函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个` for{} ` ,首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:` go c.serve() ` 。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。
73
96
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去服务,相互不影响 。
75
98
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的信息反馈到客户端。
76
148
77
149
详细的整个流程如下图所示:
78
150
@@ -82,8 +154,8 @@ func (srv *Server) Serve(l net.Listener) error {
82
154
83
155
至此我们的三个问题已经全部得到了解答,你现在对于Go如何让Web跑起来的是否已经基本了解了呢?
84
156
85
-
86
157
## 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