💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ## 缘由 最近在研究一款游戏的源码。发现里面的通信协议是protobuf定义的,但还是自己定义了协议头部,类似 head + body。先解析头部里所存储的整个包的数据长度,然后再解析包剩余的数据,这样做的目的是为了防止沾包。我在想都用了protobuf了为啥不直接用grpc呢。一时想不出,于是我用golnag + protobuf 做了一个简单的client + server ## 定义协议 一个包数据组成: * 包头 * size :表示body长度,int32,占位4字节 * cmd : 表示指令,int32,占位 4字节 * body:表示protobuf数据。 ## .proto文件 ``` // msg.proto syntax = "proto3"; option go_package = ".;GCToLs"; package GCToLS; enum MsgID { eMsgToLSFromGC_Unknow = 0; eMsgToLSFromGC_Begin = 40960; eMsgToLSFromGC_AskLogin = 40961; eMsgToLSFromGC_End = 40970; } message AskLogin { MsgID msgid = 1; uint32 platform = 2; string uin = 3; string sessionid = 4; } ``` 执行命令,生成 msg.pb.go文件 ``` protoc ./*.proto --go_out=protos -I ./ ``` ## Server示例代码 ``` package main import ( "bufio" "bytes" "encoding/binary" "fmt" "github.com/golang/protobuf/proto" pb "m1/protos" "net" ) func doServerStuf(conn net.Conn) { defer conn.Close() for { scanner := bufio.NewScanner(conn) scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { if !atEOF { if len(data) > 8 { //4字节数据包长度 4字节指令 length := int32(0) binary.Read(bytes.NewReader(data[0:4]),binary.BigEndian, &length) if length <= 0 { return 0, nil, fmt.Errorf("length is 0") } fmt.Printf("len_data %d; length: %d\n", len(data), length) if int(length) + 8 <= len(data) { return int(length), data[8:int(length) + 8], nil } } } return }) for scanner.Scan() { fmt.Println("scanner msg: ", string(scanner.Bytes())) msg := new(pb.AskLogin) err := proto.Unmarshal(scanner.Bytes(), msg) if err != nil { fmt.Printf("proto Unmarshal err: %s\n", err.Error()) return } fmt.Printf("msg.msgid: %d, msg.session: %s\n", msg.Msgid, msg.Sessionid) } if err := scanner.Err(); err != nil { fmt.Println("无数据包!") return } } } func main() { errs := make(chan error) l, err := net.Listen("tcp",":9091") if err != nil { errs <- err } fmt.Println("Accept...9001") go func() { conn ,err := l.Accept() if err != nil { errs <- err } go doServerStuf(conn) }() fmt.Errorf("%s\n", <- errs) } ``` ## Client示例代码 ``` package main import ( "bytes" "encoding/binary" "fmt" "github.com/golang/protobuf/proto" pb "m1/protos" "net" ) func main() { conn, err := net.Dial("tcp","127.0.0.1:9091") if err != nil { fmt.Errorf("%s\n", err.Error()) return } msg := &pb.AskLogin{ Msgid: 1, Platform: 1, Uin: "1", Sessionid: "1001", } msgbyte,err := proto.Marshal(msg) if err != nil { fmt.Errorf("msg Marshal error %s\n", err.Error()) return } buf := &bytes.Buffer{} var head []byte head = make([]byte, 8) binary.BigEndian.PutUint32(head[0:4], uint32(bytes.Count(msgbyte,nil) -1)) binary.BigEndian.PutUint32(head[4:8], uint32(pb.MsgID_eMsgToLSFromGC_AskLogin)) buf.Write(head[:8]) buf.Write(msgbyte) fmt.Printf("%v\n", string(buf.Bytes())) conn.Write(buf.Bytes()) defer conn.Close() } ``` ## 知识点 一、golang的net网路库的使用 二、golang的goroutine 三、golang的channel使用 四、bufio.Scan可以很好的分割一个数据包。 ### bufio.Scan的使用 ``` //生成一个 scanner scanner := bufio.NewScanner(conn) //自定义解包方法 scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {}) //获取解析出的body for bufio.Scan{ fmt.printf("%s\n", string(scanner.Bytes())); } ``` 包头是8字节,表示body长度的是 head[0:4]。表示指令的是head[4:8], 解析出body如下: ``` func doServerStuf(conn net.Conn) { defer conn.Close() for { scanner := bufio.NewScanner(conn) scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { if !atEOF { if len(data) > 8 { //4字节数据包长度 4字节指令 length := int32(0) binary.Read(bytes.NewReader(data[0:4]),binary.BigEndian, &length) if length <= 0 { return 0, nil, fmt.Errorf("length is 0") } fmt.Printf("len_data %d; length: %d\n", len(data), length) if int(length) + 8 <= len(data) { return int(length), data[8:int(length) + 8], nil } } } return }) for scanner.Scan() { fmt.Println("scanner msg: ", string(scanner.Bytes())) msg := new(pb.AskLogin) err := proto.Unmarshal(scanner.Bytes(), msg) if err != nil { fmt.Printf("proto Unmarshal err: %s\n", err.Error()) return } fmt.Printf("msg.msgid: %d, msg.session: %s\n", msg.Msgid, msg.Sessionid) } if err := scanner.Err(); err != nil { fmt.Println("无数据包!") return } } } ```