当前位置: 首页 > news >正文

21 go语言(golang) - gin框架安装及使用(二)

四、组成

前面的文章中,我们介绍了其中一部分组成,接下来继续学习:

  1. Router(路由器)

    • Gin 使用基于树结构的路由机制来处理 HTTP 请求。它支持动态路由参数、分组路由以及中间件。
    • 路由器负责将请求路径映射到相应的处理函数。
  2. Context(上下文)

    • gin.Context 是 Gin 中最重要的结构之一,它在请求生命周期内传递信息。
    • Context 提供了对请求和响应对象的访问,以及用于存储数据、设置状态码、返回 JSON 等方法。
  3. Middleware(中间件)

    • 中间件是可以在请求被最终处理之前或之后执行的一段代码,用于实现日志记录、错误恢复、认证等功能。
    • Gin 支持全局中间件和特定路由组或单个路由使用的中间件。
  4. Handlers(处理函数)

    • 处理函数是实际执行业务逻辑的位置,每个路由都会关联一个或多个处理函数。
    • 这些函数接收 gin.Context 参数,通过它们可以获取请求数据并生成响应。
  5. Error Handling(错误处理)

    • Gin 提供了一种机制来捕获和管理应用程序中的错误,可以通过 Context 的方法进行错误报告和恢复操作。
  6. Rendering and Responses(渲染与响应)

    • 支持多种格式的数据输出,包括 JSON、XML 和 HTML 渲染等,方便客户端消费不同类型的数据格式。
  7. Binding and Validation(绑定与验证)

    • 自动将 HTTP 请求中的数据绑定到结构体,并支持对输入数据进行验证,以确保其符合预期格式和规则。
  8. Templates (模板)

    • 虽然不是框架核心,但 Gin 支持集成 HTML 模板引擎,用于生成动态网页内容。

4.3 Middleware(中间件)

4.3.4 中间件的调用顺序

上一章讲的中间件,还有部分内容我们先来看看

4.3.4.1 按注册顺序执行

中间件会按照它们被添加到 Gin 实例上的顺序依次执行

func Test1(t *testing.T) {r := gin.Default()r.Use(func(c *gin.Context) {fmt.Println("func1 ...")})r.Use(func(c *gin.Context) {fmt.Println("func2 ...")})r.GET("/test1", func(c *gin.Context) {fmt.Println("test1 最终路由方法。。。")})r.Use(func(c *gin.Context) {fmt.Println("func3 ...")})r.GET("/test2", func(c *gin.Context) {fmt.Println("test2 最终路由方法。。。")})r.Run()
}

依次调用test1和test2路由,输出

func1 ...
func2 ...
test1 最终路由方法。。。
[GIN] 2024/12/13 - 15:11:44 | 200 |      24.436µs |             ::1 | GET      "/test1"
func1 ...
func2 ...
func3 ...
test2 最终路由方法。。。
[GIN] 2024/12/13 - 15:11:48 | 200 |      21.672µs |             ::1 | GET      "/test2"
4.3.4.2 c.Next()控制顺序

c.Next() 是 Gin 框架中 *gin.Context 类型的方法,用于控制中间件链的执行流程。

  • 当一个中间件调用 c.Next() 时,它将暂停当前处理中函数的执行,并将控制权交给下一个处理中函数或最终的路由处理器。
  • 多个中间件通过调用 c.Next() 可以形成嵌套结构,类似于栈。当所有后续步骤完成后,程序会回到先前上下文继续执行剩余代码。即返回时则是相反顺序(即“先进后出”)。

可以理解为,将原本顺序执行的后续中间件,嵌套在c.Next()中先执行

func Test2(t *testing.T) {r := gin.Default()r.Use(func(c *gin.Context) {fmt.Println("func1 begin...")//c.Next()fmt.Println("func1 end...")})r.Use(func(c *gin.Context) {fmt.Println("func2 begin...")//c.Next()fmt.Println("func2 end...")})r.Use(func(c *gin.Context) {fmt.Println("func3 begin...")//c.Next()fmt.Println("func3 end...")})r.GET("/test_next", func(c *gin.Context) {fmt.Println("test_next 最终路由方法。。。")})r.Run()
}

当注释掉所有c.Next()时,顺序执行

func1 begin...
func1 end...
func2 begin...
func2 end...
func3 begin...
func3 end...
test_next 最终路由方法。。。

打开func1的c.Next()时,后续的中间件和最终路由就全部在c.Next()中执行完,最后才返回func1继续执行,即最后再打印func1 end…

func1 begin...
func2 begin...
func2 end...
func3 begin...
func3 end...
test_next 最终路由方法。。。
func1 end...

再打开func2的c.Next()时,道理相同,c.Next()中嵌套c.Next()

func1 begin...
func2 begin...
func3 begin...
func3 end...
test_next 最终路由方法。。。
func2 end...
func1 end...

再打开func3的c.Next()时

func1 begin...
func2 begin...
func3 begin...
test_next 最终路由方法。。。
func3 end...
func2 end...
func1 end...
4.3.4.3 c.Abort()控制顺序

c.Abort() 是 Gin 框架中 *gin.Context 类型的方法,用于立即停止当前请求的进一步处理。

  • 一旦调用 c.Abort(),当前请求将不再继续传递给下一个处理中函数或最终的路由处理器。
  • 这意味着所有在调用点之后注册的中间件和路由处理器都不会被执行。
  • 调用 c.Abort() 会设置一个内部标志位,指示该请求已经被终止。这个标志可以通过 c.IsAborted() 方法检查。
  • 在某些情况下,例如认证失败、权限不足或其他需要立即返回响应的情况,可以使用 c.Abort() 来阻止进一步操作,并直接返回适当的响应给客户端。
func Test3(t *testing.T) {r := gin.Default()r.Use(func(c *gin.Context) {fmt.Println("func1 begin...")fmt.Println("func1 end...")})r.Use(func(c *gin.Context) {fmt.Println("func2 begin...")fmt.Println(c.IsAborted())c.Abort() // 从此中间件开始,后续的中间件包括最终路由都不再执行fmt.Println(c.IsAborted())fmt.Println("func2 end...") // 但是这行代码会执行})r.Use(func(c *gin.Context) {fmt.Println("func3 begin...")fmt.Println("func3 end...")})r.GET("/test_next", func(c *gin.Context) {fmt.Println("test_next 最终路由方法。。。")c.JSON(200, gin.H{"success": "true",})})r.Run()
}

输出:

func1 begin...
func1 end...
func2 begin...
false
true
func2 end...
4.3.4.3 Next 和 Abort 共同控制?

注:如果Next()中有Abort(),执行顺序是怎样的?

func Test4(t *testing.T) {r := gin.Default()r.Use(func(c *gin.Context) {fmt.Println("func1 begin...")c.Next()fmt.Println("func1 end...")})r.Use(func(c *gin.Context) {fmt.Println("func2 begin...")c.Abort()fmt.Println("func2 end...")})r.Use(func(c *gin.Context) {fmt.Println("func3 begin...")fmt.Println("func3 end...")})r.GET("/test_next", func(c *gin.Context) {fmt.Println("test_next 最终路由方法。。。")c.JSON(200, gin.H{"success": "true",})})r.Run()
}

输出:

func1 begin...
func2 begin...
func2 end...
func1 end...

可以看到,c.Next()还是会执行完,并返回后,打印func1 end...c.Abort()只是控制切断了func3的执行,这里很坑,问了很多AI,回答的都是func1 end...不会执行,要自己试过才知道

4.4 Handlers(处理函数)

处理函数(Handlers)是处理 HTTP 请求的核心组件。它们负责接收请求、执行业务逻辑,并返回响应。

4.4.1 Handlers 的基本概念

  1. HandlerFunc 类型

    • 在 Gin 中,所有的处理函数都必须符合 gin.HandlerFunc 类型。
    • gin.HandlerFunc 被定义为:type HandlerFunc func(*Context)
    • 这意味着任何接受一个指向 *gin.Context 参数且无返回值的函数都可以作为一个合法的 Handler。
  2. Context 对象

    • 每个 Handler 都会接收到一个 *gin.Context 对象,它封装了请求和响应的信息。
    • 开发者可以通过这个对象来获取请求数据(如查询参数、表单数据、JSON 数据等)、设置响应状态码和内容,以及控制请求流转(如调用 Next()Abort())。

4.4.2 使用

我们在前面的代码例子中,无论是绑定在路由上,还是作为中间件使用,都是这个HandlerFunc类型

  1. Handlers 通常与特定路由绑定在一起,通过 HTTP 方法(如 GET, POST)以及路径进行匹配。
  2. 也可以多个 Handlers 可以组成中间件链,每个处理中步骤按顺序执行。
  3. 匿名函数或命名函数皆可用作 Handler
func myHandler(c *gin.Context) {fmt.Println("命名函数")
}func Test5(t *testing.T) {r := gin.Default()// 使用匿名函数my := func(c *gin.Context) {fmt.Println("匿名函数1")}r.GET("/t1", my)// 使用匿名函数r.GET("/t2", func(c *gin.Context) {fmt.Println("匿名函数2")})// 使用命名函数r.GET("/t3", myHandler)r.Run()
}

4.5 Error Handling(错误处理)

错误处理(Error Handling)帮助开发者在请求处理过程中捕获和管理错误。Gin 提供了一些机制来简化错误的记录、传递和响应。

4.5.1 基本概念

Gin 使用 gin.Error 类型来表示错误。它包含了一个 error 接口,以及一些附加信息:元数据(Meta any)和类型标识(Type ErrorType),其中的错误类型用于标识不同种类的错误,例如绑定错误、渲染错误等。可以通过设置不同的类型来对错误进行分类。

// 结构体的源码:
// Error represents a error's specification.
type Error struct {Err  errorType ErrorTypeMeta any
}

4.5.2 错误处理机制

4.5.2.1 Context 中的 Errors 字段
  • 每个 *gin.Context 对象都有一个 Errors 字段,这是一个存储所有发生在该请求生命周期内的 gin.Error 切片。
  • 开发者可以通过这个字段访问并操作这些累积起来的错误,并根据需要进行进一步操作(如日志记录或响应生成)。
4.5.2.2 添加错误

在处理中函数中,可以使用 c.Error(err) 方法将新的 error 添加到当前上下文中。

r.Use(func(c *gin.Context) {fmt.Println("func1...")c.Error(fmt.Errorf("手动写入错误1"))})
4.5.2.3 检索错误

可以通过迭代上下文中的 Errors 来访问所有已记录过得 errors。

func Test6(t *testing.T) {r := gin.Default()r.Use(func(c *gin.Context) {fmt.Println("func1...")c.Error(fmt.Errorf("手动写入错误1"))})r.Use(func(c *gin.Context) {fmt.Println("func2...")c.Error(fmt.Errorf("手动写入错误2"))})r.GET("/error", func(c *gin.Context) {errors := c.Errors// 遍历所有的错误for _, err := range errors {fmt.Println(err)}fmt.Println("正常返回")})r.Run()
}

输出,其中最后两行的日志是gin自带打印的错误信息

func1...
func2...
手动写入错误1
手动写入错误2
正常返回
[GIN] 2024/12/16 - 10:50:56 | 200 |      34.129µs |       127.0.0.1 | GET      "/error"
Error #01: 手动写入错误1
Error #02: 手动写入错误2

4.5.3 自定义全局异常处理中间件

为了统一管理应用程序中的异常情况,通常会创建一个全局异常处理中间件。在这个中间件里,可以遍历每个请求产生过得 errors 并做出相应反应,比如写入日志系统等。

func myErrorHandler(c *gin.Context) {c.Next() // 执行后续处理中步骤errors := c.Errors// 检查是否有任何errors被注册if len(errors) > 0 {for i, e := range errors {fmt.Println(i, e) // 打印或保存日志信息}// 返回通用响应给客户端c.JSON(500, gin.H{"msg": "内部错误",})}
}func Test7(t *testing.T) {r := gin.Default()r.Use(myErrorHandler)r.GET("/error", func(c *gin.Context) {// 模拟业务执行过程中的错误c.Error(fmt.Errorf("手动写入错误"))c.JSON(200, gin.H{"msg": "成功",})})r.Run()
}

客户端输出,可以看到,返回的有点错乱

{"msg": "成功"
}{"msg": "内部错误"
}

所以,要实现类似spring中全局的错误处理,还需要改进一下

func myErrorHandler2(c *gin.Context) {defer func() {errors := c.Errors// 在golang中,这些错误属于可以预期的错误,可以简单的打印日志或做对应的业务处理,真正类似java中不可预期的是panicif len(errors) > 0 {for i, e := range errors {fmt.Println(i, e) // 打印或保存日志信息}}// 这里处理真正的不可预期的报错hasPanic := recover()if hasPanic != nil {fmt.Println("捕获到异常!!")c.Abort() // 这行代码一定要加,不然后续代码还会执行,比如在最终路由中的打印// 返回通用响应给客户端c.JSON(500, gin.H{"msg": "内部错误",})}}()c.Next() // 执行后续处理中步骤
}func Test8(t *testing.T) {r := gin.Default()r.Use(myErrorHandler2)r.Use(func(c *gin.Context) {// 模拟错误list := []int{1, 2, 3, 4}i := list[5]println(i)})r.GET("/error", func(c *gin.Context) {fmt.Println("正常进入最终路由")c.JSON(200, gin.H{"msg": "成功",})})r.Run()
}

4.5.4 AbortWithError

Gin 提供了便捷方法 AbortWithError(statusCode int , err error ) ,用于同时终止请求并将指定状态码与error对象一起加入到context.errors列表内 。这使得开发者能够快速地结合HTTP协议标准状态码与具体业务逻辑需求来构建更具表达力且一致性强大的API接口 。

func Test9(t *testing.T) {r := gin.Default()r.Use(func(c *gin.Context) {fmt.Println("func1...")})r.Use(func(c *gin.Context) {random := rand.Intn(2)// 模拟随机错误if random == 0 {// 将错误添加到Context中,并且终止后续的调用链c.AbortWithError(500, fmt.Errorf("内部错误"))// 等价于//c.Error(fmt.Errorf("内部错误"))//c.AbortWithStatus(500)return}fmt.Println("func2...")})r.Use(func(c *gin.Context) {fmt.Println("func3...")})r.GET("/error", func(c *gin.Context) {c.JSON(200, "正常")})r.Run()
}

输出

// 异常情况
func1...
[GIN] 2024/12/16 - 16:34:28 | 500 |      18.752µs |       127.0.0.1 | GET      "/error"
Error #01: 内部错误// 正常情况
func1...
func2...
func3...
[GIN] 2024/12/16 - 16:34:30 | 200 |      61.868µs |       127.0.0.1 | GET      "/error"

相关文章:

21 go语言(golang) - gin框架安装及使用(二)

四、组成 前面的文章中,我们介绍了其中一部分组成,接下来继续学习: Router(路由器) Gin 使用基于树结构的路由机制来处理 HTTP 请求。它支持动态路由参数、分组路由以及中间件。路由器负责将请求路径映射到相应的处理…...

Intel(R) Iris(R) Xe Graphics安装Anaconda、Pytorch(CPU版本)

一、Intel(R) Iris(R) Xe Graphics安装Anaconda 下载网址:https://repo.anaconda.com/archive/ 双击Anaconda3-2024.10-1-Windows-x86_64,一直下一步,选择安装的路径位置,一直下一步就安装完成了。打开Anaconda PowerShell Promp…...

【Unity3D】实现可视化链式结构数据(节点数据)

关键词:UnityEditor、可视化节点编辑、Unity编辑器自定义窗口工具 使用Newtonsoft.Json、UnityEditor相关接口实现 主要代码: Handles.DrawBezier(起点,终点,起点切线向量,终点切线向量,颜色,n…...

Three.js推荐-可以和Three.js结合的动画库

在 Three.js 中,3D 模型、相机、光照等对象的变换(如位置、旋转、缩放)通常需要通过动画进行控制,以实现更加生动和富有表现力的效果。然而,Three.js 本身并没有内置的强大动画管理系统,尽管可以通过关键帧…...

增强现实(AR)和虚拟现实(VR)的应用

增强现实(AR)和虚拟现实(VR)是近年来迅速发展的技术,广泛应用于多个行业,提供沉浸式的体验和增强的信息交互。以下是AR和VR的定义及其在不同领域的具体应用。 相关学点: 2025年大数据、通信技术…...

告别机器人味:如何让ChatGPT写出有灵魂的内容

目录 ChatGPT的一些AI味道小问题 1.提供编辑指南 2.提供样本 3.思维链大纲 4.融入自己的想法 5.去除重复增加多样性 6.删除废话 ChatGPT的一些AI味道小问题 大多数宝子们再使用ChatGPT进行写作时,发现我们的老朋友ChatGPT在各类写作上还有点“机器人味”太重…...

【Threejs】从零开始(六)--GUI调试开发3D效果

请先完成前置步骤再进行下面操作:【Threejs】从零开始(一)--创建threejs应用-CSDN博客 一.GUI界面概述 GUI(Graphical User Interface)指的是图形化用户界面,广泛用在各种程序的上位机,能够通过…...

Cocos Creator 试玩广告开发

之前主要是使用Unity,这次刚好项目是试玩游戏的开发,所以临时学了Cocos来开发。所以这篇文章,更加关注从Unity转到Cocos开发的经历以及试玩的基本开发。 首先,我是没有使用过Cocos的,也没有接触过Ts语言,对于Ts的开发开…...

快速解决oracle 11g中exp无法导出空表的问题

在一些生产系统中,有些时候我们为了进行oracle数据库部分数据的备份和迁移,会使用exp进行数据的导出。但在实际导出的时候,我们发现导出的时候,发现很多空表未进行导出。今天我们给出一个快速解决该问题的办法。 一、问题复现 我…...

selenium 报错 invalid argument: invalid locator

环境: Python3.12.2 selenium4.0 报错信息: invalid argument: invalid locator 错误分析: selenium语法错误,find_element方法少写By.XPATH参数 错误语法如下: driver.find_element(//div[id"myid"]) 解决办…...

Flink2.0未来趋势中需要注意的一些问题

手机打字,篇幅不长,主要讲一下FFA中关于Flink2.0的未来趋势,直接看重点。 Flink Forward Asia 2024主会场有一场关于Flink2.0的演讲,很精彩,官方也发布了一些关于Flink2.0的展望和要解决的问题。 1.0时代和2.0时代避免…...

机械鹦鹉与真正的智能:大语言模型推理能力的迷思

编者按: 大语言模型真的具备推理能力吗?我们是否误解了"智能"的本质,将模式匹配误认为是真正的推理? 本文深入探讨了大语言模型(LLMs)是否真正具备推理能力这一前沿科学问题,作者的核…...

本地电脑使用命令行上传文件至远程服务器

将本地文件上传到远程服务器,在本地电脑中cmd使用该命令: scp C:/Users/"你的用户名"/Desktop/environment.yml ws:~/environment.yml 其中,C:/Users/“你的用户名”/Desktop/environment.yml是本地文件的路径, ~/en…...

【系统】Windows11更新解决办法,一键暂停

最近的windows更新整的我是措不及防,干啥都要关注一下更新的问题,有的时候还关不掉,我的强迫症就来了,非得关了你不可! 经过了九九八十一难的研究之后,终于找到了一个算是比较靠谱的暂停更新的方法&#x…...

34. Three.js案例-创建球体与模糊阴影

34. Three.js案例-创建球体与模糊阴影 实现效果 知识点 WebGLRenderer WebGLRenderer 是 Three.js 中用于渲染 3D 场景的核心类。它负责将场景中的对象绘制到画布上。 构造器 new THREE.WebGLRenderer(parameters)参数类型描述parametersObject可选参数对象,包…...

Qt同步读取串口

头文件 #include "InsScpi.h" #include <QObject> #include <QSerialPort>class TestSerial : public QObject {Q_OBJECT public:explicit TestSerial(QObject *parent nullptr);//打开设备bool openDevice(const QString &portName);//关闭设备…...

如何用上AI视频工具Sora,基于ChatGPT升级Plus使用指南

没有GPT&#xff0c;可以参考这个教程&#xff1a;详情移步至底部参考原文查看哦~ 1.准备工作 详情移步至底部参考原文查看哦~ 详情移步至底部参考原文查看哦~ 4.Sora使用 详情移步至底部参考原文查看哦 参考文章&#xff1a;【包教包会】如何用上AI视频工具Sora&#xff…...

对象的状态变化处理与工厂模式实现

一、引言 在 C 编程中&#xff0c;有效地处理对象的状态变化以及合理运用设计模式可以极大地提高代码的可维护性、可扩展性和可读性。本文将深入探讨 C 如何处理对象的状态变化以及如何实现工厂模式。 二、C 中对象的状态变化处理 使用成员变量表示状态 class GameCharacte…...

关于IP代理API,我应该了解哪些功能特性?以及如何安全有效地使用它来隐藏我的网络位置?

IP代理API是一种服务&#xff0c;允许用户通过访问经过中间服务器的网络连接来改变其公开的互联网协议地址&#xff08;IP&#xff09;&#xff0c;从而达到隐藏真实地理位置的效果。以下是您在选择和使用IP代理API时应关注的一些功能和安全性考虑&#xff1a; 匿名度&#xff…...

在Linux上将 `.sh` 脚本、`.jar` 包或其他脚本文件添加到开机自启动

在Linux上将 .sh 脚本、.jar 包或其他脚本文件添加到开机自启动 在Linux环境中&#xff0c;有时需要将一些程序、脚本或应用程序设置为开机时自动启动。这对于那些需要在系统启动时启动的服务或应用非常有用。本文将介绍如何将 .sh 脚本、.jar 包或其他脚本文件添加到Linux系统…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时&#xff0c;与数据库的交互无疑是核心环节。虽然传统的数据库操作方式&#xff08;如直接编写SQL语句与psycopg2交互&#xff09;赋予了我们精细的控制权&#xff0c;但在面对日益复杂的业务逻辑和快速迭代的需求时&#xff0c;这种方式的开发效率和可…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

go 里面的指针

指针 在 Go 中&#xff0c;指针&#xff08;pointer&#xff09;是一个变量的内存地址&#xff0c;就像 C 语言那样&#xff1a; a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10&#xff0c;通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...

ubuntu系统文件误删(/lib/x86_64-linux-gnu/libc.so.6)修复方案 [成功解决]

报错信息&#xff1a;libc.so.6: cannot open shared object file: No such file or directory&#xff1a; #ls, ln, sudo...命令都不能用 error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory重启后报错信息&…...

高防服务器价格高原因分析

高防服务器的价格较高&#xff0c;主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因&#xff1a; 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器&#xff0c;因此…...

用鸿蒙HarmonyOS5实现国际象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码&#xff0c;使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...

Copilot for Xcode (iOS的 AI辅助编程)

Copilot for Xcode 简介Copilot下载与安装 体验环境要求下载最新的安装包安装登录系统权限设置 AI辅助编程生成注释代码补全简单需求代码生成辅助编程行间代码生成注释联想 代码生成 总结 简介 尝试使用了Copilot&#xff0c;它能根据上下文补全代码&#xff0c;快速生成常用…...

自定义线程池1.2

自定义线程池 1.2 1. 简介 上次我们实现了 1.1 版本&#xff0c;将线程池中的线程数量交给使用者决定&#xff0c;并且将线程的创建延迟到任务提交的时候&#xff0c;在本文中我们将对这个版本进行如下的优化&#xff1a; 在新建线程时交给线程一个任务。让线程在某种情况下…...

实现p2p的webrtc-srs版本

1. 基本知识 1.1 webrtc 一、WebRTC的本质&#xff1a;实时通信的“网络协议栈”类比 将WebRTC类比为Linux网络协议栈极具洞察力&#xff0c;二者在架构设计和功能定位上高度相似&#xff1a; 分层协议栈架构 Linux网络协议栈&#xff1a;从底层物理层到应用层&#xff08;如…...