Protobuf | 沾包问题
缘由
最近在研究一款游戏的源码。发现里面的通信协议是protobuf定义的,但还是自己定义了协议头部,类似 head + body。先解析头部里所存储的整个包的数据长度,然后再解析包剩余的数据,这样做的目的是为了防止沾包。我在想都用了protobuf了为啥不直接用grpc呢。一时想不出,于是我用golnag + protobuf 做了一个简单的client + server
定义协议
一个包数据组成:
- 包头
- size :表示body长度,int32,占位4字节
- cmd : 表示指令,int32,占位 4字节
.proto文件
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示例代码
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示例代码
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如下:
golang
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
}
}
}
--完--
- 原文作者: 留白
- 原文链接: https://zfunnily.github.io/2020/12/tcp/
- 更新时间:2024-04-16 01:01:05
- 本文声明:转载请标记原文作者及链接