文章目錄
-
- 緣由
- 定義協定
- .proto檔案
- Server示例代碼
- Client示例代碼
- 知識點
-
- bufio.Scan的使用
緣由
最近在研究一款遊戲的源碼。發現裡面的通信協定是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
}
}
}