Skip to content

Commit 6e11478

Browse files
committed
完成第三章的书写
1 parent 50ae2df commit 6e11478

File tree

6 files changed

+232
-92
lines changed

6 files changed

+232
-92
lines changed

3.2.md

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#3.2 GO搭建一个web服务器
22

3-
前面小节已经介绍了Web是基于http协议的一个服务,Go语言里面提供了一个完善的net/http包,通过http包可以很方便的就搭建起来一个可以运行的web服务。使用这个包能很简单地对web的路由,静态文件,模版,cookie等数据进行设置和操作。
3+
前面小节已经介绍了Web是基于http协议的一个服务,Go语言里面提供了一个完善的net/http包,通过http包可以很方便的就搭建起来一个可以运行的web服务。同时使用这个包能很简单地对web的路由,静态文件,模版,cookie等数据进行设置和操作。
44

55
##http包建立web服务器
66

@@ -10,10 +10,11 @@
1010
"fmt"
1111
"net/http"
1212
"strings"
13+
"log"
1314
)
1415

1516
func sayhelloName(w http.ResponseWriter, r *http.Request) {
16-
r.ParseForm()
17+
r.ParseForm() //解析参数,默认是不会解析的
1718
fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
1819
fmt.Println("path", r.URL.Path)
1920
fmt.Println("scheme", r.URL.Scheme)
@@ -26,8 +27,11 @@
2627
}
2728

2829
func main() {
29-
http.HandleFunc("/", sayhelloName)
30-
http.ListenAndServe(":9090", nil)
30+
http.HandleFunc("/", sayhelloName) //设置访问的路由
31+
err := http.ListenAndServe(":9090", nil) //设置监听的端口
32+
if err != nil {
33+
log.Fatal("ListenAndServe: ", err)
34+
}
3135
}
3236

3337
上面这个代码,我们build之后,然后执行web.exe,这个时候其实已经在9090端口监听tcp链接请求了。
@@ -52,7 +56,7 @@
5256
5357
>- 如果你以前是ruby程序员,那么和ROR的/script/server启动有点类似。
5458
55-
我们看到我们通过简单的几行代码就已经运行起来一个web服务了,而且这个web服务内部已经支持了高并发的特性,我将会在接下来的两个小节里面详细的讲解一下go是如何实现web高并发的
59+
我们看到Go通过简单的几行代码就已经运行起来一个web服务了,而且这个Web服务内部已经支持了高并发的特性,我将会在接下来的两个小节里面详细的讲解一下go是如何实现Web高并发的
5660

5761

5862

3.3.md

+49-85
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,49 @@
1-
#3.3 Go如何使得web工作
2-
前面小节介绍了如何通过Go搭建一个web服务,我们可以看到简单的应用了一个net/http包就方便的搭建起来了。那么他底层到底是怎么做的呢?但是万变不离其宗,他离不开我们第一小节介绍的web工作方式。
3-
4-
##对应web工作方式的几个概念
5-
6-
Request:用户请求的信息
7-
8-
Response:服务器需要反馈给客户端的信息
9-
10-
Conn:用户的每次请求链接
11-
12-
Handler:处理请求和生成返回信息的处理逻辑
13-
14-
##分析http包运行机制
15-
16-
![](images/3.3.http.png?raw=true)
17-
18-
(1) 创建listen socket, 在指定的监听端口, 等待客户端请求的到来
19-
20-
(2) listen socket接受客户端的请求, 得到client socket, 接下来通过client socket与客户端通信
21-
22-
(3) 处理客户端的请求, 首先从client socket读取http请求的协议头, 如果是post协议, 还可能要
23-
读取客户端上传的数据, 然后处理请求, 准备好客户端需要的数据, 通过client socket写给客户端
24-
25-
26-
27-
##web执行的过程分析
28-
29-
针对前一小节里面的代码,我们来一行行的分析一下,大概的http包里面执行流程应该是这样:
30-
31-
- 首先调用Http.HandleFunc
32-
33-
按顺序做了几件事:
34-
35-
1 调用了DefaultServerMux的HandleFunc
36-
37-
2 调用了DefaultServerMux的Handle
38-
39-
3 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则
40-
41-
- 其次调用http.ListenAndServe(":12345", nil)
42-
43-
按顺序做了几件事情:
44-
45-
1 实例化Server
46-
47-
2 调用Server的ListenAndServe()
48-
49-
3 调用net.Listen("tcp", addr)监听端口
50-
51-
4 启动一个for循环,在循环体中Accept请求
52-
53-
5 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()
54-
55-
6 读取每个请求的内容w, err := c.readRequest()
56-
57-
7 判断handler是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux
58-
59-
8 调用handler的ServeHttp
60-
61-
9 在这个例子中,下面就进入到DefaultServerMux.ServeHttp
62-
63-
10 根据request选择handler,并且进入到这个handler的ServeHTTP
64-
65-
mux.handler(r).ServeHTTP(w, r)
66-
67-
11 选择handler:
68-
69-
A 判断是否有路由能满足这个request(循环遍历ServerMux的muxEntry)
70-
71-
B 如果有路由满足,调用这个路由handler的ServeHttp
72-
73-
C 如果没有路由满足,调用NotFoundHandler的ServeHttp
74-
75-
76-
77-
78-
79-
## links
80-
* [目录](<preface.md>)
81-
* 上一节: [GO搭建一个简单的web服务](<3.2.md>)
82-
* 下一节: [Go的http包执行原理](<3.4.md>)
83-
84-
## LastModified
85-
* $Id$
1+
#3.3 Go如何使得Web工作
2+
前面小节介绍了如何通过Go搭建一个Web服务,我们可以看到简单的应用了一个net/http包就方便的搭建起来了。那么他底层到底是怎么做的呢?但是万变不离其宗,他离不开我们第一小节介绍的Web工作方式。
3+
4+
##对应web工作方式的几个概念
5+
6+
以下均是服务器端的相应概念
7+
8+
Request:用户请求的信息,用来解析用户的请求信息,包括post、get、cookie、url等信息
9+
10+
Response:服务器需要反馈给客户端的信息
11+
12+
Conn:用户的每次请求链接
13+
14+
Handler:处理请求和生成返回信息的处理逻辑
15+
16+
##分析http包运行机制
17+
18+
如下图所示,是Go实现Web工作模式的流程图
19+
20+
![](images/3.3.http.png?raw=true)
21+
22+
(1) 创建listen socket, 在指定的端口监听, 等待客户端请求的到来
23+
24+
(2) listen socket接受客户端的请求, 得到client socket, 接下来通过client socket与客户端通信
25+
26+
(3) 处理客户端的请求, 首先从client socket读取http请求的协议头, 如果是post协议, 还可能要读取客户端上传的数据, 然后跟给相应的handler处理请求, 准备好客户端需要的数据, 通过client socket写给客户端
27+
28+
这整个的过程里面我们只要了解清楚下面三个问题,也就知道Go是如何让Web运行起来了
29+
30+
- 如何监听端口?
31+
- 如何接收客户端请求?
32+
- 如何分配handler?
33+
34+
前面小节的代码里面我们可以看到,Go是通过一个函数来操作这个事情的`ListenAndServe`来监听起来的,这个底层其实这样处理的:初始化一个server对象,然后调用了`net.Listen("tcp", addr)`,也就是底层起的是TCP协议,然后监控了我们设置的端口。
35+
36+
监控之后如何接收客户端的请求呢?上面的监控端口之后,就调用了`srv.Serve(net.Listener)`函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个`for{}`,里面通过Listener接收请求,然后起一个Conn,然后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:`go c.serve()`。这个就是高并发体现了,用户来的请求都是goroutine去服务,相互不影响。
37+
38+
那么如何具体分配到相应的函数来处理请求呢?conn首先会解析request:`c.readRequest()`,然后获取相应的handler:`handler := c.server.Handler`,也就是我们刚才在调用函数`ListenAndServe`时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取`handler = DefaultServeMux`,那么这个是哪里来的东西呢?对,这个东西就是路由器,也就是把相应的请求url对应请求函数的路由器,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了`http.HandleFunc("/", sayhelloName)`嘛。这个就是注册了相应的路由,url为"/"的请求到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。
39+
40+
至此我们的三个问题已经全部得到了解答,你现在对于Go如何让Web跑起来的是否已经大概清楚了呢?
41+
42+
43+
## links
44+
* [目录](<preface.md>)
45+
* 上一节: [GO搭建一个简单的web服务](<3.2.md>)
46+
* 下一节: [Go的http包详解](<3.4.md>)
47+
48+
## LastModified
49+
* $Id$

3.4.md

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#3.4 Go的http包详解
2+
前面小节介绍了Go怎么样实现了Web工作模式的一个流程,这一小节,我们讲来详细的解剖一下http包,他到底怎么样实现整个的过程的。
3+
4+
Go的http有两个核心功能:Conn、ServeMux
5+
6+
##Conn的goroutine
7+
与我们一般编写的http服务器不同, Go为了实现高并发和高性能, 使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证。
8+
9+
Go在等待客户端请求里面是这样写的:
10+
11+
c, err := srv.newConn(rw)
12+
if err != nil {
13+
continue
14+
}
15+
go c.serve()
16+
17+
这里我们可以看到客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到handler的时候可以读取到相应的header信息,这样保证了每个请求的独立性。
18+
19+
##ServeMux的自定义
20+
我们前面小节讲述conn.server的时候,其实内部是调用了http包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢?
21+
22+
它的结构如下:
23+
24+
type ServeMux struct {
25+
mu sync.RWMutex //锁,由于请求设计到并发处理,因此这里需要一个锁机制
26+
m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
27+
}
28+
29+
下面看一下muxEntry
30+
31+
type muxEntry struct {
32+
explicit bool // 是否精确匹配
33+
h Handler // 这个路由表达式对应哪个handler
34+
}
35+
36+
下面看一下handler的定义
37+
38+
type Handler interface {
39+
ServeHTTP(ResponseWriter, *Request) // 路由实现器
40+
}
41+
42+
handler是一个接口,但是我们定义的函数`sayhelloName`没有实现ServeHTTP这个接口,为什么能添加呢?原来在http包里面还定义了一个类型`HandlerFunc`,我们定义的函数`sayhelloName`就是这个handlerFunc调用之后的结果,这个类型默认就实现了ServeHTTP这个接口,即我们调用了HandlerFunc(f),类似强制类型转换f成为handlerFunc类型,这样f就拥有了ServHTTP方法。
43+
44+
type HandlerFunc func(ResponseWriter, *Request)
45+
46+
// ServeHTTP calls f(w, r).
47+
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
48+
f(w, r)
49+
}
50+
51+
路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?
52+
53+
路由器接收到请求之后调用`mux.handler(r).ServeHTTP(w, r)`
54+
55+
也就是调用对应路由的handler的ServerHTTP接口,那么mux.handler(r)怎么处理的呢?
56+
57+
func (mux *ServeMux) handler(r *Request) Handler {
58+
mux.mu.RLock()
59+
defer mux.mu.RUnlock()
60+
61+
// Host-specific pattern takes precedence over generic ones
62+
h := mux.match(r.Host + r.URL.Path)
63+
if h == nil {
64+
h = mux.match(r.URL.Path)
65+
}
66+
if h == nil {
67+
h = NotFoundHandler()
68+
}
69+
return h
70+
}
71+
72+
原来他是根据用户请求的URL和路由器里面存储的map去匹配的,当匹配到之后返回存储的handler,然后handler实现了ServHTTP接口,调用这个ServHTTP就可以执行到相应的函数了。
73+
74+
通过上面这个介绍,我们了解了路由器的整个执行过程,Go其实支持外部实现的路由器,因为我们在调用`ListenAndServe`的时候,第二个参数就是Handler,只要实现了ServHTTP函数就可以。
75+
76+
如下代码所示,我们自己实现了一个简易的路由器
77+
78+
package main
79+
80+
import (
81+
"fmt"
82+
"net/http"
83+
)
84+
85+
type MyMux struct {
86+
}
87+
88+
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
89+
if r.URL.Path == "/" {
90+
sayhelloName(w, r)
91+
return
92+
}
93+
http.NotFound(w, r)
94+
return
95+
}
96+
97+
func sayhelloName(w http.ResponseWriter, r *http.Request) {
98+
fmt.Fprintf(w, "Hello myroute!")
99+
}
100+
101+
func main() {
102+
mux := &MyMux{}
103+
http.ListenAndServe(":9090", mux)
104+
}
105+
106+
##Go代码的执行流程
107+
108+
通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程。
109+
110+
- 首先调用Http.HandleFunc
111+
112+
按顺序做了几件事:
113+
114+
1 调用了DefaultServerMux的HandleFunc
115+
116+
2 调用了DefaultServerMux的Handle
117+
118+
3 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则
119+
120+
- 其次调用http.ListenAndServe(":9090", nil)
121+
122+
按顺序做了几件事情:
123+
124+
1 实例化Server
125+
126+
2 调用Server的ListenAndServe()
127+
128+
3 调用net.Listen("tcp", addr)监听端口
129+
130+
4 启动一个for循环,在循环体中Accept请求
131+
132+
5 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()
133+
134+
6 读取每个请求的内容w, err := c.readRequest()
135+
136+
7 判断handler是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux
137+
138+
8 调用handler的ServeHttp
139+
140+
9 在这个例子中,下面就进入到DefaultServerMux.ServeHttp
141+
142+
10 根据request选择handler,并且进入到这个handler的ServeHTTP
143+
144+
mux.handler(r).ServeHTTP(w, r)
145+
146+
11 选择handler:
147+
148+
A 判断是否有路由能满足这个request(循环遍历ServerMux的muxEntry)
149+
150+
B 如果有路由满足,调用这个路由handler的ServeHttp
151+
152+
C 如果没有路由满足,调用NotFoundHandler的ServeHttp
153+
154+
## links
155+
* [目录](<preface.md>)
156+
* 上一节: [Go如何使得web工作](<3.3.md>)
157+
* 下一节: [小节](<3.5.md>)
158+
159+
## LastModified
160+
* $Id$

3.5.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#3.5小节
2+
这一章我们通过介绍Web工作方式介绍了HTTP协议的相关信息,通过类比引出了Go怎么运行Web,实现了Go的web服务器,通过对Go运行Web的代码进行分析,深入了解了Go执行Web的整个流程,最后我们对Go语言的http包进行了详细的分析。
3+
4+
通过这一章的学习我希望你能够对Go开发Web有了初步的了解,我们也看到相应的代码了,这个开发是很方便的,同时又是相当的灵活。
5+
6+
## links
7+
* [目录](<preface.md>)
8+
* 上一节: [Go的http包详解](<3.4.md>)
9+
* 下一章: [小节](<4.md>)
10+
11+
## LastModified
12+
* $Id$

3.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* 1. [web工作方式](3.1.md)
55
* 2. [GO搭建一个简单的web服务](3.2.md)
66
* 3. [Go如何使得web工作](3.3.md)
7-
* 4. [Go的http包执行原理](3.4.md)
7+
* 4. [Go的http包详解](3.4.md)
88
* 5. [小结](3.5.md)
99

1010
学习基于Web的编程可能正是你读本书的原因。事实上,如何通过Go来编写Web应用也是我编写这本书的初衷。前面已经介绍过,Go目前已经拥有了成熟的Http处理包,这使得编写能做任何事情的动态Web程序易如反掌。在接下来的各章中将要介绍的内容,都是属于Web编程的范畴。本章则集中讨论一些与Web相关的概念和Go如何运行Web程序的话题。

preface.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
- 3.1 [web工作方式](3.1.md)
1818
- 3.2 [GO搭建一个简单的web服务](3.2.md)
1919
- 3.3 [Go如何使得web工作](3.3.md)
20-
- 3.4 [Go的http包执行原理](3.4.md)
20+
- 3.4 [Go的http包详解](3.4.md)
2121
- 3.5 [小结](3.5.md)
2222
* 4.表单
2323
- 4.1 处理表单的输入

0 commit comments

Comments
 (0)