IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> gin 源码阅读 - gin 与 net/http 的关系 -> 正文阅读

[网络协议]gin 源码阅读 - gin 与 net/http 的关系

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?main

import?"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)

48e1bfdced15cfcad13a6921ff98e28e.png
TCP/IP 五层模型
68a38c977047f53b3de1bc3f9b9dbe7e.png
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)?//?<-----看这里listen
????if?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()?//?<-----?看这里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)?//?<---?看这里
????}
}

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.Handler
????if?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?=?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
}

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

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

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

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-09-18 10:36:22  更:2021-09-18 10:37:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 1:55:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码