1.安装相关包
设置代理
set GOPROXY=https://goproxy.cn
安装 golang
的proto工具包go get -u github.com/golang/protobuf/proto
安装 goalng
的proto编译支持go get -u github.com/golang/protobuf/protoc-gen-go
查看 protoc 版本
protoc --version
2.创建项目
模拟微服务开发,在某个空文件夹下建立两个文件夹
分别为 client
和 server
3.创建 proto 文件
proto 文件是微服务交互的基本
proto的语法见官方:https://developers.google.com/protocol-buffers/docs/proto3?hl=zh-cn
这里简单写一个示例(people.proto)
syntax = "proto3"; // 协议为proto3 package model; // 包名 option go_package ="./model"; //protoc --go_out=./ ./people.proto // 定义消息, people 是类名 message people { string name = 1; int32 age = 2; }
4.生成 .bp.go
文件
protoc --go_out=./ ./people.proto
运行后会在spider目录下生成 people.pb.go
文件
该文件是 server 和 client 的通信协议,勿动
5.编写 server 端
编写 server.go
文件
package prob import ( "bufio" "fmt" "github.com/golang/protobuf/proto" "net" "os" "tes/prob/model" ) func Server() { //监听 listener, err := net.Listen("tcp", "localhost:6600") if err != nil { panic(err) } for { conn, err := listener.Accept() if err != nil { panic(err) } fmt.Println("new connect", conn.RemoteAddr()) go readMessage(conn) } } //接收消息 func readMessage(conn net.Conn) { defer conn.Close() reader :=bufio.NewReader(conn) //读消息 for { data,err := Decode(reader) if err ==nil && len(data)>0{ p := &model.People{} err := proto.Unmarshal(data, p) if err != nil { panic(err) } //buf= make([]byte, 10240,10240)//前两个字节表示本次请求body为长度 fmt.Printf("receive %s %+v \n", conn.RemoteAddr(), p) if p.Name == "stop" { os.Exit(1) } } if err!=nil{ panic(err) } } }
6.编写 client 端
编写 client.go
文件
package prob import ( "fmt" "github.com/golang/protobuf/proto" "net" "strconv" "tes/prob/model" "time" ) func Client() { strIP := "localhost:6600" var conn net.Conn var err error //连接服务器 if conn, err = net.Dial("tcp", strIP); err != nil { panic(err) } fmt.Println("connect", strIP, "success") defer conn.Close() //发送消息 //sender := bufio.NewScanner(os.Stdin) for i :=0; i<10000;i++ { stSend := &model.People{ Name: "lishang "+ strconv.Itoa(i), Age: *proto.Int(i), } //protobuf编码 pData, err := proto.Marshal(stSend) if err != nil { panic(err) } data,err := Encode(pData) if err==nil{ conn.Write(data) fmt.Printf("%d send %d \n" ,i,uint16(len(pData))) } /*if sender.Text() == "stop" { return }*/ } for{ time.Sleep(time.Millisecond*10) /*stSend := &model.People{ Name: "lishang "+ strconv.Itoa(22222222), Age: *proto.Int(22222222), } //protobuf编码 pData, err := proto.Marshal(stSend) if err != nil { panic(err) } b := make([]byte,2,2) binary.BigEndian.PutUint16(b,uint16(len(pData))) //发送 conn.Write(append(b,pData...))*/ } }
7.粘包处理
编写 protol.go
文件
package prob import ( "bufio" "bytes" "encoding/binary" ) //将数据包编码(即加上包头再转为二进制) func Encode(mes []byte) ([]byte, error) { //获取发送数据的长度,并转换为四个字节的长度,即int32 len := uint16(len(mes)) //创建数据包 dataPackage := new(bytes.Buffer) //使用字节缓冲区,一步步写入性能更高 //先向缓冲区写入包头 //大小端口诀:大端:尾端在高位,小端:尾端在低位 //编码用小端写入,解码也要从小端读取,要保持一致 err := binary.Write(dataPackage, binary.LittleEndian, len) //往存储空间小端写入数据 if err != nil { return nil, err } //写入消息 err = binary.Write(dataPackage, binary.LittleEndian, mes) if err != nil { return nil, err } return dataPackage.Bytes(), nil } //解码数据包 func Decode(reader *bufio.Reader) ([]byte, error) { //读取数据包的长度(从包头获取) lenByte, err := reader.Peek(2) //读取前四个字节的数据 if err!=nil{ return []byte{}, err } //转成Buffer对象,设置为从小端读取 buff := bytes.NewBuffer(lenByte) var len uint16 //读取的数据大小,初始化为0 err = binary.Read(buff, binary.LittleEndian, &len) //从小端读取 if err != nil { return []byte{}, err } //读取消息 pkg := make([]byte, int(len)+2) //Buffered返回缓冲区中现有的可读取的字节数 if reader.Buffered() < int(len)+2 { //如果读取的包头的数据大小和读取到的不符合 hr := 0 for hr < int(len)+2 { l, err := reader.Read(pkg[hr:]) if err != nil { return []byte{}, err } hr+=l } }else{ _, err := reader.Read(pkg) if err != nil { return []byte{}, err } } return pkg[2:], nil }
8.运行
编写 main.go
文件
package main import ( "fmt" "os" "os/signal" "syscall" "tes/prob" ) func main(){ go prob.Server() go prob.Client() c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) for { s := <-c fmt.Printf("job get a signal %s", s.String()) switch s { case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: fmt.Printf("job exit") return case syscall.SIGHUP: default: return } } }
我们运行后可看到结果已经正常返回并打印