go protobuf tcp 粘包处理 demo

4 篇文章 1 订阅

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


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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值