|
1 |
| -# 3.3 Go如何使得Web工作 |
2 |
| -前面小节介绍了如何通过Go搭建一个Web服务,我们可以看到简单应用一个net/http包就方便的搭建起来了。那么Go在底层到底是怎么做的呢?万变不离其宗,Go的Web服务工作也离不开我们第一小节介绍的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 |
| - |
21 |
| - |
22 |
| -图3.9 http包执行流程 |
23 |
| - |
24 |
| -1. 创建Listen Socket, 监听指定的端口, 等待客户端请求到来。 |
25 |
| - |
26 |
| -2. Listen Socket接受客户端的请求, 得到Client Socket, 接下来通过Client Socket与客户端通信。 |
27 |
| - |
28 |
| -3. 处理客户端的请求, 首先从Client Socket读取HTTP请求的协议头, 如果是POST方法, 还可能要读取客户端提交的数据, 然后交给相应的handler处理请求, handler处理完毕准备好客户端需要的数据, 通过Client Socket写给客户端。 |
29 |
| - |
30 |
| -这整个的过程里面我们只要了解清楚下面三个问题,也就知道Go是如何让Web运行起来了 |
31 |
| - |
32 |
| -- 如何监听端口? |
33 |
| -- 如何接收客户端请求? |
34 |
| -- 如何分配handler? |
35 |
| - |
36 |
| -前面小节的代码里面我们可以看到,Go是通过一个函数来操作这个事情的`ListenAndServe`来监听起来的,这个底层其实这样处理的:初始化一个server对象,然后调用了`net.Listen("tcp", addr)`,也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。 |
37 |
| - |
38 |
| -监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了`srv.Serve(net.Listener)`函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个`for{}`,首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:`go c.serve()`。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。 |
39 |
| - |
40 |
| -那么如何具体分配到相应的函数来处理请求呢?conn首先会解析request:`c.readRequest()`,然后获取相应的handler:`handler := c.server.Handler`,也就是我们刚才在调用函数`ListenAndServe`时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取`handler = DefaultServeMux`,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了`http.HandleFunc("/", sayhelloName)`嘛。这个作用就是注册了请求`/`的路由规则,当请求uri为"/",路由就会转到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。 |
41 |
| - |
42 |
| -详细的整个流程如下图所示: |
43 |
| - |
44 |
| - |
45 |
| - |
46 |
| -图3.10 一个http连接处理流程 |
47 |
| - |
48 |
| -至此我们的三个问题已经全部得到了解答,你现在对于Go如何让Web跑起来的是否已经基本了解呢? |
49 |
| - |
50 |
| - |
51 |
| -## links |
52 |
| - * [目录](<preface.md>) |
53 |
| - * 上一节: [GO搭建一个简单的web服务](<03.2.md>) |
54 |
| - * 下一节: [Go的http包详解](<03.4.md>) |
| 1 | +# 3.3 Go如何使得Web工作 |
| 2 | +前面小节介绍了如何通过Go搭建一个Web服务,我们可以看到简单应用一个net/http包就方便的搭建起来了。那么Go在底层到底是怎么做的呢?万变不离其宗,Go的Web服务工作也离不开我们第一小节介绍的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 | + |
| 21 | + |
| 22 | +图3.9 http包执行流程 |
| 23 | + |
| 24 | +1. 创建Listen Socket, 监听指定的端口, 等待客户端请求到来。 |
| 25 | + |
| 26 | +2. Listen Socket接受客户端的请求, 得到Client Socket, 接下来通过Client Socket与客户端通信。 |
| 27 | + |
| 28 | +3. 处理客户端的请求, 首先从Client Socket读取HTTP请求的协议头, 如果是POST方法, 还可能要读取客户端提交的数据, 然后交给相应的handler处理请求, handler处理完毕准备好客户端需要的数据, 通过Client Socket写给客户端。 |
| 29 | + |
| 30 | +这整个的过程里面我们只要了解清楚下面三个问题,也就知道Go是如何让Web运行起来了 |
| 31 | + |
| 32 | +- 如何监听端口? |
| 33 | +- 如何接收客户端请求? |
| 34 | +- 如何分配handler? |
| 35 | + |
| 36 | +前面小节的代码里面我们可以看到,Go是通过一个函数`ListenAndServe`来处理这些事情的,这个底层其实这样处理的:初始化一个server对象,然后调用了`net.Listen("tcp", addr)`,也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。 |
| 37 | + |
| 38 | +下面代码来自Go的http包的源码,通过下面的代码我们可以看到整个的http处理过程: |
| 39 | + |
| 40 | + func (srv *Server) Serve(l net.Listener) error { |
| 41 | + defer l.Close() |
| 42 | + var tempDelay time.Duration // how long to sleep on accept failure |
| 43 | + for { |
| 44 | + rw, e := l.Accept() |
| 45 | + if e != nil { |
| 46 | + if ne, ok := e.(net.Error); ok && ne.Temporary() { |
| 47 | + if tempDelay == 0 { |
| 48 | + tempDelay = 5 * time.Millisecond |
| 49 | + } else { |
| 50 | + tempDelay *= 2 |
| 51 | + } |
| 52 | + if max := 1 * time.Second; tempDelay > max { |
| 53 | + tempDelay = max |
| 54 | + } |
| 55 | + log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay) |
| 56 | + time.Sleep(tempDelay) |
| 57 | + continue |
| 58 | + } |
| 59 | + return e |
| 60 | + } |
| 61 | + tempDelay = 0 |
| 62 | + if srv.ReadTimeout != 0 { |
| 63 | + rw.SetReadDeadline(time.Now().Add(srv.ReadTimeout)) |
| 64 | + } |
| 65 | + if srv.WriteTimeout != 0 { |
| 66 | + rw.SetWriteDeadline(time.Now().Add(srv.WriteTimeout)) |
| 67 | + } |
| 68 | + c, err := srv.newConn(rw) |
| 69 | + if err != nil { |
| 70 | + continue |
| 71 | + } |
| 72 | + go c.serve() |
| 73 | + } |
| 74 | + panic("not reached") |
| 75 | + } |
| 76 | + |
| 77 | +监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了`srv.Serve(net.Listener)`函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个`for{}`,首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:`go c.serve()`。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。 |
| 78 | + |
| 79 | +那么如何具体分配到相应的函数来处理请求呢?conn首先会解析request:`c.readRequest()`,然后获取相应的handler:`handler := c.server.Handler`,也就是我们刚才在调用函数`ListenAndServe`时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取`handler = DefaultServeMux`,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了`http.HandleFunc("/", sayhelloName)`嘛。这个作用就是注册了请求`/`的路由规则,当请求uri为"/",路由就会转到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。 |
| 80 | + |
| 81 | + |
| 82 | +详细的整个流程如下图所示: |
| 83 | + |
| 84 | + |
| 85 | + |
| 86 | +图3.10 一个http连接处理流程 |
| 87 | + |
| 88 | +至此我们的三个问题已经全部得到了解答,你现在对于Go如何让Web跑起来的是否已经基本了解呢? |
| 89 | + |
| 90 | + |
| 91 | +## links |
| 92 | + * [目录](<preface.md>) |
| 93 | + * 上一节: [GO搭建一个简单的web服务](<03.2.md>) |
| 94 | + * 下一节: [Go的http包详解](<03.4.md>) |
0 commit comments