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

news/2024/9/19 9:15:29/ 标签: golang, 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)
ginServerHttp_119">5.3. 服务器端需要接收请求(request),处理请求和返回请求(response) ----gin实现的ServerHttp接口可以介入此操作

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

gin_122">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.ppmy.cn/news/1527605.html

相关文章

Java重修笔记 第五十六天 坦克大战(六)多线程基础 - 线程同步、死锁

多线程同步机制 多线程编程中&#xff0c;一些敏感数据可能会被多个线程同时访问造成数据混乱&#xff08;例如票数&#xff09;&#xff0c;使用线程同步机制&#xff0c;通过锁对象&#xff08;对象实例或类实例&#xff09;的方式来保证该段代码在任意时刻&#xff0c;最多…

ram和rom的种类迭代和介绍

RAM&#xff08;随机存取存储器&#xff09;在计算机和电子设备中扮演着至关重要的角色。随着技术的发展&#xff0c;RAM有多个迭代和种类&#xff0c;每个种类在速度、功耗和使用场景上有所不同。以下是RAM的详细迭代种类及介绍。 1. SRAM&#xff08;Static RAM&#xff0c;…

linux-软件包管理-包管理工具(Debian 系)

Linux 软件包管理概述 在Linux系统中&#xff0c;软件包管理是系统维护的核心部分之一。通过软件包管理器&#xff0c;用户可以方便地安装、更新、删除和查询系统中的软件包。每个Linux发行版通常都有自己专属的包管理工具&#xff0c;这些工具基于不同的包格式。例如&#xf…

pycharm连接远程linux服务器上的docker进行深度学习训练

实习过程中由于GPU都在服务器上&#xff0c;编辑代码很麻烦。并且服务器上配置了docker的环境&#xff0c;所以用pycharm连接远程服务器的docker进行深度学习&#xff0c;这样在本地调用远程服务器的GPU和环境&#xff0c;更方便一点&#xff0c;将这个过程记录下来&#xff0c…

CentOS中使用DockerCompose方式部署带postgis的postgresql(附kartoza/docker-postgis镜像下载)

场景 CentOS中使用Docker部署带postgis的postgresql&#xff1a; CentOS中使用Docker部署带postgis的postgresql_centos postgis插件在容器中如何安装-CSDN博客 上面使用Docker搜索和拉取kartoza/postgis时并没有任何限制。 当下如果不能科学上网时&#xff0c;大部分镜像源…

力扣150题——多维动态规划

交错字符串 题目 97. 交错字符串 - 力扣&#xff08;LeetCode&#xff09; 思路 用dp[i][j]代表s1的前i个字母和s2的前s2个字母能否交错组成s3的前ij-1的子串 状态转移方程即为 如果 s1[i-1] s3[i j - 1]&#xff0c;并且 dp[i-1][j] 为 true&#xff0c;则 dp[i][j] 也…

vmware中的ubuntu系统扩容分区

1.虚拟机关机 右击虚拟机/设置&#xff0c;进入虚拟机设置 3.启动虚拟机&#xff0c;进入命令行 4.fdisk -l查看要扩展的分区名 5.resize要扩容的分区 su root parted /dev/sda resizepart 3 100% fdisk -l resize2fs /dev/sda3 df -T完成 6.其他 进入磁盘管理 fdisk /d…

oracle 如何查询表被锁

在Oracle数据库中&#xff0c;查询表是否被锁可以通过多种方式实现。以下是一些常用的方法来查询Oracle数据库中的表锁情况&#xff1a; 1. 使用V$LOCKED_OBJECT视图 V$LOCKED_OBJECT是Oracle提供的动态性能视图&#xff0c;用于显示当前被锁定的对象信息。通过查询该视图&am…

XML_Tomcat_HTTP

第四章 XML_Tomcat10_HTTP 一 XML XML是EXtensible Markup Language的缩写&#xff0c;翻译过来就是可扩展标记语言。所以很明显&#xff0c;XML和HTML一样都是标记语言&#xff0c;也就是说它们的基本语法都是标签。 可扩展 三个字表面上的意思是XML允许自定义格式。但这不代…

使用 Istio 缓解电信 5G IoT 微服务 Pod 架构的安全挑战

在 Kubernetes 集群中部署微服务在 5G 电信中至关重要。但是&#xff0c;它也带来了重大的安全风险。虽然防火墙规则和代理提供了初始安全性&#xff0c;但 Kubernetes 中的默认通信机制&#xff08;例如未加密的网络流量和缺乏访问控制&#xff09;本质上是不安全的。这种不安…

《拿下奇怪的前端报错》:nvm不可用报错`GLIBC_2.27‘‘GLIBCXX_3.4.20‘not Found?+ 使用docker构建多个前端项目实践

有些前端的小伙伴可能会好奇&#xff0c;nvm是什么&#xff1f;这里接简单介绍下&#xff0c;它是一个Nodejs版本管理工具。为什么需要它呢&#xff1f;当然是需要多个Nodejs版本的时候&#xff0c;那什么时候需要多个Nodejs版本&#xff1f;那肯定是在有点年头的公司了&#x…

使用git命令

git add . git commit -m "commit message" 拉取 git pull origin <branch-name> 推送 git push origin <branch-name> 创建新分支 git branch <new-branch-name> # 切换到新分支 git checkout <new-branch-name> 合并分支 git mer…

区块链先驱孙宇晨:引领价值传播,激发行业创新活力

​孙宇晨&#xff0c;这位被誉为“区块链布道师”的年轻企业家&#xff0c;以其独特的愿景和行动力在区块链行业中脱颖而出。作为波场TRON的创始人&#xff0c;他不仅是区块链技术的倡导者&#xff0c;更是一位不懈推动行业发展的领导者。他通过自身的努力和影响力&#xff0c;…

C++ | Leetcode C++题解之第415题字符串相加

题目&#xff1a; 题解&#xff1a; class Solution { public:string addStrings(string num1, string num2) {int i num1.length() - 1, j num2.length() - 1, add 0;string ans "";while (i > 0 || j > 0 || add ! 0) {int x i > 0 ? num1[i] - 0 …

进程监控与管理详解

一、进程的定义: 进程process是正在运行的程序,包括: 分配的内存地址空间 安全属性、包括所有权和特权 一个或多个线程 进程状态 进程的环境包括: 本地和全局变量 当前调度上下文…

算法入门-贪心1

第八部分&#xff1a;贪心 409.最长回文串&#xff08;简单&#xff09; 给定一个包含大写字母和小写字母的字符串 s &#xff0c;返回通过这些字母构造成的最长的回文串 的长度。 在构造过程中&#xff0c;请注意 区分大小写 。比如 "Aa" 不能当做一个回文字符串…

清理C盘缓存的垃圾,专业清理C盘缓存垃圾的步骤与策略

在维护计算机系统的过程中&#xff0c;定期清理C盘&#xff08;通常是系统盘&#xff09;中的缓存和垃圾文件是一项至关重要的任务。这不仅能有效释放磁盘空间&#xff0c;提升系统性能&#xff0c;还能减少因磁盘空间不足导致的程序运行缓慢或错误。以下是一系列专业且安全的步…

在 Stable Diffusion 1.5 中 Lora, Dreambooth, Textual Inversion的详解指北

Lora, Dreambooth and Textual Inversion 说明 您是否想象过您可爱的宠物与埃菲尔铁塔合影的画面&#xff0c;或者想象过如何生成一张带有您朋友面孔的人工智能图像&#xff1f; 是的&#xff0c;通过稳定扩散技术的微调&#xff0c;这完全是可能的&#xff01; 创建这些场景…

LCR 023

题目&#xff1a;LCR 023 解法一&#xff1a;哈希表 将链表A所有元素放入Set中&#xff0c;遍历链表B元素&#xff0c;若某一元素在Set中存在&#xff0c;则该元素便是重复元素 public ListNode getIntersectionNode(ListNode headA, ListNode headB) {Set<Object> set …

万兆时代 TCP/IP如何赋能以太网飞跃

科技飞速发展&#xff0c;数据传输的需求日益增长&#xff0c;尤其是在物理、科研等领域&#xff0c;对数据传输的速度、稳定性和效率提出了更高的要求。在这样的背景下&#xff0c;万兆以太网&#xff08;10Gbit Ethernet&#xff09;以其高带宽、低延迟和强大的传输能力成为众…