7days-golang学习笔记
| 2024-4-9
0  |  阅读时长 0 分钟
日期
May 30, 2023 → Feb 22, 2024
Tags
笔记
golang
后端

7天用Go从零实现Web框架Gee教程系列

首先贴一下教程本体,教程源码库🥳,本笔记主要记录了跟着教程构建项目过程的思路梳理+遇到的一些知识点补充,不够完备,仅供存档参考
7days-golang
geektutuUpdated Apr 20, 2024

DAY1

标准库启动Web服务
day1-http-base/base1/main.go
运行后,使用curl指令终端的输出结果:
  1. powershell
  • 原因:在powershell中curl 命令被映射为 Invoke-WebRequest cmdlet 的别名,需要改用curl.exe来运行
curl输出
curl.exe输出(与下同)
 
  1. Cmd
  1. 使用浏览器打开看到的内容

DAY2

  • context:
    • 必要性:
      • 封装*http.Requesthttp.ResponseWriter的方法,简化相关接口的调用
      • 用于存储动态路由解析的内容,若有中间件用于存储中间件的相关信息
  • 静态路由的实现:
    • 数据结构:map键值对存储

DAY3

  • 动态路由的实现:
    • 数据结构:前缀树(Trie树)
  • 路由:
    • 路由注册:开发服务时,注册路由规则,映射handler
    • 路由访问:访问时,匹配路由规则,查找到对应的handler

Golang 单元测试

  • 需要用到package为testing
  • 文件命名为XXX_test.go
  • 代码示例:
    • 运行单元测试go test -v

    reflect包

    • 提供了Go语言中的反射功能,定义了大量的方法用于获取类型信息,设置值等
    • 常用方法
      • reflect.TypeOf():获取对象类型
      • reflect.ValueOf():获取反射值对象,返回值为reflect.Value类型
      • reflect.Value.NumField():从结构体的反射值对象中获取它的字段个数;
      • reflect.Value.Field(i):从结构体的反射值对象中获取第i个字段的反射值对象;
      • reflect.Kind():从反射值对象中获取种类;
      • reflect.Int()/reflect.Uint()/reflect.String()/reflect.Bool():这些方法从反射值对象做取出具体类型。

    DAY4

    • 实现路由分组控制(Route Group Control)
      • 分组控制(Group Control)是 Web 框架应提供的基础功能之一
      • 大部分情况下的路由分组,是以相同的前缀来区分的
      • 分组控制的作用:
        • 直接对分组应用中间件,不需要针对单个路由进行控制

    Go的嵌套类型

    • 在Go语言中,嵌套类型是将已有的类型直接声明在新的结构类型里。被嵌入的类型被称为新的外部类型的内部类型。
    • 通过嵌入类型,与内部类型相关的成员变量会提升到外部类型上。就好像这些成员变量直接声明在外部类型一样

    DAY5

    • 中间件(middlewares),简单说,就是非业务的技术类组件。
      • 其实从广义来说操作系统上,业务系统下与业务无关的,都是中间件,包括数据库,离线等。
        • 路由服务:Tengine、VipServer
        • 消息中间件:Apache kafka,Apache RocketMQ等
        • RPC框架:Dubbo
        • 缓存服务:Redis
      • 为Web框架提供插口,允许用户自己定义功能,嵌入到框架中,仿佛这个功能是框架原生支持的一样
      • 对中间件而言,需要考虑2个比较关键的点:
      • 插入点在哪?使用框架的人并不关心底层逻辑的具体实现,如果插入点太底层,中间件逻辑就会非常复杂。如果插入点离用户太近,那和用户直接定义一组函数,每次在 Handler 中手工调用没有多大的优势了。
      • 中间件的输入是什么?中间件的输入,决定了扩展能力。暴露的参数太少,用户发挥空间有限。
      • ⇒ 中间件设计:Gee 的中间件的定义与路由映射的 Handler 一致,处理的输入是Context对象。插入点是框架接收到请求初始化Context对象后,允许用户使用自己定义的中间件做一些额外的处理,例如记录日志等,以及对Context进行二次加工。
         

    DAY6

    • 服务端渲染,Web框架要支持 JS、CSS 等静态文件
      • 如何做?解析请求的地址,映射到服务器上文件的真实地址,交给http.FileServer处理

    DAY7

    Go中的panic:

    • 当出现无法恢复的错误,可以手动触发 panic
    • panic 会导致程序被中止,但当defer遇到panic的时候,defer会先执行完
      • defer是go中一种延迟调用机制
      • 当函数遇到panic,defer仍然会被执行。Go会先执行所有的defer链表(该函数的所有defer),当所有defer被执行完毕且没有recover时,才会进行panic。
      • ⇒ 基于此特性,defer的作用如下:
        1. 可以用于资源的关闭,避免异常情况的发生
        1. 在defer中进行recover,实现类似于try…catch的效果

    runtime包

    • runtime.Callers(3, pcs[:]),Callers 用来返回调用栈的程序计数器
    • 通过 runtime.FuncForPC(pc) 可以获取pc对应的函数,在通过 fn.FileLine(pc) 获取到调用该函数的文件名和行号
     

    7天用Go从零实现分布式缓存GeeCache

    分布式缓存:例如Redis、memcached等
    • 设计一个分布式缓存系统,需要考虑资源控制、淘汰策略、并发、分布式节点通信等各个方面的问题

    Day1

    • 缓存淘汰算法:
      • FIFO(仅考虑时间因素):
        • 很多场景下,部分记录虽然是最早添加但也最常被访问,而不得不因为呆的时间太长而被淘汰,存在瓶颈
      • LFU(仅考虑效率因素):
        • 维护每个记录的访问次数,对内存的消耗高
        • 受历史数据的影响比较大,若某个数据历史上访问次数奇高,但在某个时间点之后几乎不再被访问,但因为历史访问次数过高,而迟迟不能被淘汰。
      • LRU:相对平衡的一种淘汰算法
        • ⇒ LRU的实现:键值对+双向链表

    Day2

    互斥锁

    多个协程(goroutine)同时读写同一个变量,在并发度较高的情况下,会发生冲突。
    ⇒ 需要互斥锁去控制访问
    sync.Mutex 是 Go 语言标准库提供的一个互斥锁,当一个协程(goroutine)获得了这个锁的拥有权后,其它请求锁的协程(goroutine) 就会阻塞在 Lock() 方法的调用上,直到调用 Unlock() 锁被释放

    接口型函数

    • 实现了一个接口的函数类型
    优点:既能够将普通的函数类型(需类型转换)作为参数,也可以将结构体作为参数,使用更为灵活,可读性也更好,这就是接口型函数的价值

    Day3

    为分布式缓存建立基于 HTTP 的通信机制
    • 服务端
    • 客户端

    Day4

    单节点缓存⇒分布式缓存

    如何访问分布式缓存节点

    期望:不同节点中存储的缓存内容尽可能不同,用户多次访问同一个数据的时候尽可能选择同一个节点
    ⇒ 对于给定的 key,每一次都选择同一个节点
    ⇒ 哈希:如何处理节点数量变化情况?
    ⇒ 一致性哈希算法

    一致性哈希:

    一致性哈希算法将 key 映射到 2^32 的空间中,将这个数字首尾相连,形成一个环。
    notion image
    • 计算节点/机器(通常使用节点的名称、编号和 IP 地址)的哈希值,放置在环上。
    • 计算 key 的哈希值,放置在环上,顺时针寻找到的第一个节点,就是应选取的节点/机器。
    节点的增加和删除
    一致性哈希算法,在新增/删除节点时,只需要重新定位该节点附近的一小部分数据,而不需要重新定位所有的节点
    数据倾斜问题
    如果服务器的节点过少,容易引起 key 的倾斜,为了解决这个问题,可以引入虚拟节点的概念,一个真实节点对应多个虚拟节点。
    • 该方法扩充了节点数量,且开销小,只需要增加一个字典(map)维护真实节点与虚拟节点的映射关系

    Day5

    • 添加HTTP客户端⇒实现分布式节点
    • 分布式缓存的工作流程
      • notion image
    • 代码逻辑梳理:
      • 可以把group看成一个控制中心,maincache是本地缓存(缓存节点的实体,真正存放缓存的地方),getter是用户访问缓存的入口,peers的实际类型为HTTPPool,可以看成是一个缓存节点池(并没有真正的缓存节点),管理着对远程节点的访问,包装了访问缓存节点的一致性哈希算法。
      • run.sh脚本一共创建了3个group,并且启动了三个缓存节点服务(8001,8002,8003),启动了一个api服务(9999),其中api服务(9999)和缓存节点服务(8003)是通过同一个group进行管理的,在创建每个group的时候,远程节点的addr信息写入了每个group的HTTPPool中。
      • 用户访问api的过程,首先访问该group中的maincache,若没有命中,再通过HTTPPool中的一致性哈希算法选择远程节点或本地节点,返回缓存值

    Day6

    缓存雪崩、缓存击穿和缓存穿透

    缓存雪崩:缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。缓存雪崩通常因为缓存服务器宕机、缓存的 key 设置了相同的过期时间等引起。
    缓存击穿:一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到 DB ,造成瞬时DB请求量大、压力骤增。
    缓存穿透:查询一个不存在的数据,因为不存在则不会写到缓存中,所以每次都会去请求 DB,如果瞬间流量过大,穿透到 DB,导致宕机。
    • 对于相同的 key,如何做到只向远端节点发起一次请求
      • 利用加锁的方法控制并发访问,在geecache中的实现是有多个相同key的请求同时访问时,阻塞第一个请求之后的所有请求,待第一个缓存读取结束后,将结果存入map,并释放阻塞的goroutine,剩余请求直接读取map结果

    Day7

    protolbuf(Protocol Buffers

    protobuf 即 Protocol Buffers,是一种轻便高效的结构化数据存储格式,与语言、平台无关,可扩展可序列化。
    protobuf 广泛地应用于远程过程调用(RPC) 的二进制传输,使用 protobuf 的目的非常简单,为了获得更高的性能。传输前使用 protobuf 编码,接收方再进行解码,可以显著地降低二进制传输的大小。另外一方面,protobuf 可非常适合传输结构化数据,便于通信字段的扩展。

    7天用Go从零实现ORM框架GeeORM

    什么是ORM?
    对象关系映射(Object Relational Mapping,简称ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。
    ORM框架相当于对象和数据库中间的桥梁,通过操作具体的对象完成对关系型数据库的操作。
    设计ORM框架需要关注的问题:
    1)MySQL,PostgreSQL,SQLite 等数据库的 SQL 语句是有区别的,ORM 框架如何在开发者不感知的情况下适配多种数据库?
    2)如何对象的字段发生改变,数据库表结构能够自动更新,即是否支持数据库自动迁移(migrate)?
    3)数据库支持的功能很多,例如事务(transaction),ORM 框架能实现哪些?
    4)…

    Day1

    • SQLite了解:
      • SQLite 是一款轻量级的,遵守 ACID 事务原则的关系型数据库。
    • "database/sql"标准库可以对数据库进行访问
    • VT100控制码:以\033打头
      • notion image
    • 封装sql中原生方法的目的:
        1. 统一打印日志
        1. 执行完成后,清空 (s *Session).sql 和 (s *Session).sqlVars 两个变量,实现Session的复用

    Day2

    如何将 Go 语言的类型映射为数据库中的类型?
    1. eg:Go 语言中的 intint8int16 等类型均对应 SQLite 中的 integer 类型
    1. 不同数据库支持的数据类型之间有差异
    • Dialect:抽象出各个数据库差异的部分,实现最大程度的复用和解耦,使得ORM 框架兼容多种数据库
      • 映射数据类型和特定的SQL语句
      • 屏蔽数据库差异
    • Schema:利用反射(reflect)完成结构体和数据库表结构的映射,包括表名、字段名、字段类型、字段 tag 等。

    Day3

    • 重点:使用反射(reflect)将数据库的记录转换为对应的结构体实例

    Day4

    • 链式调用:链式调用是一种简化代码的编程方式,能够使代码更简洁、易读。
      • 链式调用的原理也非常简单,某个对象调用某个方法后,将该对象的引用/指针返回,即可以继续调用该对象的其他方法。

    Day5

    • Hook机制:翻译为钩子,其主要思想是提前在可能增加功能的地方埋好(预设)一个钩子,当我们需要重新修改或者增加这个地方的逻辑的时候,把扩展的类或者方法挂载到这个点即可。
      • 对于ORM框架,合适的扩展点在记录的增删差改前后都非常合适
      • 钩子机制同样是通过反射来实现的

    Day6

    • 事务(transaction):数据库事务(transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。
    • 事务的ACID属性
      • 原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部完成,要么全部不执行。
      • 一致性(Consistency): 几个并行执行的事务,其执行结果必须与按某一顺序 串行执行的结果相一致。
      • 隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。
      • 持久性(Durability):对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障。

    Day7

    • 实现字段新增与删除
      • difference 用来计算前后两个字段切片的差集。新表 - 旧表 = 新增字段,旧表 - 新表 = 删除字段。
      • 使用 ALTER 语句新增字段。
      • 使用创建新表并重命名的方式删除字段。

    7天用Go从零实现RPC框架GeeRPC

    通信:
    • RPC:RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,允许调用不同进程空间的程序。RPC 的客户端和服务器可以在一台机器上,也可以在不同的机器上。程序员使用时,就像调用本地程序一样,无需关注内部的实现细节。
    • Restful API:浏览器和服务器之间广泛使用的基于 HTTP 协议的 Restful API。与 RPC 相比,Restful API 有相对统一的标准,因而更通用,兼容性更好,支持不同的语言。
    • RPC与Resful API的比较:
      • Restful 接口需要额外的定义,无论是客户端还是服务端,都需要额外的代码来处理,而 RPC 调用则更接近于直接调用。
      • 基于 HTTP 协议的 Restful 报文冗余,承载了过多的无效信息,而 RPC 通常使用自定义的协议格式,减少冗余报文。
      • RPC 可以采用更高效的序列化协议,将文本转为二进制传输,获得更高的性能。
      • 因为 RPC 的灵活性,所以更容易扩展和集成诸如注册中心(registry)负载均衡(load balance)等功能。
        • 注册中心:客户端和服务端互相不感知对方的存在,服务端启动时将自己注册到注册中心,客户端调用时,从注册中心获取到所有可用的实例,选择一个来调用。这样服务端和客户端只需要感知注册中心的存在就够了。注册中心通常还需要实现服务动态添加、删除,使用心跳确保服务处于可用状态等功能。
    RPC框架需要解决什么问题?
    1. 解决传输协议和报文编码的问题,在进行两个应用程序之间的通信前,需要确定采用的传输协议和报文编码格式,需要考虑不同的情况和需求。
    1. 实现注册中心,通过注册中心统一管理,服务断启动时把自己注册到注册中心,客户端调用时,从注册中心获取到所有可用的实例。
      1. 注册中心的功能:
          • 消息传递
          • 实现服务动态添加、删除
          • 使用心跳确保服务处于可用状态
    1. 统一的RPC框架,可以避免团队服务提供方实现消息编解码等“业务之外”的重复技术扰动,提供公共能力

    Day1

    1. 什么是gob?(encoding/gob包)
        • gob包管理gob流——编码器(发送器)和解码器(接收器)之间交换的二进制值。典型的用法是传输远程过程调用(rpc)的参数和结果,例如包“net/rpc”提供的那些。
        • 该实现为流中的每种数据类型编译自定义编解码器,并且当使用单个编码器传输值流时效率最高,从而分摊编译成本。
    1. Codec:对消息体进行编解码的接口
      1. 实例有GobCodecJsonCodec
    1. 通信过程:客户端与服务端的通信需要协商一些内容,如消息编解码方式、报文的内容、格式、长度等信息
      1. 消息通常分为head、body两部分,如HTTP报文,对于RPC协议可以自主设计
      2. 为了提升性能,报文一般会在报文的开头部分规划固定的字节,用于协商相关的信息,如定义压缩方法、序列化方式、header长度、body长度等
      3. geerpc只需要协商消息编解码方式,这部分信息由结构体 Option 承载。
    1. 报文的设计:
      1. GeeRPC 客户端固定采用 JSON 编码 Option,后续的 header 和 body 的编码方式由 Option 中的 CodeType 指定
    1. serveCodec 的过程非常简单。主要包含三个阶段
        • 读取请求 readRequest
        • 处理请求 handleRequest
        • 回复请求 sendResponse
        需要注意的内容:
      1. handleRequest 使用了协程并发执行请求。
      2. 处理请求是并发的,但是回复请求的报文必须是逐个发送的,并发容易导致多个回复报文交织在一起,客户端无法解析。在这里使用锁(sending)保证。
      3. 使用waitGroup来防止服务处理过程中,程序的提前退出
        1. 每个handleRequest执行结束,调用一次waitGroup.done()

    Day2

    1. 实现一个支持异步和并发的高性能客户端,,客户端(client)的最重要功能为:
        • 接收响应(三种不同的情况)
          • call 不存在,可能是请求没有发送完整,或者因为其他原因被取消,但是服务端仍旧处理了
          • call 存在,但服务端处理出错,即 h.Error 不为空
          • call 存在,服务端处理正常,那么需要从 body 中读取 Reply 的值
        • 发送请求
    1. 什么是Call?表示一次RPC调用
        • 在rpc中,一个能够被远程调用的函数需要满足五个条件
          • the method’s type is exported. – 方法所属类型是导出的。
          • the method is exported. – 方式是导出的。
          • the method has two arguments, both exported (or builtin) types. – 两个入参,均为导出或内置类型。
          • the method’s second argument is a pointer. – 第二个入参必须是一个指针。
          • the method has return type error. – 返回值为 error 类型。
        • Call就是根据上述要求封装的结构体,用于承载RPC调用的所有信息
        • Call支持异步调用
          • Call 结构体中添加了一个字段 Done,Done 的类型是 chan *Call,当调用结束时,会调用 call.done() 通知调用方。
          • go 里面是通过 channel (也就是 chan 类型)来进行通信,一个协程往 chan 里面写数据,其他协程可以从 chan 中读取数据
             
    1. channel类型
    notion image
    1. 实现client
      1. 实现与Call相关的方法
        1. 注册/移除Call
        2. 通知错误信息
      2. 创建连接
        1. 明确好编解码方式
        2. 明确好服务端地址
      3. 接收响应
      4. 发送请求

    Day3

    1. RPC框架的基础功能:像调用本地程序一样调用远程服务
        • 需要将程序映射为服务
        • how?
            • 在rpc中,一个能够被远程调用的函数需要满足五个条件
              • the method’s type is exported. – 方法所属类型是导出的。
              • the method is exported. – 方式是导出的。
              • the method has two arguments, both exported (or builtin) types. – 两个入参,均为导出或内置类型。
              • the method’s second argument is a pointer. – 第二个入参必须是一个指针。
              • the method has return type error. – 返回值为 error 类型。
        • 客户端发送的请求中会携带ServiceMethod(服务方法)和Argv(参数),这些都需要通过解析请求,然后调用对应的服务,通过反射可以很容易地自动化映射的过程,获取某个结构体的所有方法、参数类型和返回值
    1. 通过反射实现结构体与service的映射关系
        • 定义methodType结构体,包含了一个方法的完整信息
        • 实现方法,用于创建参数
    1. sync/atomic包:提供了5种类型的原子操作和1个Value类型,在偏底层的应用场景里才使用
        • 使用sync/atomic提供的原子操作可以确保在任意时刻只有一个goroutine对变量进行操作,避免并发冲突。

    Day4

    1. 超时处理:在RPC框架中,超时处理可以避免客户端or服务端由于网络或错误导致的挂死、资源耗尽,能够提升服务的可用性。
    1. RPC过程中需要处理超时的地方:
        • 客户端
          • 与服务端建立连接,导致的超时
          • 发送请求到服务端,写报文导致的超时
          • 等待服务端处理时,等待处理导致的超时(比如服务端已挂死,迟迟不响应)
          • 从服务端接收响应时,读报文导致的超时
        • 服务端
          • 读取客户端请求报文时,读报文导致的超时
          • 发送响应报文时,写报文导致的超时
          • 调用映射服务的方法时,处理报文导致的超时
    1. 无缓冲区channel
      1. 用make(chan int) 创建的chan, 是无缓冲区的, send 数据到chan 时,在没有协程取出数据的情况下, 会阻塞当前协程的运行。ch <- 后面的代码就不会再运行,直到channel 的数据被接收,当前协程才会继续往下执行。

    Day5

    1. 支持HTTP协议:RPC 的消息格式与标准的 HTTP 协议并不兼容,因此需要对协议进行转换,HTTP 协议的 CONNECT 方法恰好提供了这个能力,CONNECT 一般用于代理服务
      1. 实现逻辑:
        1. 浏览器通过 HTTP 明文形式向代理服务器发送一个 CONNECT 请求告诉代理服务器目标地址和端口
        2. 代理服务器接收到这个请求后,会在对应端口与目标站点建立一个 TCP 连接,连接建立成功后返回 HTTP 200 状态码告诉浏览器与该站点的加密通道已经完成
        3. 接下来代理服务器仅需透传浏览器和服务器之间的加密数据包即可,代理服务器无需解析 HTTPS 报文
    1. 服务端支持HTTP协议;
      1. 通信过程
        1. 客户端向 RPC 服务器发送 CONNECT 请求
        2. RPC 服务器返回 HTTP 200 状态码表示连接建立。
        3. 客户端使用创建好的连接发送 RPC 报文,先发送 Option,再发送 N 个请求报文,服务端处理 RPC 请求并响应
    1. 客户端支持HTTP协议
      1. 通信过程:
        1. 发起 CONNECT 请求
        2. 检查从服务端返回状态码,即可成功建立连接。
    1. runtime包:Go 语言的 goroutine 是由 运行时(runtime)调度和管理的,Go 语言的 runtime 类似 Java 和 .NET 语言所用到的虚拟机,它负责管理包括内存分配、垃圾回收(第 10.8 节)、栈处理、goroutine、channel、切片(slice)、map 和反射(reflection)等等
    1. 支持 HTTP 协议的好处:
      1. RPC 服务仅仅使用了监听端口的 /_geerpc 路径,在其他路径上我们可以提供诸如日志、统计等更为丰富的功能。

    Day6

    1. 负载均衡:存在多个服务实例时,客户端可以选择任意一个实例进行调度,选择的策略有很多,负载均衡的策略可以提高系统的吞吐量,确保服务的响应速度、提高资源利用率
      1. 负载均衡的策略:
        • 随机选择策略 - 从服务列表中随机选择一个。
        • 轮询算法(Round Robin) - 依次调度不同的服务器,每次调度执行 i = (i + 1) mode n。
        • 加权轮询(Weight Round Robin) - 在轮询算法的基础上,为每个服务实例设置一个权重,高性能的机器赋予更高的权重,也可以根据服务实例的当前的负载情况做动态的调整,例如考虑最近5分钟部署服务器的 CPU、内存消耗情况。
        • 哈希/一致性哈希策略 - 依据请求的某些特征,计算一个 hash 值,根据 hash 值将请求发送到对应的机器。一致性 hash 还可以解决服务实例动态添加情况下,调度抖动的问题。一致性哈希的一个典型应用场景是分布式缓存服务。
    1. 服务发现:
      1. 与通信部分解耦
      2. SelectMode:代表不同的负载均衡策略
      3. Discovery:接口类型,包含了服务发现所需要的最基本的接口
    1. Broadcast:将请求广播到所有的服务实例,如果任意一个实例发生错误,则返回其中一个错误;如果调用成功,则返回其中一个的结果。

    Day7

    1. 注册中心:主流的注册中心有 etcd、zookeeper 等,功能强大,GeeRPC 实现一个简单的支持心跳保活的注册中心。
      1. 注册中心的基础功能:
        1. 添加服务实例
        2. 返回可用的服务列表
    1. 什么是心跳:通过心跳检测请求可以维持连接(避免连接因长时间不活跃而被网关防火墙关闭),也能让服务端比较及时的知道客户端是否异常掉线。
      1. 客户端定时每X秒(推荐小于60秒)向服务端发送特定数据(任意数据都可)
      2. 服务端设定为X秒没有收到客户端心跳则认为客户端掉线,并关闭连接触发onClose回调。
      3. 为什么需要心跳?
        1. 正常的情况客户端断开连接会向服务端发送一个fin包,服务端收到fin包后得知客户端连接断开,则立刻触发onClose事件回调。
        2. 但是有些极端情况如客户端掉电、网络关闭、拔网线、路由故障等,这些极端情况客户端无法发送fin包给服务端,服务端便无法知道连接已经断开。如果客户端与服务端定时有心跳数据传输,则会比较及时的发现连接断开,触发onClose事件回调。
     
    走走停停,完成整个7days的教程历时大半年【 May 30, 2023-Feb 22, 2024 】,真的很感谢这个教程。最后Go相关的学习也算是告一段落了,下面是个人觉得比较好的Go学习资料
    Loading...
    目录