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

news2024/11/15 9:40:21

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 = ctx
	return 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.2
		go 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 support
		req := 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获取context
	c := engine.pool.Get().(*Context)
	//将上次 response的 相关信息清除,并将这次response相应信息存入其中
	c.writermem.reset(w)
	// 将请求信息存入context
	c.Request = req
	// 重置context
	c.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.trees
	for 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.handlers
			c.fullPath = value.fullPath
			c.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 int16

walk: // Outer loop for walking the tree
	for {
		// 获取本节点的path
		prefix := 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 indices
				idxc := path[0]
				for i, c := range []byte(n.indices) {
					if c == idxc {
						//  strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
						if 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.fullPath
				return
			}
			// ...
}
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.coloradmin.cn/o/2135758.html

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

相关文章

SurfaceTexture OnFrameAvailableListener 调用流程分析

背景: 最近项目中遇到一个问题, 需要搞清楚OnFrameAvailableListener 回调流程, 本文借此机会做个记录, 巩固印象, 有相关困惑的同学也可以参考下. 本文基于Android 14 framework 源码进行分析 SurfaceTexture.java OnFrameAvailableListener 设置过程 public void setOnFra…

102.游戏安全项目-显示人物属性

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易道云信息技术研究院 上一个内容&#xff1a;101.游戏安全项目-创建人物对象结构 效果图&#xff1a; 以 101.游戏安全项目-创建人物…

[产品管理-15]:NPDP新产品开发 - 13 - 产品创新流程 - 具体产品的创新流程:精益生产与敏捷开发

目录 前言&#xff1a;​ 一、集成产品开发IPD模型——集成跨功能团队的产品开发 1.1 概述 1、IPD模型的核心思想 2、IPD模型的主要组成部分 3、IPD模型的实施步骤 4、IPD模型的优点 1.2 基于IPD系统的组织实践等级 1.3 IPD的优缺点 二、瀑布开发模型 1、定义与特点…

【一分钟学C++】std::memory_order

竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生~ 公众号&#xff1a; C学习与探索 | 个人主页&#xff1a; rainInSunny | 个人专栏&#xff1a; Learn OpenGL In Qt 文章目录 写在前面为什么需要Memory OrderMemory OrderRelaxed OrderRelease-Acquire Order 写在前面 使用std::mem…

day45-测试平台搭建之前端vue学习-基础4

目录 一、生命周期 1.1.概念 1.2.常用的生命周期钩子 1.3.关于销毁Vue实例 1.4.原理​编辑 1.5.代码 二、非单文件组件 2.1.组件 2.2.使用组件的三大步骤 2.3.注意点 2.4.关于VueComponent 2.5.一个重要的内置关系 三、今日学习思维导图 一、生命周期 1.1.概念 1).又名&…

每日OJ_牛客_点击消除(栈)

目录 牛客_点击消除&#xff08;栈&#xff09; 解析代码 牛客_点击消除&#xff08;栈&#xff09; 点击消除_牛客题霸_牛客网 描述&#xff1a; 牛牛拿到了一个字符串。 他每次“点击”&#xff0c;可以把字符串中相邻两个相同字母消除&#xff0c;例如&#xff0c;字符…

【机器学习】10——logistic的直观理解

机器学习10——logistic的直观理解 logistic 目录 机器学习10——logistic的直观理解训练过程具体例子 训练过程 数据集: 特征: 学习时间&#xff08;例如&#xff0c;1小时、2小时等&#xff09;。 标签: 是否通过考试&#xff08;0 或 1&#xff09;。 模型结构: 输入: 学习…

OpenAI推出o1系列模型:AI思考力爆表,带来全新智能体验

OpenAI的——o1系列模型&#xff0c;传说中的「草莓」&#xff0c;终于来与大家见面了&#xff01; 这个新模型可不一般&#xff0c;它可以进行复杂的推理&#xff0c;就像在认真思考一样&#xff0c;不再是简单的回答问题。CEO奥特曼称&#xff0c;这是一个全新的开始。它不仅…

智能照明监控系统在发电厂的应用

0前言 国内发电厂普遍使用传统照明控制方法&#xff0c;包括配电箱集中控制、就地开关控制和自动控制。然而&#xff0c;随着技术进步&#xff0c;这些方法已无法满足对安全、舒适、便捷、信息交互和节能环保的需求。因此&#xff0c;实施智能照明控制系统变得必要&#xff0c…

考研报名确认上传身份证户口本学历证明照片如何压缩裁剪

随着考研季节的到来&#xff0c;数以万计的考生开始准备报名所需的各种材料。在这一过程中&#xff0c;证件照片的上传无疑是一个关键环节。正确的照片格式和尺寸不仅能确保报名流程的顺利进行&#xff0c;还能避免因材料不合格而造成的不必要麻烦。本文将详细介绍如何在考研报…

密码学基础--ECDSA算法入门

目录 1.ECDSA签名长度的疑惑 2.ECDSA原理 2.1 生成签名 2.2 验签过程 2.3 签名编码问题 3.小结 1.ECDSA签名长度的疑惑 我们来看看ECDSA签名长什么样子&#xff0c;使用MuscleV02自动生成密钥对&#xff0c;并对message"0x11223344”进行签名&#xff0c;结果如下&a…

一款超级给力的DAW软件flstudio24.1.1.4285最新破解版!

嗨&#xff0c;音乐制作爱好者们&#xff01;今天要跟大家安利一款超级给力的DAW软件&#xff0c;它就是——fl studio24.1.1.4285最新破解版&#xff01; fl studio24.1.1.4285最新破解版简介&#xff1a;这款强大的数字音频工作站软件&#xff0c;是音乐人梦寐以求的工具。它…

Python+Pytest框架,“api_key.py文件怎么编写“?

1、在"api_keyword"文件夹下新增"api_key.py" import allure import requests import json import jsonpath from deepdiff import DeepDifffrom config import *allure.title("测试用例执行") class ApiKey:allure.step(">>>:开…

【win工具】win安装flameshot并设置截图快捷键

1.下载flameshot软件2.windows端配置flameshot快捷键3.取消win自带截图快捷键 1.下载flameshot软件 https://flameshot.org/#download installer版本为安装包 portable版本为免安装版 2.windows端配置flameshot快捷键 https://cloud.tencent.com/developer/article/2114952 W…

Java项目: 基于SpringBoot+mybatis+maven课程答疑系统(含源码+数据库+毕业论文)

一、项目简介 本项目是一套基于SpringBootmybatismaven课程答疑系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、…

Pikachu靶场之csrf

CSRF 跨站请求伪造 CSRF入门及靶场实战 - FreeBuf网络安全行业门户 攻击者伪造恶意链接&#xff0c;诱使用户点击&#xff0c;这个链接附带了用户的认证凭据Cookie、Session等&#xff0c;执行操作如转账。 因为带了cookie、session&#xff0c;服务器认为是用户的行为。借用…

尚品汇-订单拆单、支付宝关闭交易、关闭过期订单整合(五十)

目录&#xff1a; &#xff08;1&#xff09;拆单接口 &#xff08;2&#xff09;取消订单业务补充关闭支付记录 &#xff08;3&#xff09;支付宝关闭交易 &#xff08;4&#xff09;查询支付交易记录 &#xff08;5&#xff09;PaymentFeignClient 远程接口 &#xff08…

玩转扩展库,温湿度传感器篇!—合宙Air201资产定位模组LuatOS快速入门05

随着LuatOS快速入门系列教程的推出&#xff0c;小伙伴们学习热情高涨。 合宙Air201不仅支持三种定位方式&#xff0c;还具有丰富的扩展功能&#xff0c;通过外扩BTB链接方案&#xff0c;最多可支持21个IO接口&#xff1a;SPI、I2C、UART等多种接口全部支持。 本期&#xff0c…

electron-vite vue3离线使用monaco-editor

目录 1.搭建一个 electron-vite 项目 2.安装monaco-editor和vite-plugin-monaco-editor 3.electron.vite.config.mjs配置 4.创建 worker.js并在main.js 引入 5.创建组件 MonacoVite.vue 组件 6. App.vue中引入组件 7.运行测试 1.搭建一个 electron-vite 项目 pnpm creat…

如何在算家云搭建TripoSR(三维重建)

一、模型介绍 TripoSR是由Tripo AI和Stability AI合作开发的先进开源模型&#xff0c;能在短时间内从单张图片生成高质量 3D 模型。 利用大型重建模型&#xff08;LRM&#xff09;的原理&#xff0c;TripoSR带来了关键的进步&#xff0c;大大提高了3D重建的速度和质量。模型的…