Go 语言实现的 Web 服务工作方式与其他形式下的 Web 工作方式并没有什么不同,具体流程如下:

—— http包执行流程

Request:来自用户的请求信息,包括 post、get、Cookie、url 等。

Response:服务器返回给客户端的信息。

Connect:用户的每次的请求连接

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

根据上图,Go 语言中的 http 包具体做了这么三个操作:

  1. 创建 Listen Socket,监听指定端口,等待客户端请求。
  2. Listen Socket 接受客户端请求,得到 Client Socket,接下来通过 Client Socket与客户端通信
  3. 处理客户端请求。首先从 Client Socket 获取 HTTP 请求数据,然后交给相应的 handler 处理请求,handler 处理完毕后再通过 Client Socket 返回给客户端。

接着我们从代码的角度来看一下这三个操作是如何实现的:

一,监听端口

在 Go 语言中只需要通过调用 ListenAndServe 方法即可设置监听端口:

func main() {
http.HandleFunc("/", sayhelloName) //设置访问的路由 err := http.ListenAndServe(":9090", nil) //设置监听的端口
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
} func sayhelloName(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello Wrold!") //这个写入到w的是输出到客户端的
}

运行结果如下:

我们接着看一下 ListenAndServe 方法的具体实现,看一下它是如何实现监听端口的:

// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}

通过查看该方法的源码,可以发现该方法初始化了一个 server 对象,并调用了该 server 对象的 ListenAndServe() 方法:

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// If srv.Addr is blank, ":http" is used.
//
// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr) //底层使用tcp协议搭建了一个服务,然后监听所设置的端口
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

在这个方法中调用了 net.Listen("tcp", addr) 来监听端口

二,接收客户端请求

在完成了对端口的监听之后,再通过调用 srv.Serve(net.Listener) 方法来处理接收客户端的请求消息。

// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
//
// HTTP/2 support is only enabled if the Listener returns *tls.Conn
// connections and they were configured with "h2" in the TLS
// Config.NextProtos.
//
// Serve always returns a non-nil error and closes l.
// After Shutdown or Close, the returned error is ErrServerClosed.
func (srv *Server) Serve(l net.Listener) error {
... 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
} ...
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}

在这个方法中,启动了一个 for 循环使 Listener 不断地接收来自客户端的请求,并且对每一个请求都实例化一个 Conn,并开启一个 goroutine 来为这个请求进行服务 go c.serve()。用户的每一次请求都是在一个新的 goroutine中服务,互相不影响。

三,为不同的请求分配处理逻辑

对于来自客户端的不同请求,服务器端需要根据情况来分配相对应的函数进行处理。

在之前的 main 方法中,我们调用了 http.ListenAndServe(":9090", nil) 来监听端口,而第二个参数传入的是 nil,那么这个参数是干嘛用的呢?

func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
} type Server struct {
Addr string // 监听的地址和端口 默认为":http"
Handler Handler // 路由管理,如果为nil,则默认为http.DefaultServeMux
ReadTimeout time.Duration //读的最大超时时间
WriteTimeout time.Duration //写的最大超时时间
MaxHeaderBytes int //请求头的最大长度
TLSConfig *tls.Config // 配置TLS
...
}

从源码中的注释可以看出,当初始化 Server 时,如果不指定参数 Handler,则默认获取 Handler = http.DefaultServeMux,而 DefaultServeMux 又是一个默认创建的 ServeMux 类型的数据。

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

从源码上看到,DefaultServeMux 又是一个默认创建的 ServeMux。那么这个 ServeMux 又是干嘛的呢?

ServeMux

ServeMux 是 Go 语言中默认的路由规则管理器,它维护了一个存放了路由信息的 map 表,key 是请求路径,而 value 则是该路径所对应的处理程序(Handler)。当有请求到来的时候,根据这个 map 路由表来判断将请求分发个哪个 Handler。

type ServeMux struct {
mu sync.RWMutex //锁机制,用于处理并发
m map[string]muxEntry //路由表
hosts bool // 是否为主机模式
} type muxEntry struct {
h Handler
pattern string
}

通过查看这两个结构体的源码,就能更容易的理解路由是如何处理请求的了:

当接收到一个请求时,server 就会根据 ServeMux.m 中保存的请求路径(string)来匹配相对应的 muxEntry,接着就可以调用 muxEntry 中 h Handler 的ServeHttp 方法来处理请求了。

type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

回到最开始的例子 main 方法中,我们为根路径 "/" 注册了一个 sayhelloName 函数,但是 sayhelloName 函数并没有实现 ServeHTTP 接口,那么为什么它能起到作用呢?这因为在 http 包内还定义了一个 HandleFunc 类型,这个类型默认就实现了 ServeHTTP 这个接口。

// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

而我们注册的自定义 sayhelloName 函数,经过 http.HandleFunc("/", sayhelloName) ,最后都会被转换成 HandlerFunc 类型。

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
...
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler)) //转换自定义的func为HandlerFunc类型
}

在上面这段代码可以看到,因为调用了 HandlerFunc(handler) 方法进行了强制类型转换,我们自定义的函数被都转换为了 HandlerFunc 类型。而 HandlerFunc 又实现了 Handler 接口的 ServeHTTP 方法,所以 HandlerFunc 是 Handler 的具体实现类型,所以 mux 也就拥有了 ServeHTTP 方法了。

到此,路由器就注册好了相应的路由规则了。我们接着来分析一下路由是如何分发处理逻辑的。

路由分发

路由器接收到请求之后就会调用 mux.handler(r).ServeHTTP(w, r),也就是调用相应路由的 handler 的 ServeHTTP 接口。

// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}

原来它是根据用户请求的 URL 和路由器中的存储的 map 去匹配的,当匹配到路径之后,就会返回存储的 handler,接着就可以调用这个 handler 的 ServeHTTP 执行相应的函数了。

路由匹配规则

// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
} // Check for longest valid match.
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}
// Does path match pattern?
func pathMatch(pattern, path string) bool {
if len(pattern) == 0 {
// should not happen
return false
}
n := len(pattern)
if pattern[n-1] != '/' {
return pattern == path
}
return len(path) >= n && path[0:n] == pattern
}

总结流程图:

—— http处理连接流程

Go http包执行流程的更多相关文章

  1. python—day15 包的认识、执行顺序、执行流程、循环导入、包的导入、绝对、相对导入

    一.包的认识   包通过文件夹来管理一系列功能相近的模块 ​ 包:一系列模块的集合体 重点:包中一定有一个专门用来管理包中所有模块的文件 包名:存放一系列模块的文件夹名字 包名(包对象)存放的是管理模 ...

  2. 轻量级前端MVVM框架avalon - 执行流程2

    接上一章 执行流程1 在这一大堆扫描绑定方法中应该会哪些实现? 首先我们看avalon能帮你做什么? 数据填充,比如表单的一些初始值,切换卡的各个面板的内容({{xxx}},{{xxx|html}}, ...

  3. 分享一张SQLSERVER执行流程的图片

    分享一张SQLSERVER执行流程的图片 有天论坛里有人问,一时间并发连接很多,是不是可以在SSMS里配置连接池 连接池是属于客户端的,配置只能在连接字符串里配置,修改你的连接字符串,SSMS没有一个 ...

  4. (一)熟悉执行流程——基于ThinkPHP3.2的内容管理框架OneThink学习

    ThinkPHP作为国内具有代表性的PHP框架,经过多年的发展,受到越来越多公司与开发者的青睐.我也在忙里偷闲中抽出部分时间,来学习这个优秀的框架.在开始学习这个框架时,最好通过实例来学习,更容易结合 ...

  5. Android系统Recovery工作原理之使用update.zip升级过程---updater-script脚本语法简介以及执行流程(转)

    目前update-script脚本格式是edify,其与amend有何区别,暂不讨论,我们只分析其中主要的语法,以及脚本的流程控制. 一.update-script脚本语法简介: 我们顺着所生成的脚本 ...

  6. ThinkPHP 框架执行流程分析

    总体来说,应用的流程涉及到几个文件:Index.phpThinkPHP.phpThink.class.phpApp.class.phpDispatcher.class.phpThinkPHP/Mode ...

  7. struts2 之 【struts2简介,struts2开发步骤,struts2详细配置,struts2执行流程】

    入门框架学习避免不了的问题: 1. 什么是框架? 简单的说,框架就是模板,模子,模型.就是一个可重用的半成品. 2. 如何学习框架? 学习框架其实就是学习规则,使用框架就是遵循框架的规则,框架是可变的 ...

  8. Struts框架之 执行流程 struts.xml 配置详细

    1.执行流程 服务器启动: 1. 加载项目web.xml 2. 创建Struts核心过滤器对象, 执行filter  →  init()   struts-default.xml,    核心功能的初 ...

  9. Struts2第二篇【开发步骤、执行流程、struts.xml讲解、defalut-struts讲解】

    前言 我们现在学习的是Struts2,其实Struts1和Struts2在技术上是没有很大的关联的.Struts2其实基于Web Work框架的,只不过它的推广没有Struts1好,因此就拿着Stru ...

随机推荐

  1. Abp 聚合测试

    Abp 官网开始的教程例子,是IRpositoty<entity> 直接出现在应用层.但是如果是一个聚合根也会这样吗?  那么聚合根是访问仓储的最小单元,要通过聚合根来操作业务,就是实体, ...

  2. pm2 常用操作

    PM2全局安装 npm i pm2 -g PM2启动.net core pm2 start "dotnet xxx.dll" --name api //name后面跟你要取的名字 ...

  3. ORACLE数据库 自动备份 定时计划任务 windows

    疑问为什么没有输入oracle 的数据库安装目录就能直接备份呢,可能是因为oracle默认安装c盘,在docs命令直接能操作吧,不信可以使用sqlplus试试. 一共分三步: 一.建立一个.bat 批 ...

  4. mac上配置apidoc环境

    1. 安装node.js 和npm 前往 https://nodejs.org/en/ 下载node.js的最新版本,双击.pkg进行安装 在终端输入 node -v ,如正确输出版本号即安装成功 ( ...

  5. 【Struts2】简介及入门

    一.概述 二.Struts2 快速入门程序 2.1 开发流程比较 2.2 引入依赖 2.2 创建jsp页面 2.3 在web.xml中配置前端控制器 2.4 创建struts.xml配置文件 2.4 ...

  6. Codeforces 845G Shortest Path Problem?

    http://codeforces.com/problemset/problem/845/G 从顶点1dfs全图,遇到环则增加一种备选方案,环上的环不需要走到前一个环上作为条件,因为走完第二个环可以从 ...

  7. 使用Google Thumbnails 压缩图片

    背景说明:最近项目中需要用到一些图片文件的上传 ,但是有些图片很大,比如轮播图,大有的有几兆,这样加载一个首页都要很久,显然这样对用户体验是非常不友好的,对服务器资源将是一种浪费. 为了解决这个问题, ...

  8. pip install win32api报错解决方法

    在安装pywinauto模块,导入模块后,提示缺少:win32api 但是在使用pip install安装win32api后,居然报错 错误信息如下: Could not find a version ...

  9. 【异常】ssh无法登录验证,非root用户ssh本机无法成功

    1 自己搭建的是伪分布式环境,需要以非root用户启动Hadoop集群,之前root已经配置了ssh免密登录,但是自己切换到hdfs用户重新生成了一套ssh key, 但是切换到hdfs始终无法成功登 ...

  10. 05_ Flume多级Agent之间串联案例

    多级agent之间串联: 从tail命令获取数据发送到avro端口,另一个节点可配置一个avro源来获取数据,发送外部存储 启动两个Agent节点: 使用scp命令拷贝flume安装包到另一台虚拟机; ...