gin是目前Go里面使用最广泛的框架之一了,弄清楚gin框架的原理,有助于我们更好的使用gin。这个系列gin源码阅读会逐步讲明白 gin 的原理,欢迎关注后续文章。

gin 概览

想弄清楚 gin, 需要弄明白以下几个问题:

  • request数据是如何流转的

  • gin框架到底扮演了什么角色

  • 请求从gin流入net/http, 最后又是如何回到gin中

  • gin的context为何能承担起来复杂的需求

  • gin的路由算法

  • gin的中间件是什么

  • gin的Engine具体是个什么东西

  • net/http的requeset, response都提供了哪些有用的东西

gin的官方第一个demo入手.

package mainimport "github.com/gin-gonic/gin"func main() {r := gin.Default()r.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong",})})r.Run() // listen and serve on 0.0.0.0:8080
}

r.Run() 的源码:

func (engine *Engine) Run(addr ...string) (err error) {defer func() { debugPrintError(err) }()address := resolveAddress(addr)debugPrint("Listening and serving HTTP on %s\n", address)err = http.ListenAndServe(address, engine)return
}

看到开始调用的是 http.ListenAndServe(address, engine), 这个函数是net/http的函数, 然后请求数据就在net/http开始流转.

Request 数据是如何流转的

先不使用gin, 直接使用net/http来处理http请求

func main() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello World"))})if err := http.ListenAndServe(":8000", nil); err != nil {fmt.Println("start http server fail:", err)}
}

在浏览器中输入localhost:8000, 会看到Hello World. 下面利用这个简单demo看下request的流转流程.

HTTP是如何建立起来的

简单的说一下http请求是如何建立起来的(需要有基本的网络基础, 可以找相关的书籍查看, 推荐看UNIX网络编程卷1:套接字联网API)

TCP/IP 五层模型

socket建立过程

TCP/IP五层模型下, HTTP位于应用层, 需要有传输层来承载HTTP协议. 传输层比较常见的协议是TCP,UDP, SCTP等. 由于UDP不可靠, SCTP有自己特殊的运用场景, 所以一般情况下HTTP是由TCP协议承载的(可以使用wireshark抓包然后查看各层协议)

使用TCP协议的话, 就会涉及到TCP是如何建立起来的. 面试中能够常遇到的名词三次握手, 四次挥手就是在这里产生的. 具体的建立流程就不在陈述了, 大概流程就是图中左半边

所以说, 要想能够对客户端http请求进行回应的话, 就首先需要建立起来TCP连接, 也就是socket. 下面要看下net/http是如何建立起来socket?

net/http 是如何建立 socket 的

从图上可以看出, 不管server代码如何封装, 都离不开bind,listen,accept这些函数. 就从上面这个简单的demo入手查看源码.

func main() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello World"))})if err := http.ListenAndServe(":8000", nil); err != nil {fmt.Println("start http server fail:", err)}
}

注册路由

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello World"))
})

这段代码是在注册一个路由及这个路由的handler到DefaultServeMux

// server.go:L2366-2388
func (mux *ServeMux) Handle(pattern string, handler Handler) {mux.mu.Lock()defer mux.mu.Unlock()if pattern == "" {panic("http: invalid pattern")}if handler == nil {panic("http: nil handler")}if _, exist := mux.m[pattern]; exist {panic("http: multiple registrations for " + pattern)}if mux.m == nil {mux.m = make(map[string]muxEntry)}mux.m[pattern] = muxEntry{h: handler, pattern: pattern}if pattern[0] != '/' {mux.hosts = true}
}

可以看到这个路由注册太过简单了, 也就给gin, iris, echo等框架留下了扩展的空间, 后面详细说这个东西

服务监听及响应

上面路由已经注册到net/http了, 下面就该如何建立socket了, 以及最后又如何取到已经注册到的路由, 将正确的响应信息从handler中取出来返回给客户端

1.创建 socket

if err := http.ListenAndServe(":8000", nil); err != nil {fmt.Println("start http server fail:", err)
}
// net/http/server.go:L3002-3005
func ListenAndServe(addr string, handler Handler) error {server := &Server{Addr: addr, Handler: handler}return server.ListenAndServe()
}
// net/http/server.go:L2752-2765
func (srv *Server) ListenAndServe() error {// ... 省略代码ln, err := net.Listen("tcp", addr) // <-----看这里listenif err != nil {return err}return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

2.Accept 等待客户端链接

// net/http/server.go:L2805-2853
func (srv *Server) Serve(l net.Listener) error {// ... 省略代码for {rw, e := l.Accept() // <----- 看这里acceptif e != nil {select {case <-srv.getDoneChan():return ErrServerCloseddefault:}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 = 0c := srv.newConn(rw)c.setState(c.rwc, StateNew) // before Serve can returngo c.serve(ctx) // <--- 看这里}
}

3. 提供回调接口 ServeHTTP

// net/http/server.go:L1739-1878
func (c *conn) serve(ctx context.Context) {// ... 省略代码serverHandler{c.server}.ServeHTTP(w, w.req)w.cancelCtx()if c.hijacked() {return}w.finishRequest()// ... 省略代码
}
// net/http/server.go:L2733-2742
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {handler := sh.srv.Handlerif handler == nil {handler = DefaultServeMux}if req.RequestURI == "*" && req.Method == "OPTIONS" {handler = globalOptionsHandler{}}handler.ServeHTTP(rw, req)
}
// net/http/server.go:L2352-2362
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {if r.RequestURI == "*" {if r.ProtoAtLeast(1, 1) {w.Header().Set("Connection", "close")}w.WriteHeader(StatusBadRequest)return}h, _ := mux.Handler(r) // <--- 看这里h.ServeHTTP(w, r)
}

4. 回调到实际要执行的 ServeHTTP

// net/http/server.go:L1963-1965
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {f(w, r)
}

这基本是整个过程的代码了.

  1. ln, err := net.Listen("tcp", addr)做了初试化了socket, bind, listen的操作.

  2. rw, e := l.Accept()进行accept, 等待客户端进行连接

  3. go c.serve(ctx) 启动新的goroutine来处理本次请求. 同时主goroutine继续等待客户端连接, 进行高并发操作

  4. h, _ := mux.Handler(r) 获取注册的路由, 然后拿到这个路由的handler, 然后将处理结果返回给客户端

从这里也能够看出来, net/http基本上提供了全套的服务.

为什么会出现很多go框架

// net/http/server.go:L2218-2238
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 = 0for k, v := range mux.m {if !pathMatch(k, path) {continue}if h == nil || len(k) > n {n = len(k)h = v.hpattern = v.pattern}}return
}

从这段函数可以看出来, 匹配规则过于简单, 当能匹配到路由的时候就返回其对应的handler, 当不能匹配到时就返回/. net/http的路由匹配根本就不符合 RESTful 的规则,遇到稍微复杂一点的需求时,这个简单的路由匹配规则简直就是噩梦。

所以基本所有的go框架干的最主要的一件事情就是重写net/http的route。我们直接说 gin就是一个 httprouter 也不过分, 当然gin也提供了其他比较主要的功能, 后面会一一介绍。

综述, net/http基本已经提供http服务的70%的功能, 那些号称贼快的go框架, 基本上都是提供一些功能, 让我们能够更好的处理客户端发来的请求. 如果你有兴趣的话,也可以基于 net/http 做一个 Go 框架出来。

以上内容转载自公众号HHFCodeRv,如需转载请关注公众号进行联系~!

- END -

扫码关注公众号「网管叨bi叨」

给网管个星标,第一时间吸我的知识 

gin源码解析(1) - gin 与 net/http 的关系相关推荐

  1. gin 源码解析 - 详解http请求在gin中的流转过程

    本篇文章是 gin 源码分析系列的第二篇,这篇文章我们主要弄清一个问题:一个请求通过 net/http 的 socket 接收到请求后, 是如何回到 gin 中处理逻辑的? 我们仍然以 net/htt ...

  2. Gin源码解析和例子——中间件(middleware)

    在<Gin源码解析和例子--路由>一文中,我们已经初识中间件.本文将继续探讨这个技术.(转载请指明出于breaksoftware的csdn博客) Gin的中间件,本质是一个匿名回调函数.这 ...

  3. Java源码解析:hashCode与相同对象的关系

    1.普通类对象 1. hashCode相同,不一定是同一个对象 2. 同一个对象的,hashCode值一定相同 2. 数值型的原始数据类型对应的包装类 只要值是一样的,hashCode就会是相同的.尽 ...

  4. Gin源码解析和例子——路由

    Gin是一个基于golang的net包实现的网络框架.从github上,我们可以看到它相对于其他框架而言,具有优越的性能.本系列将从应用的角度来解析其源码.(转载请指明出于breaksoftware的 ...

  5. Go框架 gin 源码学习--路由的实现原理剖析

    往期回顾: gin源码解析 - gin 与 net/http 的关系 gin 源码解析 - 详解http请求在gin中的流转过程 上面两篇文章基本讲清楚了 Web Server 如何接收客户端请求,以 ...

  6. Java 线程池ThreadPoolExecutor的应用与源码解析

    ThreadPoolExecutor 工作原理 假设corePool=5,队列大小为100,maxnumPoolSize为10 向线程池新提交一个任务,会根据ThreadFactory创建一个新的线程 ...

  7. 详细讲解go web框架之gin框架源码解析记录及思路流程和理解

    开篇 首先gin 框架是在 官方提供的net/http标准包进行的相应封装. 那么要想理解gin框架, 就要先懂一些 net/http标准包 的相关知识. 可以参考中文的 文档: https://st ...

  8. android tcp socket框架_最流行的 Web 框架 Gin 源码阅读

    最近公司大部分项目开始往golang换, api的框架选定使用gin, 于是将 gin的源码看了一遍, 会用几篇文章将gin的流程及流程做一个梳理, 下面进入正题. gin框架预览 上图大概是 gin ...

  9. 谷歌BERT预训练源码解析(二):模型构建

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_39470744/arti ...

最新文章

  1. 网络通信应用开发利器!—— ESPlus —— ESFramework通信框架的增强库
  2. Latent dirichlet allocation note -- Prepare
  3. TFS 2008 中文版下载及安装完整图解
  4. 玩聚SR和FriendFeed的区别
  5. 验证DetailsView插入数据不为空
  6. Unity AI副总裁Danny Lange:如何用AI助推游戏行业?
  7. JEECG 命名规范
  8. 推荐几篇开源论文,包含人脸、目标检测跟踪、分割、去噪、超分辨率等
  9. 微服务 java9模块化_Java9系列第8篇-Module模块化编程
  10. php百度鹰眼,Android 百度鹰眼里程计算简单实列
  11. java arraylist 添加对象_如何在Java中将对象添加到ArrayList
  12. 2020最新淘宝等级表图及商品发布限制数量类目表
  13. HTML led字体包
  14. 合宙Air105 + GC032A摄像头驱动显示教程说明
  15. 母亲的牛奶 Mother's Milk(usaco)
  16. [安卓开发] 总结一些android的云测试平台
  17. 好玩的免费GM游戏整理汇总
  18. 买华为手机U8825D的体验
  19. 解决mac 10.11 以后 无法使用未签名第三驱动
  20. ROS系统安装 kinetic (超详细)

热门文章

  1. 在PS中如何进行图文互排,且层的使用……
  2. nodejs 二进制安装
  3. 重炉后-文件上传下载
  4. cocos2dx Auto-batching的使用
  5. 无法删除sqlserver的jobs的方式
  6. 【开源】QuickPager ASP.NET2.0分页控件V2.0.0.1——支持多种数据库。让分页更加简单。...
  7. 类成员的访问修饰符和可访问性
  8. Practice Lab 7:路由再分发
  9. 苏宁智慧家庭助跑智慧零售
  10. Angularjs 中使用 layDate 日期控件