go protobuf tcp 粘包处理 demo

 

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
}
}
}


我们运行后可看到结果已经正常返回并打印

posted @ 2021-08-16 09:38  selfim写博客  阅读(550)  评论(0编辑  收藏  举报