Languages
Effective Go II
Initialization Constants Go语言中的常量是不可变的。它们在编译时创建(即使在函数中定义为局部常量),并且只能是数字、字符(rune)、字符串或布尔值。由于编译时的限制,定义常量的表达式必须是编译时可计算的常量表达式。例如,1<<3 是常量表达式,而 math.Sin(math.Pi/4) 不是,因为对 math.Sin 的函数调用需要在运行时进行。 在Go中,使用 iota 枚举器来创建枚举常量。由于 iota 可以成为表达式的一部分,并且表达式可以隐式重复,因此很容易构建复杂的值集合。 type ByteSize float64 const ( _ = iota // 通过赋值给空标识符忽略第一个值 KB ByteSize = 1 << (10 * iota) MB GB TB PB EB ZB YB ) 能够为任何用户定义的类型(如 ByteSize)附加 String 这类方法,使得任意值可以自动格式化打印。虽然这种方法最常用于结构体,但对于标量类型(如浮点类型)也同样有用 func (b ByteSize) String() string { switch { case b >= YB: return fmt.Sprintf("%.2fYB", b/YB) case b >= ZB: return fmt.Sprintf("%.2fZB", b/ZB) case b >= EB: return fmt.Sprintf("%.2fEB", b/EB) case b >= PB: return fmt.Sprintf("%.2fPB", b/PB) case b >= TB: return fmt.Sprintf("%.2fTB", b/TB) case b >= GB: return fmt.Sprintf("%.2fGB", b/GB) case b >= MB: return fmt.Sprintf("%.2fMB", b/MB) case b >= KB: return fmt.Sprintf("%.2fKB", b/KB) } return fmt.Sprintf("%.2fB", b) } 表达式 YB 打印为 1.00YB,而 ByteSize(1e13) 打印为 9.09TB。 ...
Go gRPC 开发实战笔记
1. 核心流程概览 定义协议 (.proto): 描述服务接口和消息结构 生成代码 (protoc): 自动生成 Go 语言的接口代码 服务端实现 (Server): 实现生成的接口逻辑,并启动 gRPC Server 客户端调用 (Client): 建立连接并调用远程方法 2. 伪代码框架 你可以直接复制这个框架作为新服务的模板。 第一步:定义 Proto 文件 路径: protobuf/service_name.proto syntax = "proto3"; // 指定生成的 Go 代码包路径 option go_package = "path/to/genproto/service_name"; // 1. 定义服务接口 service MyService { // 定义方法:接收 Request,返回 Response rpc MyMethod(MyRequest) returns (MyResponse) {} } // 2. 定义请求消息 message MyRequest { string some_id = 1; int32 amount = 2; } // 3. 定义响应消息 message MyResponse { bool success = 1; string message = 2; Data data = 3; // 嵌套消息 } message Data { string result = 1; } 第二步:生成代码 (Makefile) 使用 protoc 工具生成代码。 ...
从汇编看世界-Go Interface
本章示例代码 运行环境 WSL Ubuntu 24.04 LTS 64位 // 文件名 main.go package main type Mather interface { Add(a, b int32) int32 Sub(a, b int64) int64 } type Adder struct{ id int32 } //go:noinline func (adder Adder) Add(a, b int32) int32 { return a + b } //go:noinline func (adder Adder) Sub(a, b int64) int64 { return a - b } func main() { m := Mather(Adder{id: 6754}) // This call just makes sure that the interface is actually used. // Without this call, the linker would see that the interface defined above // is in fact never used, and thus would optimize it out of the final // executable. m.Add(10, 32) } 从源码看 Interface 结构组成 iface iface 是在运行过程中用来表示 interface 的结构体, 如下 ...
Effective Go I
Formatting Go 语言自带格式化工具 # 格式化文件并输出到标准输出 gofmt main.go # 直接修改文件 gofmt -w main.go # 格式化整个目录 gofmt -w ./... Commentary Go 支持使用 // 单行注释 和 /* */ 多行注释 Names Package names Go 的包导入, 默认使用最后一层目录名作为名称, 即 import "a/b/c" //使用时用 c Getters Go 语言中不自带 getters/setters, 如需自定义, 建议 getters 直接以本体命名, 无需加 Get, 例如 owner := obj.Owner() if owner != user { obj.SetOwner(user) } Interface names 单方法接口命名 用方法名 + -er 后缀命名,表示执行动作的角色。 例如:Reader, Writer, Closer, Formatter 常用动作方法名 ...
初识汇编-Golang
伪汇编 Go 汇编是抽象中间层 Go 编译器输出的是一种抽象的、可移植的汇编形式 这种汇编不直接对应真实硬件指令 Go 汇编器将这个"伪汇编"转换为具体硬件的机器指令 设计目标:易于移植 主要优势:简化 Go 语言向新架构的移植 只需要为新硬件实现汇编器后端,无需重写整个编译器 这是 Go 能快速支持多种架构的关键设计 简单示例 代码示例 package main //go:noinline func add(a, b int32) (int32, bool) { return a + b, true } func main() { add(10, 32) } //go:noinline 即编译时采取无内联的形式, 有无内联的区别可以参照下图 编译示例 运行环境 WSL Ubuntu 24.04 LTS 64位 # -S 打印汇编指令信息 -N 关闭编译器优化 # 读者也可以不使用 -N 自行观察两种模式下编译的产物差异 w@LAPTOP-IDKREHQV:~/go-test$ go tool compile -S -N main.go # add 函数编译指令 main.add STEXT nosplit size=51 args=0x8 locals=0x10 funcid=0x0 align=0x0 0x0000 00000 (/home/w/go-test/main.go:3) TEXT main.add(SB), NOSPLIT|ABIInternal, $16-8 0x0000 00000 (/home/w/go-test/main.go:3) PUSHQ BP 0x0001 00001 (/home/w/go-test/main.go:3) MOVQ SP, BP 0x0004 00004 (/home/w/go-test/main.go:3) SUBQ $8, SP 0x0008 00008 (/home/w/go-test/main.go:3) FUNCDATA $0, gclocals·g5+hNtRBP6YXNjfog7aZjQ==(SB) 0x0008 00008 (/home/w/go-test/main.go:3) FUNCDATA $1, gclocals·g5+hNtRBP6YXNjfog7aZjQ==(SB) 0x0008 00008 (/home/w/go-test/main.go:3) FUNCDATA $5, main.add.arginfo1(SB) 0x0008 00008 (/home/w/go-test/main.go:3) MOVL AX, main.a+24(SP) 0x000c 00012 (/home/w/go-test/main.go:3) MOVL BX, main.b+28(SP) 0x0010 00016 (/home/w/go-test/main.go:3) MOVL $0, main.~r0+4(SP) 0x0018 00024 (/home/w/go-test/main.go:3) MOVB $0, main.~r1+3(SP) 0x001d 00029 (/home/w/go-test/main.go:4) ADDL BX, AX 0x001f 00031 (/home/w/go-test/main.go:4) MOVL AX, main.~r0+4(SP) 0x0023 00035 (/home/w/go-test/main.go:4) MOVB $1, main.~r1+3(SP) 0x0028 00040 (/home/w/go-test/main.go:4) MOVL $1, BX 0x002d 00045 (/home/w/go-test/main.go:4) ADDQ $8, SP 0x0031 00049 (/home/w/go-test/main.go:4) POPQ BP 0x0032 00050 (/home/w/go-test/main.go:4) RET # main 函数编译指令 main.main STEXT size=42 args=0x0 locals=0x10 funcid=0x0 align=0x0 0x0000 00000 (/home/w/go-test/main.go:7) TEXT main.main(SB), ABIInternal, $16-0 0x0000 00000 (/home/w/go-test/main.go:7) CMPQ SP, 16(R14) 0x0004 00004 (/home/w/go-test/main.go:7) PCDATA $0, $-2 0x0004 00004 (/home/w/go-test/main.go:7) JLS 35 0x0006 00006 (/home/w/go-test/main.go:7) PCDATA $0, $-1 0x0006 00006 (/home/w/go-test/main.go:7) PUSHQ BP 0x0007 00007 (/home/w/go-test/main.go:7) MOVQ SP, BP 0x000a 00010 (/home/w/go-test/main.go:7) SUBQ $8, SP 0x000e 00014 (/home/w/go-test/main.go:7) FUNCDATA $0, gclocals·g5+hNtRBP6YXNjfog7aZjQ==(SB) 0x000e 00014 (/home/w/go-test/main.go:7) FUNCDATA $1, gclocals·g5+hNtRBP6YXNjfog7aZjQ==(SB) 0x000e 00014 (/home/w/go-test/main.go:7) MOVL $10, AX 0x0013 00019 (/home/w/go-test/main.go:7) MOVL $32, BX 0x0018 00024 (/home/w/go-test/main.go:7) PCDATA $1, $0 0x0018 00024 (/home/w/go-test/main.go:7) CALL main.add(SB) 0x001d 00029 (/home/w/go-test/main.go:7) ADDQ $8, SP 0x0021 00033 (/home/w/go-test/main.go:7) POPQ BP 0x0022 00034 (/home/w/go-test/main.go:7) RET 0x0023 00035 (/home/w/go-test/main.go:7) NOP 0x0023 00035 (/home/w/go-test/main.go:7) PCDATA $1, $-1 0x0023 00035 (/home/w/go-test/main.go:7) PCDATA $0, $-2 0x0023 00035 (/home/w/go-test/main.go:7) CALL runtime.morestack_noctxt(SB) 0x0028 00040 (/home/w/go-test/main.go:7) PCDATA $0, $-1 0x0028 00040 (/home/w/go-test/main.go:7) JMP 0 ..... add 函数编译解析 标头字段解释 TEXT main.add(SB), NOSPLIT|ABIInternal, $16-8 main: 代表包名, 并不是 main 函数 NOSPLIT: 表示不用进行栈溢出检测, 通常存在于没有本地变量的函数, 无需额外栈帧分配 $16-8: 16 表示分配 16 字节大小的栈帧(之后会解释 16 从哪里来), 8 表示参数大小位 8 字节(两个 int32) golang 栈特性 在 golang 中栈的分配大多数情况下不通过 PUSH/POP, 而是通过改变 SP 寄存器的指针指向,即: ...
Gin Authorization
Gin Middleware
基本用法示例 func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() // 设置 example 变量 c.Set("example", "12345") // 请求前 c.Next() // 请求后 latency := time.Since(t) log.Print(latency) // 获取发送的 status status := c.Writer.Status() log.Println(status) } } func main() { r := gin.New() r.Use(Logger()) r.GET("/test", func(c *gin.Context) { example := c.MustGet("example").(string) // 打印:"12345" log.Println(example) }) // 监听并在 0.0.0.0:8080 上启动服务 r.Run(":8080") } 常用函数(Gin Context) 本节中 c 均指 c *gin.Context ...
Gin QuickStart
基本用法示例 package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { router := gin.Default() router.GET("/ping", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) router.Run() } 一般情况下 Gin 和 go 的 HTTP 模块配合使用, Gin 负责基本框架, go 的 HTTP 模块负责状态码管理等 默认路由为 router := gin.Default 启动命令为 router.Run(), 默认监听 8000 端口 自定义配置 // 不指定 Gin 的绑定地址和端口。默认绑定所有接口,端口为 8080。 // 使用不带参数的 `Run()` 时,可通过 `PORT` 环境变量更改监听端口。 router := gin.Default() router.Run() // 指定 Gin 的绑定地址和端口。 router := gin.Default() router.Run("192.168.1.100:8080") // 仅指定监听端口。将绑定所有接口。 router := gin.Default() router.Run(":8080") // 设置哪些 IP 地址或 CIDR 被视为可信任的代理,用于设置记录真实客户端 IP 的请求头。 // 更多详情请参阅文档。 router := gin.Default() router.SetTrustedProxies([]string{"192.168.1.2"}) 基本路由规则 func main() { // 禁用控制台颜色 // gin.DisableConsoleColor() // 使用默认中间件(logger 和 recovery 中间件)创建 gin 路由 router := gin.Default() router.GET("/someGet", getting) router.POST("/somePost", posting) router.PUT("/somePut", putting) router.DELETE("/someDelete", deleting) router.PATCH("/somePatch", patching) router.HEAD("/someHead", head) router.OPTIONS("/someOptions", options) // 默认在 8080 端口启动服务,除非定义了一个 PORT 的环境变量。 router.Run() // router.Run(":3000") hardcode 端口号 } 重定向 HTTP 重定向(外部 / 内部 URL) ...