Go第三方框架--gin框架(三)

5. net/http框架源码-- 多路复用的实现

这块核心功能对应 1.3 的圆圈2,所属代码如下图:
在这里插入图片描述
run代码涉及的操作不是gin框架的核心,还记的我说过gin是在net/http的基础上操作的吗,我们来看下gin和net/http包的关联关系。
gin: 主要建立engine ,生成http方法树和对方法树的查找。剩下的采用多路复用实现的连接等操作都是使用的net/http。怎么复用?我们已经说过了,engine实现了 handler的 ServerHTTP方法。具体见1

好了梳理了大部分流程,我们再在来梳理下Run()方法。其代码如下

func (engine *Engine) Run(addr ...string) (err error) {defer func() { debugPrintError(err) }()if engine.isUnsafeTrustedProxies() {debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")}// 解析 地址address := resolveAddress(addr)debugPrint("Listening and serving HTTP on %s\n", address)// 这里引入 net/http包 直接调用它的方法err = http.ListenAndServe(address, engine.Handler())return
}

我们看到这里直接调用了 net/http包的方法。
再往下讲述之前我们先来梳理下net包服务器客户端连接的要求:

5.1. 一个服务器对应多个客户端,且新的客户端链接不知道啥时候来,所以需要异步等待 ----windows:IOCP模型,linux:epoll模型
5.1.1. 首先建立套接字,将server_ip:port跟套接字绑定,然后封装成一个tcp的监听器
5.1.2. 当客户端开始连接时,根据服务器监听器,启动一个协程异步阻塞监听端口(IOCP或者epoll, 新建一个tcp监听器 这个tcp连接有 套接字信息 server_ip:port+c;client_ip:port

5.1.1的调用链如下
我们来看下具体代码
在这里插入图片描述
这里忽略了 调用的 结构体 只列出涉及的函数 感兴趣的可以自己追踪下
重点介绍两个节点

socket: socket(…) 函数创建 套接字 并将套接字注册到新创建的文件描述符中

fd.pd.init: (调用链最后一个函数)将文件描述符注册到监听事件中

func (pd *pollDesc) init(fd *FD) error {serverInit.Do(runtime_pollServerInit)  //初始化 Go 语言运行时的网络轮询服务器. 对应 epoll_create(如果是linux) 建立红黑树ctx, errno := runtime_pollOpen(uintptr(fd.Sysfd)) // 打开一个文件描述符 对应 epoll_ctl 可以对红黑树进行增删操作if errno != 0 {return errnoErr(syscall.Errno(errno))}pd.runtimeCtx = ctxreturn nil
}

这里只是简要介绍感兴趣的可以自己追踪研究下,到这里套接字被层层包装 到了 tcp监听器中。

好了到这里 服务器的tcp监听器就建立了。

我们来梳理下 套接字的 嵌套流程

套接字(socket)—>文件描述符(fd:这里有对特定内存的读写,请求和响应都这这里)---->tcp连接(只有
seriver_ip:port)

5.1.2 的调用链为:
在这里插入图片描述
调用完后会生成一个新的 tcp连接 包含服务器客户端的ip,然后启动一个协程来开始处理这个tcp连接,这时tcp握手完成,这个新的协程就开始执行新客户端发来的请求了,大致是这样。
srv.Serve函数结构如下:

func (srv *Server) Serve(l net.Listener) error {// ...ctx := context.WithValue(baseCtx, ServerContextKey, srv)for {// 生成一个新的tcp链接;调用 runtime_pollWait (epoll) ;这里阻塞,等待新的客户端来建立tcp连接rw, err := l.Accept()// ... // 启动新的协程 来处理这个tcp连接 这时 进入5.2go c.serve(connCtx)}
}
5.2. 一旦某个信道(server_ip:port+c;ient_ip:port)建立,就可以不断从对应的套接字进行接收和发送数据 ---- 套接字介入(套接字其实就是可以操作某特定内存的句柄,特定内存在这里一般指request和reponse请求需要使用的一对 buf)

上面 代码中 go c.serve(connCtx) 对应的5.2的主要功能,我们来简要梳理下:

func (c *conn) serve(ctx context.Context) {// ...c.r = &connReader{conn: c}c.bufr = newBufioReader(c.r)  // 这里将 网络tcp连接(当然也包括对应的套接字)跟bufr对接 使得网络流可以输入(request请求)到buf中,也可以向buf中写(response响应) 这样整个链条就通了c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10) //向buf中写(response响应) for {w, err := c.readRequest(ctx)  //采用bufer.ReadLine 方法来同步阻塞 等待某个特定客户端的请求(例如在网页调用某个网址加url),从bufr中获取数据 (request请求),w是响应头 这个w包装了 c(完整的tcp连接)。// ... // Expect 100 Continue supportreq := w.req// 这里调用 gin的ServeHTTP(gin实现了ServeHTTP函数,这里就是各个HTtp框架和net/http 框架交互的地方)serverHandler{c.server}.ServeHTTP(w, w.req)// ...}
}

到这里我们可以看到tcp连接句柄又被包装了一层 放到了 response(w)中。所以套接字的包装如下:

套接字(socket)—>文件描述符(fd:这里有对特定内存的读写,请求和响应都这这里)---->tcp连接(只有
seriver_ip:port)---->response(w;server_ip:port, client_ip:port)。

可以看到在5.1.2的调用链上,又包装了一层response。
我们只简要介绍下net/http的部分代码,感兴趣的可以自己去看下源码。到这里1.3 的圆圈2(其实还要加上其前面和后面的一步)对应内容基本就简要讲解完毕了。接下来又轮到gin框架的介入,基本流程是这样的:

  1. 启动gin框架,建立gin的方法树
  2. 开始执行run函数,这时run函数调用net/http框架来处理tcp连接(包括建立服务端套接字,阻塞等待新客户端到来然后建立新的完整的套接字)
  3. 等到 连接建立,就开始从对应buf获取请求和响应体,然后将请求和响应结构体转交给gin框架来处理

接下来就是gin框架处理请求和返回响应体了,主要涉及上述代码段最后一行代码

serverHandler{c.server}.ServeHTTP(w, w.req)
5.3. 服务器端需要接收请求(request),处理请求和返回请求(response) ----gin实现的ServerHttp接口可以介入此操作

5.3就进入了gin框架的地界,我们接下来看下gin框架怎么处理请求。

6. gin框架源码–路由匹配(压缩前缀树的查找)

这里来到了 gin的ServeHTTP函数 这个函数主要干两件事,公平,公平,还是他…

  1. 根据url查找对应的树节点
  2. 根据树节点的挂载的函数,来执行请求,返回响应体(这里返回底层又调用的net/http包)
    我们来看下ServeHTTP函数
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {// 从pool获取contextc := engine.pool.Get().(*Context)//将上次 response的 相关信息清除,并将这次response相应信息存入其中c.writermem.reset(w)// 将请求信息存入contextc.Request = req// 重置contextc.reset()// 开始执行request请求engine.handleHTTPRequest(c)// 将context存入池中engine.pool.Put(c)
}

这里套接字链条再增加一个 context,如下:

套接字(socket)—>文件描述符(fd:这里有对特定内存的读写,请求和响应都这这里)---->tcp连接(只有
seriver_ip:port)---->response(w;server_ip:port, client_ip:port)---->context。我们层层传递的都是对应的结构体的指针(有显式的指针和隐式指针-----接口)这样可以做到同一个tcp数据流只会有一个套接字来处理,也就是同一套bufer存储r和w,以此类推。

ServerHTTP函数中 engine.handleHTTPRequest©是主逻辑实现,其代码如下:

// handleHTTPRequest 主要来实现 来自客户端的request请求;ServeHTTP 的主要实现
func (engine *Engine) handleHTTPRequest(c *Context) {// 获取请求方法和路径httpMethod := c.Request.Method// ...// Find root of the tree for the given HTTP method// 从根节点获取相关http方法对应的树t := engine.treesfor i, tl := 0, len(t); i < tl; i++ {if t[i].method != httpMethod {continue}root := t[i].root// Find route in tree//   根据url获取相关 节点 主要获取 执行函数链value := root.getValue(rPath, c.params, c.skippedNodes, unescape)if value.params != nil {c.Params = *value.params}// 开始执行函数链 (函数链一般包括中间件函数和对应组添加的额外函数,注册的函数一般最后执行)if value.handlers != nil {c.handlers = value.handlersc.fullPath = value.fullPathc.Next()c.writermem.WriteHeaderNow()return}// 特殊情况 不做介绍// ...
}

其中 root.getValue用来从树上获取url对应的根节点,然后开始执行根节点上挂载的函数,执行挂载函数主要执行我们构造框架时注册的函数,然后通过context的函数来执行底层net/http方法返回响应。

6.1 获取对应树节点

root.getValue代码如下:

/ getValue;engine 实现获取path对应节点的核心函数;主要思路是 将path按照每层节点的 path 参数进行截断 比较,然后置换参数 for循环 直到找到符合条件的node;采用的算法是树的层次遍历
// ps: 只介绍最通用的正常路径匹配,有通配符等特殊匹配情况不再介绍之列
func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {var globalParamsCount int16walk: // Outer loop for walking the treefor {// 获取本节点的pathprefix := n.path// 如果本节点的路径长度 小于 要寻找的路径 则截断 路径 置换 节点;继续下一个层次节点的寻找;eg: n.path=/aa   path=/aa/bb 则 进行截断 path=/bb 节点是 /aa的某子节点。if len(path) > len(prefix) {if path[:len(prefix)] == prefix {path = path[len(prefix):]// Try all the non-wildcard children first by matching the indicesidxc := path[0]for i, c := range []byte(n.indices) {if c == idxc {//  strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChildif n.wildChild {index := len(*skippedNodes)*skippedNodes = (*skippedNodes)[:index+1](*skippedNodes)[index] = skippedNode{path: prefix + path,node: &node{path:      n.path,wildChild: n.wildChild,nType:     n.nType,priority:  n.priority,children:  n.children,handlers:  n.handlers,fullPath:  n.fullPath,},paramsCount: globalParamsCount,}}n = n.children[i]continue walk}}// ...// 如果比配上 则返回本节点对应的 函数链和全路径if path == prefix {// ...// Check if this node has a handle registered.if value.handlers = n.handlers; value.handlers != nil {value.fullPath = n.fullPathreturn}// ...
}
6.2 执行请求 返回

获得了挂载的树后,开始执行函数链,例如假设 以 2.2 中的 路径“/aa/bb”
浏览器输入 localhost:8080/aa/bb 后,则获得树的对应节点value的函数参数是func(c *gin.Context) { c.JSON(200, gin.H{"route path ": “/benchmark/bb”}) },
我们来添加一些代码:

func(c *gin.Context) {req:=c.req
// 示例代码
respInfo:= handle(req) // 处理请求
c.writer.xxx=respInfo  // 响应体c.JSON(200, gin.H{"route path ": "/benchmark/bb"}) }

c是包含req和resp的 context见调用链,执行解析请求后,执行请求后,会执行c.JSON 来返回函数。而c.JSON内部调用 net/http来向客户端返回执行结果,到这里整个请求和返回的链条就闭环了。

7. 收尾

我们可以看到,gin框架只根据url和方法(GET/POST等)构建方法树,然后根据url和方法来找到对应的树节点,最后执行函数,将结果存入返回体。其余的操作都会交给net/http包来实现。

ps: 本人菜鸟 不太专业 如果有错还请各位大侠指出;免责声明:凡是按照本八股文去面试被怼的,本人概不承担责任。
参考文章
https://juejin.cn/post/7263826380889915453

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1534770.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

批发供应系统案例分析

在快速变化的商业环境中&#xff0c;创新成为推动批发供应行业不断前行的关键动力。以下将通过几个案例分析&#xff0c;探讨创新批发供应系统如何引领行业变革&#xff0c;提升效率&#xff0c;优化服务&#xff0c;并创造新的市场机遇。 一、智能化库存预测与补货系统 案例概…

人工智能在鼻咽癌中的应用综述|文献精析·24-09-13

小罗碎碎念 这篇文章系统回顾了人工智能在鼻咽癌管理中的应用&#xff0c;发现AI在提高诊断、预后评估和放疗计划的自动化方面具有积极影响。 作者角色作者姓名单位&#xff08;中文&#xff09;第一作者Wai Tong Ng香港大学深圳医院临床肿瘤中心&#xff0c;中国&#xff1b;香…

不可思议!这7个反共识设计原则,正悄然改变AI应用的未来格局!

引言 在AI技术日益成熟的今天&#xff0c;如何设计出既符合用户需求又具备高度智能化的原生应用&#xff0c;成为摆在开发者面前的重要课题。然而&#xff0c;传统的应用设计思维往往限制了AI潜力的充分发挥。本文提出的七个反共识观点&#xff0c;旨在挑战传统观念&#xff0…

老程序员的数字游戏开发笔记(一) —— 世界从Godot开始

目录 开篇废话 GDScript 是什么 创建 GDScript 背后的动机是什么 Godot 会支持【此处插入 FMOD、GameWorks 等闭源 SDK 的名字】吗&#xff1f; 是否能用 Godot 创建非游戏应用&#xff1f; Godot 使用的用户界面工具包是什么&#xff1f; 为什么 Godot 使用 SCons 构建…

抖音视频下载

对于特别喜欢的视频有时需要珍藏&#xff0c;下文方法可能会帮到你&#xff0c;但要注意尊重版权和遵守相关声明。 Edge浏览器打开抖音短视频&#xff0c;按F12&#xff0c;选择 网络&#xff1b;筛选条件?a&#xff1b;双击搜索结果打开视频&#xff1b;选择想要的视频&…

镜像问题(k8s部署考试系统)

如果使用containerd拉取不到镜像的话&#xff0c;就使用docker 1.修改decker的镜像源 [rootk8s-master ~]# vim /etc/docker/daemon.json { "registry-mirrors": [ "https://do.nark.eu.org", "https://dc.j8.work", …

SSM 传统工艺品销售网站---附源码74714

摘 要 近年来&#xff0c;信息网络迅猛推进&#xff0c;其具有灵活方便、传递消息速度快等优点&#xff0c;这一新兴媒体日渐兴盛&#xff0c;已经成为人们日常生活获取信息一个重要手段。从08开始&#xff0c;电商行业如春风吹过后的小草&#xff0c;呈现出一片蓬勃地发展态势…

人力资源管理系统员工组织与微软AD域服务系统集成案例

一、项目背景 上海某电路半导体有限公司主要产品应用于图像传感器、 图像信号处理芯片、 低功耗芯片、 射频芯片等。 公司内部HR人力资源管理系统统一进行人员批量预入职和同步等流程&#xff0c;由SAP PO平台进行中间管理&#xff0c;在微软AD域系统上创建人员&#xff0c…

若依plus-cloud nacos yml 动态获取 配置内容

这是剑走偏锋的写法。 直接上代码 在nacos中配置一个ruoyi-cms.yml 内容中有cms:filepath:******* springcloud配置 package org.dromara.cms.entity;import lombok.RequiredArgsConstructor; import org.springframework.core.env.Environment; import org.springframewo…

优化IDEA卡顿,提示慢的问题,亲测有效!

1、优化JVM的参数 以下文件在idea安装目录的idea64.exe.vmoptions文件中。 一般来说我们只需要调整-Xms、-Xmx、-XX:ReservedCodeCacheSize三个即可&#xff0c;根据电脑的实际内存去调&#xff0c;我的电脑是48G内存&#xff0c;调到了 -Xms4096m (堆初始内存大小) -Xmx8192m…

基于图像的端到端方案实现小车在模拟城市场景中的自主导航

基于图像的端到端方案实现小车在模拟城市场景中的自主导航 FSD&#xff08;Full Self-Driving&#xff09;是特斯拉公司推出的一种自动驾驶技术&#xff0c;旨在实现完全自主的驾驶体验。FSD系统依靠大量的数据和高级的机器学习算法&#xff0c;结合车载传感器&#xff08;如摄…

共模干扰的形成和滤除

1、共模就是共同对地的干扰&#xff1a; 如图&#xff0c;我们可以看到共模的原理图。UPQ就是共模电压&#xff0c;ICM1 ICM2 就是共模电流。 ICM1 ICM2 大小不一定相同&#xff0c;方向相同。 2、共模信号和差模信号的区别&#xff1a; 通常电源线有三根线&#xff1a;火线L、…

红帽RHCA认证什么级别?红帽认证等级细分攻略

Linux 系统作为开源软件的杰出典范&#xff0c;于服务器、云计算、大数据等诸多领域占据着至关重要的地位。对于矢志在 Linux 领域精研深耕的技术人才而言&#xff0c;红帽认证无疑是擢升技能与职业竞争力的关键密钥。今日&#xff0c;我们即将深度剖析 RHCA 红帽认证架构师这一…

[产品管理-4]:NPDP新产品开发 - 2 - 战略 - 制定企业经营战略目标的结构化方法与工具

目录 一、SWOT分析工具 1、SWOT分析工具概述 2、SWOT分析与企业战略目标制定的关系 3、SWOT分析在企业战略目标制定中的应用实例 4、SWOT分析的改进与应用建议 二、P E S T L E 分 析&#xff1a;外部环境分析 2.1 概述 1. 政治因素&#xff08;Political&#xff09; …

建筑机器人通用操作系统设计方案

建筑机器人操作系统通用发行版概述 1. 基础版&#xff08;Entry Level&#xff09; 目标用户&#xff1a;小型建筑公司、DIY爱好者或初学者。特点&#xff1a;提供基础的机器人控制和任务管理功能&#xff0c;支持简单的自动化作业流程&#xff0c;如基础的混凝土搅拌、物料搬…

盘点那些初级软件测试面试题汇总

一、请描述如何划分缺陷与错误严重性和优先级别&#xff1f; 给软件缺陷与错误划分严重性和优先级的通用原则&#xff1a; &#xff08;1&#xff09;表示软件缺陷所造成的危害和恶劣程度。 &#xff08;2&#xff09;优先级表示修复缺陷的重要程度和次序。 严重性&#xf…

探索广东省自闭症寄宿学校的独特教育模式

在广东省这片充满活力的土地上&#xff0c;自闭症儿童的教育问题日益受到社会各界的关注。随着特殊教育领域的不断发展&#xff0c;一系列针对自闭症儿童的寄宿学校应运而生&#xff0c;其中&#xff0c;广州的星贝育园自闭症儿童寄宿制学校以其独特的教育模式&#xff0c;成为…

性能测试-性能分析与调优原理总结

性能分析与调优如何下手&#xff0c;先从硬件开始&#xff0c;还是先从代码或数据库。 从操作系统&#xff08;CPU调度&#xff0c;内存管理&#xff0c;进程调度&#xff0c;磁盘I/O&#xff09;、网络、协议&#xff08;HTTP&#xff0c; TCP/IP &#xff09;&#xff0c;还是…

Unity 粒子系统参数说明

一、Particle System 1. Duration&#xff08;持续时间&#xff09; 粒子系统运行一次所需的时间。它决定粒子系统持续播放的时间长度。 2. Looping&#xff08;循环播放&#xff09; 如果启用&#xff0c;粒子系统将在播放完一次后自动重新开始播放&#xff0c;直到你停止它…

IP-Adapter学习

参考&#xff1a;https://baijiahao.baidu.com/s?id1803373544569190578&wfrspider&forpc IP-Adapter 是 SD中比较重要的controlnet&#xff0c;由腾讯公司出品。主要原理&#xff1a;提取图像特征&#xff0c;并嵌入预训练文本&#xff0c;最终加入到扩散图像中。简…