声明 :本文部分内容使用AI辅助生成,经人工编辑、审核和补充个人经验。
更新说明 :技术栈版本信息基于 Go 1.22+ / Pitaya 2.x。
Go语言开发踩坑记录 用Go开发游戏服务器有一段时间了,从基础语法到Pitaya框架,记录一下踩过的坑和学习心得。
环境搭建 安装配置
VS Code配置
安装Go扩展
配置GOPROXY加速:
1 2 go env -w GOPROXY="https://goproxy.cn,direct" go env -w GO111MODULE=on
安装Go工具包:Ctrl+Shift+P → Go: Install/Update Tools
第一个程序 1 2 3 4 5 6 7 package mainimport "fmt" func main () { fmt.Println("你好,世界!" ) }
运行:
1 2 3 4 5 go run main.go go build -o hello main.go ./hello
基础语法踩坑 坑1:变量声明 Go的变量声明有几种方式:
1 2 3 4 5 6 7 8 9 10 11 12 var name string = "Go" var age int = 15 var version = 1.21 isAwesome := true var x, y, z int = 1 , 2 , 3
注意:=只能在函数内使用,包级别变量要用var。
坑2:字符串长度 Go字符串是UTF-8编码,len()返回的是字节数,不是字符数。
1 2 3 4 5 6 str := "Hello, Go语言!" fmt.Println(len (str)) runes := []rune (str) fmt.Println(len (runes))
切片和数组的区别
特性
数组
切片
长度
固定
可变
传递方式
值拷贝
引用传递
使用场景
固定大小数据
动态数据集合
1 2 3 4 5 6 7 8 9 10 var arr1 [5 ]int arr2 := [3 ]int {1 , 2 , 3 } slice1 := make ([]int , 0 , 5 ) slice2 := []int {1 , 2 , 3 } slice1 = append (slice1, 10 )
Map使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 scores := make (map [string ]int ) scores["Alice" ] = 95 if value, exists := scores["Charlie" ]; exists { fmt.Println("存在:" , value) } else { fmt.Println("不存在" ) } delete (scores, "Bob" )for name, age := range ages { fmt.Printf("%s: %d\n" , name, age) }
流程控制 Switch特点 Go的switch默认break,不需要显式声明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 switch day {case "Monday" , "Tuesday" , "Wednesday" , "Thursday" , "Friday" : fmt.Println("工作日" ) case "Saturday" , "Sunday" : fmt.Println("周末" ) default : fmt.Println("无效日期" ) } switch n {case 1 : fmt.Println("1" ) fallthrough case 2 : fmt.Println("2" ) }
for循环 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 for i := 0 ; i < 5 ; i++ { fmt.Println(i) } n := 0 for n < 5 { fmt.Println(n) n++ } for { } for index, value := range nums { fmt.Printf("索引: %d, 值: %d\n" , index, value) }
函数与错误处理 多返回值 1 2 3 4 5 6 7 8 9 10 func divide (a, b int ) (quotient, remainder int ) { quotient = a / b remainder = a % b return } q, r := divide(17 , 5 ) fmt.Printf("商: %d, 余数: %d\n" , q, r)
错误处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var ErrDivideByZero = errors.New("除数不能为零" )func safeDivide (a, b int ) (int , error ) { if b == 0 { return 0 , ErrDivideByZero } return a / b, nil } result, err := safeDivide(10 , 0 ) if err != nil { fmt.Println("错误:" , err) return }
Panic与Recover 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func mayPanic () { panic ("出现问题了!" ) } func main () { defer func () { if r := recover (); r != nil { fmt.Println("恢复自:" , r) } }() mayPanic() fmt.Println("这行不会执行" ) }
结构体与接口 结构体定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 type Person struct { Name string Age int Email string } type Rectangle struct { Width float64 Height float64 } func NewRectangle (width, height float64 ) *Rectangle { return &Rectangle{Width: width, Height: height} } func (r Rectangle) Area() float64 { return r.Width * r.Height } func (r *Rectangle) Scale(factor float64 ) { r.Width *= factor r.Height *= factor }
接口实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type Shape interface { Area() float64 Perimeter() float64 } type Circle struct { Radius float64 } func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius } func (c Circle) Perimeter() float64 { return 2 * 3.14159 * c.Radius } func PrintShapeInfo (s Shape) { fmt.Printf("面积: %.2f, 周长: %.2f\n" , s.Area(), s.Perimeter()) }
Go的接口是隐式实现,只要实现方法集就是该类型。空接口interface{}可表示任意类型。
并发编程 Goroutine 1 2 3 4 5 6 7 8 9 10 11 12 13 func sayHello () { for i := 0 ; i < 5 ; i++ { fmt.Println("Hello" ) time.Sleep(100 * time.Millisecond) } } func main () { go sayHello() time.Sleep(1 * time.Second) fmt.Println("主程序结束" ) }
Channel通信 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func producer (ch chan <- int ) { for i := 0 ; i < 5 ; i++ { ch <- i fmt.Println("生产:" , i) } close (ch) } func consumer (ch <-chan int ) { for num := range ch { fmt.Println("消费:" , num) } } func main () { ch := make (chan int , 3 ) go producer(ch) consumer(ch) }
Select多路复用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ch1 := make (chan string ) ch2 := make (chan string ) go func () { time.Sleep(1 * time.Second) ch1 <- "来自channel 1" }() go func () { time.Sleep(2 * time.Second) ch2 <- "来自channel 2" }() for i := 0 ; i < 2 ; i++ { select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) case <-time.After(3 * time.Second): fmt.Println("超时" ) } }
Sync包工具 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var wg sync.WaitGroupvar mu sync.Mutexcounter := 0 for i := 0 ; i < 100 ; i++ { wg.Add(1 ) go func () { defer wg.Done() mu.Lock() counter++ mu.Unlock() }() } wg.Wait() fmt.Println("计数器:" , counter)
Go Modules 基本命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 go mod init github.com/username/project go mod tidy go mod graph go mod download go get github.com/gin-gonic/gin@v1.8.0 go get -u ./...
go.mod示例:
1 2 3 4 5 6 7 8 module github.com/example/myproject go 1.21 require ( github.com/gin-gonic/gin v1.9.1 github.com/stretchr/testify v1.8.4 )
Pitaya游戏服务器 环境准备 1 2 3 4 5 6 7 8 9 wget https://github.com/etcd-io/etcd/releases/download/v3.5.9/etcd-v3.5.9-linux-amd64.tar.gz tar -xzf etcd-v3.5.9-linux-amd64.tar.gz ./etcd-v3.5.9-linux-amd64/etcd wget https://github.com/nats-io/nats-server/releases/download/v2.9.20/nats-server-v2.9.20-linux-amd64.tar.gz tar -xzf nats-server-v2.9.20-linux-amd64.tar.gz ./nats-server
坑3:依赖版本兼容 Pitaya对etcd和nats版本有要求,要用兼容的版本。另外注意go.mod里的replace指令,有时候需要替换某些依赖。
集群配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func clusterConfig () *config.PitayaConfig { conf := config.NewDefaultPitayaConfig() conf.Cluster.Info.Region = "cn-north-1" conf.Cluster.SD.Etcd.Servers = []string {"http://etcd1:2379" } conf.Cluster.SD.Etcd.DialTimeoutSec = 5 conf.Cluster.NATS.Connect = "nats://nats-server:4222" conf.Cluster.NATS.ConnectionTimeout = time.Duration(5 * time.Second) return conf }
单元测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func add (a, b int ) int { return a + b } func TestAdd (t *testing.T) { testCases := []struct { a, b, expected int }{ {1 , 2 , 3 }, {0 , 0 , 0 }, {-1 , 1 , 0 }, {100 , 200 , 300 }, } for _, tc := range testCases { result := add(tc.a, tc.b) if result != tc.expected { t.Errorf("add(%d, %d) = %d; expected %d" , tc.a, tc.b, result, tc.expected) } } } func BenchmarkAdd (b *testing.B) { for i := 0 ; i < b.N; i++ { add(100 , 200 ) } }
总结 Go语言开发几点心得:
字符串长度用[]rune转换后再取,避免中文问题
切片是引用类型,传递时要注意副作用
接口是隐式实现,不需要显式声明
Channel记得关闭,否则可能goroutine泄漏
Pitaya要注意etcd和nats版本兼容性
错误处理要习惯,Go没有try-catch
Go的并发模型确实很强大,但也要注意goroutine泄漏和死锁问题。多写测试用例,确保代码质量。