Go Web 入门与实战系列:接收和处理请求(一)

2,190 阅读3分钟

这是我参与8月更文挑战的第3天,活动详情查看: 8月更文挑战

Web 应用程序是一种可以通过 Web 访问的应用程序,Web 程序的最大好处是用户很容易访问应用程序,用户只需要有浏览器即可,不需要再安装其他软件。Web 应用对于身处互联网时代的我们来说太普遍。无论哪一种语言,只要它能够开发出与人类交互的软件,它就必然会支持 Web 应用开发。 本系列文章将会介绍 Go Web 的应用与实践。欢迎关注。

上一篇文章介绍了具体构建 Go Web 服务器。通过前面文章实例的实现,我们看到编写一个 Web 服务器其实很简单,只要调用 Http 包的两个函数就可以了。本文开始将会深入实现原理,具体讲解 Go 是如何接收和处理请求。

Web 工作的几个概念

Go 的 net/http 标准库分为客户端和服务端两个部分,主要包括如下的结构和函数:

  • 与服务端相关的有:Server、ServerMux、Handler/HandlerFunc、Header、Request 和 Cookie;
  • 与客户端相关的有:Client、Response、Header、Request 和 Cookie。

其中,Header、Request 和 Cookie 是客户端和服务端共同涉及的组成部分。我们重点关注服务端的功能。

服务器端的几个概念:

  • Request:用户请求,用来解析用户的请求信息,包括 post、get、cookie、url 等信息;
  • Response:服务端需要反馈给客户端的信息;
  • Conn:用户的每次请求链接;
  • Handler:处理请求和生成返回信息的处理逻辑

image.png

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

处理器处理请求

在了解了 Go http 包执行的流程,我们将会具体分析处理器处理请求的过程。

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

下面代码来自 Go 的 http 包的源码,通过下面的代码我们可以看到整个的 http 处理过程:

func (srv *Server) Serve(l net.Listener) error {
	l = &onceCloseListener{Listener: l}
	defer l.Close()

	//...
	defer srv.trackListener(&l, false)

	var tempDelay time.Duration     // how long to sleep on accept failure
	baseCtx := context.Background() // base is always background, per Issue 16220
	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
		}
		tempDelay = 0
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve(ctx)
	}
}

监听之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了 srv.Serve(net.Listener) 函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个 for{} 循环,首先通过 Listener 接收请求,其次创建一个 Conn,最后单独开了一个 goroutine,把这个请求的数据当做参数给conn去服务:go c.serve()。这就是高并发体现了,用户的每一次请求都是在一个新的 goroutine 去服务,相互不影响。

小结

本文主要介绍 Web 工作的几个概念以及处理请求的部分原理。下面的文章将会继续分析收发请求的其他过程。

阅读最新文章,关注公众号:aoho求索