From 6e11478f1abd1063a0e3ae97583d40c84bab8a4e Mon Sep 17 00:00:00 2001 From: astaxie Date: Wed, 5 Sep 2012 16:11:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=AC=AC=E4=B8=89=E7=AB=A0?= =?UTF-8?q?=E7=9A=84=E4=B9=A6=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3.2.md | 14 +++-- 3.3.md | 134 ++++++++++++++++---------------------------- 3.4.md | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3.5.md | 12 ++++ 3.md | 2 +- preface.md | 2 +- 6 files changed, 232 insertions(+), 92 deletions(-) create mode 100644 3.4.md create mode 100644 3.5.md diff --git a/3.2.md b/3.2.md index 492b1dda..2f830acd 100644 --- a/3.2.md +++ b/3.2.md @@ -1,6 +1,6 @@ #3.2 GO搭建一个web服务器 -前面小节已经介绍了Web是基于http协议的一个服务,Go语言里面提供了一个完善的net/http包,通过http包可以很方便的就搭建起来一个可以运行的web服务。使用这个包能很简单地对web的路由,静态文件,模版,cookie等数据进行设置和操作。 +前面小节已经介绍了Web是基于http协议的一个服务,Go语言里面提供了一个完善的net/http包,通过http包可以很方便的就搭建起来一个可以运行的web服务。同时使用这个包能很简单地对web的路由,静态文件,模版,cookie等数据进行设置和操作。 ##http包建立web服务器 @@ -10,10 +10,11 @@ "fmt" "net/http" "strings" + "log" ) func sayhelloName(w http.ResponseWriter, r *http.Request) { - r.ParseForm() + r.ParseForm() //解析参数,默认是不会解析的 fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息 fmt.Println("path", r.URL.Path) fmt.Println("scheme", r.URL.Scheme) @@ -26,8 +27,11 @@ } func main() { - http.HandleFunc("/", sayhelloName) - http.ListenAndServe(":9090", nil) + http.HandleFunc("/", sayhelloName) //设置访问的路由 + err := http.ListenAndServe(":9090", nil) //设置监听的端口 + if err != nil { + log.Fatal("ListenAndServe: ", err) + } } 上面这个代码,我们build之后,然后执行web.exe,这个时候其实已经在9090端口监听tcp链接请求了。 @@ -52,7 +56,7 @@ >- 如果你以前是ruby程序员,那么和ROR的/script/server启动有点类似。 -我们看到我们通过简单的几行代码就已经运行起来一个web服务了,而且这个web服务内部已经支持了高并发的特性,我将会在接下来的两个小节里面详细的讲解一下go是如何实现web高并发的。 +我们看到Go通过简单的几行代码就已经运行起来一个web服务了,而且这个Web服务内部已经支持了高并发的特性,我将会在接下来的两个小节里面详细的讲解一下go是如何实现Web高并发的。 diff --git a/3.3.md b/3.3.md index ebdec6ce..de512ff2 100644 --- a/3.3.md +++ b/3.3.md @@ -1,85 +1,49 @@ -#3.3 Go如何使得web工作 -前面小节介绍了如何通过Go搭建一个web服务,我们可以看到简单的应用了一个net/http包就方便的搭建起来了。那么他底层到底是怎么做的呢?但是万变不离其宗,他离不开我们第一小节介绍的web工作方式。 - -##对应web工作方式的几个概念 - -Request:用户请求的信息 - -Response:服务器需要反馈给客户端的信息 - -Conn:用户的每次请求链接 - -Handler:处理请求和生成返回信息的处理逻辑 - -##分析http包运行机制 - -![](images/3.3.http.png?raw=true) - -(1) 创建listen socket, 在指定的监听端口, 等待客户端请求的到来 - -(2) listen socket接受客户端的请求, 得到client socket, 接下来通过client socket与客户端通信 - -(3) 处理客户端的请求, 首先从client socket读取http请求的协议头, 如果是post协议, 还可能要 - 读取客户端上传的数据, 然后处理请求, 准备好客户端需要的数据, 通过client socket写给客户端 - - - -##web执行的过程分析 - -针对前一小节里面的代码,我们来一行行的分析一下,大概的http包里面执行流程应该是这样: - -- 首先调用Http.HandleFunc - - 按顺序做了几件事: - - 1 调用了DefaultServerMux的HandleFunc - - 2 调用了DefaultServerMux的Handle - - 3 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则 - -- 其次调用http.ListenAndServe(":12345", nil) - - 按顺序做了几件事情: - - 1 实例化Server - - 2 调用Server的ListenAndServe() - - 3 调用net.Listen("tcp", addr)监听端口 - - 4 启动一个for循环,在循环体中Accept请求 - - 5 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve() - - 6 读取每个请求的内容w, err := c.readRequest() - - 7 判断handler是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux - - 8 调用handler的ServeHttp - - 9 在这个例子中,下面就进入到DefaultServerMux.ServeHttp - - 10 根据request选择handler,并且进入到这个handler的ServeHTTP - - mux.handler(r).ServeHTTP(w, r) - - 11 选择handler: - - A 判断是否有路由能满足这个request(循环遍历ServerMux的muxEntry) - - B 如果有路由满足,调用这个路由handler的ServeHttp - - C 如果没有路由满足,调用NotFoundHandler的ServeHttp - - - - - -## links - * [目录]() - * 上一节: [GO搭建一个简单的web服务](<3.2.md>) - * 下一节: [Go的http包执行原理](<3.4.md>) - -## LastModified - * $Id$ +#3.3 Go如何使得Web工作 +前面小节介绍了如何通过Go搭建一个Web服务,我们可以看到简单的应用了一个net/http包就方便的搭建起来了。那么他底层到底是怎么做的呢?但是万变不离其宗,他离不开我们第一小节介绍的Web工作方式。 + +##对应web工作方式的几个概念 + +以下均是服务器端的相应概念 + +Request:用户请求的信息,用来解析用户的请求信息,包括post、get、cookie、url等信息 + +Response:服务器需要反馈给客户端的信息 + +Conn:用户的每次请求链接 + +Handler:处理请求和生成返回信息的处理逻辑 + +##分析http包运行机制 + +如下图所示,是Go实现Web工作模式的流程图 + +![](images/3.3.http.png?raw=true) + +(1) 创建listen socket, 在指定的端口监听, 等待客户端请求的到来 + +(2) listen socket接受客户端的请求, 得到client socket, 接下来通过client socket与客户端通信 + +(3) 处理客户端的请求, 首先从client socket读取http请求的协议头, 如果是post协议, 还可能要读取客户端上传的数据, 然后跟给相应的handler处理请求, 准备好客户端需要的数据, 通过client socket写给客户端 + +这整个的过程里面我们只要了解清楚下面三个问题,也就知道Go是如何让Web运行起来了 + +- 如何监听端口? +- 如何接收客户端请求? +- 如何分配handler? + +前面小节的代码里面我们可以看到,Go是通过一个函数来操作这个事情的`ListenAndServe`来监听起来的,这个底层其实这样处理的:初始化一个server对象,然后调用了`net.Listen("tcp", addr)`,也就是底层起的是TCP协议,然后监控了我们设置的端口。 + +监控之后如何接收客户端的请求呢?上面的监控端口之后,就调用了`srv.Serve(net.Listener)`函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个`for{}`,里面通过Listener接收请求,然后起一个Conn,然后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:`go c.serve()`。这个就是高并发体现了,用户来的请求都是goroutine去服务,相互不影响。 + +那么如何具体分配到相应的函数来处理请求呢?conn首先会解析request:`c.readRequest()`,然后获取相应的handler:`handler := c.server.Handler`,也就是我们刚才在调用函数`ListenAndServe`时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取`handler = DefaultServeMux`,那么这个是哪里来的东西呢?对,这个东西就是路由器,也就是把相应的请求url对应请求函数的路由器,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了`http.HandleFunc("/", sayhelloName)`嘛。这个就是注册了相应的路由,url为"/"的请求到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。 + +至此我们的三个问题已经全部得到了解答,你现在对于Go如何让Web跑起来的是否已经大概清楚了呢? + + +## links + * [目录]() + * 上一节: [GO搭建一个简单的web服务](<3.2.md>) + * 下一节: [Go的http包详解](<3.4.md>) + +## LastModified + * $Id$ diff --git a/3.4.md b/3.4.md new file mode 100644 index 00000000..716df063 --- /dev/null +++ b/3.4.md @@ -0,0 +1,160 @@ +#3.4 Go的http包详解 +前面小节介绍了Go怎么样实现了Web工作模式的一个流程,这一小节,我们讲来详细的解剖一下http包,他到底怎么样实现整个的过程的。 + +Go的http有两个核心功能:Conn、ServeMux + +##Conn的goroutine +与我们一般编写的http服务器不同, Go为了实现高并发和高性能, 使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证。 + +Go在等待客户端请求里面是这样写的: + + c, err := srv.newConn(rw) + if err != nil { + continue + } + go c.serve() + +这里我们可以看到客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到handler的时候可以读取到相应的header信息,这样保证了每个请求的独立性。 + +##ServeMux的自定义 +我们前面小节讲述conn.server的时候,其实内部是调用了http包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢? + +它的结构如下: + + type ServeMux struct { + mu sync.RWMutex //锁,由于请求设计到并发处理,因此这里需要一个锁机制 + m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式 + } + +下面看一下muxEntry + + type muxEntry struct { + explicit bool // 是否精确匹配 + h Handler // 这个路由表达式对应哪个handler + } + +下面看一下handler的定义 + + type Handler interface { + ServeHTTP(ResponseWriter, *Request) // 路由实现器 + } + +handler是一个接口,但是我们定义的函数`sayhelloName`没有实现ServeHTTP这个接口,为什么能添加呢?原来在http包里面还定义了一个类型`HandlerFunc`,我们定义的函数`sayhelloName`就是这个handlerFunc调用之后的结果,这个类型默认就实现了ServeHTTP这个接口,即我们调用了HandlerFunc(f),类似强制类型转换f成为handlerFunc类型,这样f就拥有了ServHTTP方法。 + + type HandlerFunc func(ResponseWriter, *Request) + + // ServeHTTP calls f(w, r). + func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { + f(w, r) + } + +路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢? + +路由器接收到请求之后调用`mux.handler(r).ServeHTTP(w, r)` + +也就是调用对应路由的handler的ServerHTTP接口,那么mux.handler(r)怎么处理的呢? + + func (mux *ServeMux) handler(r *Request) Handler { + mux.mu.RLock() + defer mux.mu.RUnlock() + + // Host-specific pattern takes precedence over generic ones + h := mux.match(r.Host + r.URL.Path) + if h == nil { + h = mux.match(r.URL.Path) + } + if h == nil { + h = NotFoundHandler() + } + return h + } + +原来他是根据用户请求的URL和路由器里面存储的map去匹配的,当匹配到之后返回存储的handler,然后handler实现了ServHTTP接口,调用这个ServHTTP就可以执行到相应的函数了。 + +通过上面这个介绍,我们了解了路由器的整个执行过程,Go其实支持外部实现的路由器,因为我们在调用`ListenAndServe`的时候,第二个参数就是Handler,只要实现了ServHTTP函数就可以。 + +如下代码所示,我们自己实现了一个简易的路由器 + + package main + + import ( + "fmt" + "net/http" + ) + + type MyMux struct { + } + + func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + sayhelloName(w, r) + return + } + http.NotFound(w, r) + return + } + + func sayhelloName(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello myroute!") + } + + func main() { + mux := &MyMux{} + http.ListenAndServe(":9090", mux) + } + +##Go代码的执行流程 + +通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程。 + +- 首先调用Http.HandleFunc + + 按顺序做了几件事: + + 1 调用了DefaultServerMux的HandleFunc + + 2 调用了DefaultServerMux的Handle + + 3 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则 + +- 其次调用http.ListenAndServe(":9090", nil) + + 按顺序做了几件事情: + + 1 实例化Server + + 2 调用Server的ListenAndServe() + + 3 调用net.Listen("tcp", addr)监听端口 + + 4 启动一个for循环,在循环体中Accept请求 + + 5 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve() + + 6 读取每个请求的内容w, err := c.readRequest() + + 7 判断handler是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux + + 8 调用handler的ServeHttp + + 9 在这个例子中,下面就进入到DefaultServerMux.ServeHttp + + 10 根据request选择handler,并且进入到这个handler的ServeHTTP + + mux.handler(r).ServeHTTP(w, r) + + 11 选择handler: + + A 判断是否有路由能满足这个request(循环遍历ServerMux的muxEntry) + + B 如果有路由满足,调用这个路由handler的ServeHttp + + C 如果没有路由满足,调用NotFoundHandler的ServeHttp + +## links + * [目录]() + * 上一节: [Go如何使得web工作](<3.3.md>) + * 下一节: [小节](<3.5.md>) + +## LastModified + * $Id$ \ No newline at end of file diff --git a/3.5.md b/3.5.md new file mode 100644 index 00000000..a7109024 --- /dev/null +++ b/3.5.md @@ -0,0 +1,12 @@ +#3.5小节 +这一章我们通过介绍Web工作方式介绍了HTTP协议的相关信息,通过类比引出了Go怎么运行Web,实现了Go的web服务器,通过对Go运行Web的代码进行分析,深入了解了Go执行Web的整个流程,最后我们对Go语言的http包进行了详细的分析。 + +通过这一章的学习我希望你能够对Go开发Web有了初步的了解,我们也看到相应的代码了,这个开发是很方便的,同时又是相当的灵活。 + +## links + * [目录]() + * 上一节: [Go的http包详解](<3.4.md>) + * 下一章: [小节](<4.md>) + +## LastModified + * $Id$ \ No newline at end of file diff --git a/3.md b/3.md index e1aca644..e519512d 100644 --- a/3.md +++ b/3.md @@ -4,7 +4,7 @@ * 1. [web工作方式](3.1.md) * 2. [GO搭建一个简单的web服务](3.2.md) * 3. [Go如何使得web工作](3.3.md) - * 4. [Go的http包执行原理](3.4.md) + * 4. [Go的http包详解](3.4.md) * 5. [小结](3.5.md) 学习基于Web的编程可能正是你读本书的原因。事实上,如何通过Go来编写Web应用也是我编写这本书的初衷。前面已经介绍过,Go目前已经拥有了成熟的Http处理包,这使得编写能做任何事情的动态Web程序易如反掌。在接下来的各章中将要介绍的内容,都是属于Web编程的范畴。本章则集中讨论一些与Web相关的概念和Go如何运行Web程序的话题。 diff --git a/preface.md b/preface.md index 37f723cb..7e8ec007 100644 --- a/preface.md +++ b/preface.md @@ -17,7 +17,7 @@ - 3.1 [web工作方式](3.1.md) - 3.2 [GO搭建一个简单的web服务](3.2.md) - 3.3 [Go如何使得web工作](3.3.md) - - 3.4 [Go的http包执行原理](3.4.md) + - 3.4 [Go的http包详解](3.4.md) - 3.5 [小结](3.5.md) * 4.表单 - 4.1 处理表单的输入