zoukankan      html  css  js  c++  java
  • go web的基本原理

    go web的基本原理

    摘抄自参考书《goweb编程》

    golang的一个很大的应用就是服务端的开发,根据net/http库可以快速的搭建一个web服务器。

    goweb的简单实现

    代码:

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/http"
    	"strings"
    )
    
    func sayhelloName(w http.ResponseWriter, r *http.Request) {
    	r.ParseForm()       //解析参数,默认是不会解析的
    	fmt.Println("----------------------")
    	fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息  输出:map[]
    	fmt.Println("----------------------")
    	fmt.Println("path", r.URL.Path)//path /
    	fmt.Println("----------------------")
    	fmt.Println("scheme", r.URL.Scheme)//scheme
    	fmt.Println("----------------------")
    	fmt.Println(r.Form["url_long"])//[]
    	fmt.Println("----------------------")
    	for k, v := range r.Form {
    		fmt.Println("key:", k)
    		fmt.Println("val:", strings.Join(v, ""))
    	}
    	fmt.Fprintf(w, "Hello 周正!") //这个写入到 w 的是输出到客户端的
    }
    func main() {
    	http.HandleFunc("/", sayhelloName)       //设置访问的路由
    	err := http.ListenAndServe(":9090", nil) //设置监听的端口
    	if err != nil {
    		log.Fatal("ListenAndServe: ", err)
    	}
    }
    
    

    调试:

    在浏览器输入127.0.0.1:9090得到如下界面:

    mark

    mark

    我们看到上面的代码,要编写一个 web 服务器很简单,只要调用 http 包的两个函数就可以了。

    如果你以前是 PHP 程序员,那你也许就会问,我们的 nginx、apache 服务器不需要吗?Go就是不需要这些,因为他直接就监听 tcp 端口了做了 nginx 做的事情,然后sayhelloName 这个其实就是我们写的逻辑函数了,跟 php 里面的控制层(controller)函数类似。

    Go 如何使得 Web 工作

    web 工作方式的几个概念

    以下均是服务器端的几个概念

    Request:用户请求的信息,用来解析用户的请求信息,包括 post、get、cookie、url 等信息

    Response:服务器需要反馈给客户端的信息

    Conn:用户的每次请求链接

    Handler:处理请求和生成返回信息的处理逻辑

    http 包运行机制

    mark

    http 包执行流程 :

    1. 创建 Listen Socket, 监听指定的端口, 等待客户端请求到来。
    2. Listen Socket 接受客户端的请求, 得到 Client Socket, 接下来通过 Client Socket 与 客户端通信。
    3. 处理客户端的请求, 首先从 Client Socket 读取 HTTP 请求的协议头, 如果是 POST 方法, 还可能要读取客户端提交的数据, 然后交给相应的 handler 处理请求, handler 处理完 毕准备好客户端需要的数据, 通过 Client Socket 写给客户端。

    这整个的过程里面我们只要了解清楚下面三个问题,也就知道 Go 是如何让 Web 运行起来 了

    1. 如何监听端口?
    2. 如何接收客户端请求?
    3. 如何分配 handler?

    直接看源码:

    mark

    mark

    mark

    经过上面的层层套娃,我们终于到达了这个核心的函数:

    func (srv *Server) Serve(l net.Listener) error {
    	if fn := testHookServerServe; fn != nil {
    		fn(srv, l) // call hook with unwrapped listener
    	}
    
    	origListener := l
    	l = &onceCloseListener{Listener: l}
    	defer l.Close()
    
    	if err := srv.setupHTTP2_Serve(); err != nil {
    		return err
    	}
    
    	if !srv.trackListener(&l, true) {
    		return ErrServerClosed
    	}
    	defer srv.trackListener(&l, false)
    
    	var tempDelay time.Duration // how long to sleep on accept failure
    
    	baseCtx := context.Background()
    	if srv.BaseContext != nil {
    		baseCtx = srv.BaseContext(origListener)
    		if baseCtx == nil {
    			panic("BaseContext returned a nil context")
    		}
    	}
    
    	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    	for {
    		rw, e := l.Accept()
    		if e != nil {
    			select {
    			case <-srv.getDoneChan():
    				return ErrServerClosed
    			default:
    			}
    			if ne, ok := e.(net.Error); ok && ne.Temporary() {
    				if tempDelay == 0 {
    					tempDelay = 5 * time.Millisecond
    				} else {
    					tempDelay *= 2
    				}
    				if max := 1 * time.Second; tempDelay > max {
    					tempDelay = max
    				}
    				srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
    				time.Sleep(tempDelay)
    				continue
    			}
    			return e
    		}
    		connCtx := ctx
    		if cc := srv.ConnContext; cc != nil {
    			connCtx = cc(connCtx, rw)
    			if connCtx == nil {
    				panic("ConnContext returned nil")
    			}
    		}
    		tempDelay = 0
    		c := srv.newConn(rw)
    		c.setState(c.rwc, StateNew) // before Serve can return
    		go c.serve(connCtx)
    	}
    }
    

    好,下面回答刚才的三个问题:

    1. 如何监听端口?

    Go 是通过一个函数 ListenAndServe 来处理这些事情 的,这个底层其实这样处理的:初始化一个 server 对象,然后调用了 net.Listen("tcp", addr),也就是底层用 TCP 协议搭建了一个服务,然后监控我们设置的端口

    1. 如何接收客户端请求?

      上面代码执行监控端口之后,调用了srv.Serve(net.Listener)函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个 for{},首先通过 Listener 接收请求,其次创建一个 Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个 conn 去服务:go c.serve()

      这里的新创建的Coon是基于tcp的连接创建的,相当于封装了一下。

    2. 如何分配 handler?

      conn 首先会解析 request:c.readRequest(), 然后获取相应的 handler:handler := c.server.Handler,也就是我们刚才在调用函数ListenAndServe 时候的第二个参数,我们前面例子传递的是 nil,也就是为空,那么默认获取 handler = DefaultServeMux,那么这个变量用来做什么的呢?

      对,这个变量就是一个路由 器,它用来匹配 url 跳转到其相应的 handle 函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了 http.HandleFunc("/", sayhelloName)嘛。这个作用就是注册了请求/的路由规则,当请求 uri 为"/",路由就会转到函数 sayhelloName,DefaultServeMux

      会调用 ServeHTTP 方法,这个方法内部其实就是调用 sayhelloName 本身,最后通过写入response 的信息反馈到客户端。

      mark

    通过对 http 包的分析之后,现在让我们来梳理一下整个的代码执行过程。

    Go 代码的执行流程

    首先调用 Http.HandleFunc:

    按顺序做了几件事:

    1. 调用了 DefaultServerMux 的 HandleFunc
    2. 调用了 DefaultServerMux 的 Handle3 往 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

  • 相关阅读:
    noi 2011 noi嘉年华 动态规划
    最小乘积生成树
    noi 2009 二叉查找树 动态规划
    noi 2010 超级钢琴 划分树
    noi 2011 阿狸的打字机 AC自动机
    noi 2009 变换序列 贪心
    poj 3659 Cell Phone Network 动态规划
    noi 2010 航空管制 贪心
    IDEA14下配置SVN
    在SpringMVC框架下建立Web项目时web.xml到底该写些什么呢?
  • 原文地址:https://www.cnblogs.com/wind-zhou/p/12961974.html
Copyright © 2011-2022 走看看