• 消息包处理
    • 简单协议
    • 操作方法
    • 使用示例1,基本使用
    • 使用示例2,自定义数据结构

    消息包处理

    gtcp提供了许多方便的原生操作连接数据的方法,但是在绝大多数的应用场景中,开发者需要自己设计数据结构,并进行封包/解包处理,由于TCP消息协议是没有消息边界保护的,因此复杂的网络通信环境中很容易出现粘包/断包的情况。因此gtcp也提供了简单的数据协议,方便开发者进行消息包交互,开发者不再需要担心消息包的处理细节,包括封包/解包处理,这一切复杂的逻辑gtcp已经帮你处理好了。

    简单协议

    gtcp模块提供了简单轻量级数据交互协议,效率非常高,协议格式如下:

    1. 数据长度(16bit)|数据字段(变长)
    1. 数据长度:默认为16位(2字节),用于标识该消息体的数据长度,单位为字节,不包含自身的2字节;
    2. 数据字段:变长,根据数据长度可以知道,数据最大长度不能超过0xFFFF = 65535 bytes = 64 KB

    简单协议由gtcp封装实现,如果开发者客户端和服务端如果都使用gtcp模块来通信则无需关心协议实现,专注数据字段封装/解析实现即可。如果涉及和其他开发语言对接,则需要按照该协议实现对接即可,由于简单协议非常简单轻量级,因此对接成本很低。

    数据字段也可以为空,即没有任何长度。

    操作方法

    https://godoc.org/github.com/gogf/gf/g/net/gtcp

    1. type Conn
    2. func (c *Conn) SendPkg(data []byte, option ...PkgOption) error
    3. func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) error
    4. func (c *Conn) SendRecvPkg(data []byte, option ...PkgOption) ([]byte, error)
    5. func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) ([]byte, error)
    6. func (c *Conn) RecvPkg(option ...PkgOption) (result []byte, err error)
    7. func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) ([]byte, error)

    可以看到,消息包方法命名是在原有的基本连接操作方法中加上了Pkg关键词便于区分。

    其中,请求参数中的PkgOption数据结构如下,用于定义消息包接收策略:

    1. // 数据读取选项
    2. type PkgOption struct {
    3. HeaderSize int // 自定义头大小(默认为2字节,最大不能超过4字节)
    4. MaxDataSize int // (byte)数据读取的最大包大小,默认最大不能超过2字节(65535 byte)
    5. Retry Retry // 失败重试策略
    6. }

    使用示例1,基本使用

    1. package main
    2. import (
    3. "fmt"
    4. "github.com/gogf/gf/g/net/gtcp"
    5. "github.com/gogf/gf/g/os/glog"
    6. "github.com/gogf/gf/g/util/gconv"
    7. "time"
    8. )
    9. func main() {
    10. // Server
    11. go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) {
    12. defer conn.Close()
    13. for {
    14. data, err := conn.RecvPkg()
    15. if err != nil {
    16. fmt.Println(err)
    17. break
    18. }
    19. fmt.Println("receive:", data)
    20. }
    21. }).Run()
    22. time.Sleep(time.Second)
    23. // Client
    24. conn, err := gtcp.NewConn("127.0.0.1:8999")
    25. if err != nil {
    26. panic(err)
    27. }
    28. defer conn.Close()
    29. for i := 0; i < 10000; i++ {
    30. if err := conn.SendPkg([]byte(gconv.String(i))); err != nil {
    31. glog.Error(err)
    32. }
    33. time.Sleep(1*time.Second)
    34. }
    35. }

    这个示例比较简单,执行后,输出结果为:

    1. receive: [48]
    2. receive: [49]
    3. receive: [50]
    4. receive: [51]
    5. ...

    使用示例2,自定义数据结构

    大多数场景下,我们需要对发送的消息能自定义数据结构,开发者可以利用数据字段传递任意的消息内容实现。

    以下是一个简单的自定义数据结构的示例,用于客户端上报当前主机节点的内存及CPU使用情况,示例代码位于:https://github.com/gogf/gf/tree/master/geg/net/gtcp/pkg_operations/monitor

    在该示例中,数据字段使用了JSON数据格式进行自定义,便于数据的编码/解码。

    1. types/types.go

      数据结构定义。

       package types
      
       import "github.com/gogf/gf/g"
      
       type NodeInfo struct {
           Cpu       float32 // CPU百分比(%)
           Host      string  // 主机名称
           Ip        g.Map   // IP地址信息(可能多个)
           MemUsed   int     // 内存使用(byte)
           MemTotal  int     // 内存总量(byte)
           Time      int     // 上报时间(时间戳)
       }
      
    2. gtcp_monitor_server.go

      服务端。

       package main
      
       import (
           "encoding/json"
           "github.com/gogf/gf/g/net/gtcp"
           "github.com/gogf/gf/g/os/glog"
           "github.com/gogf/gf/geg/net/gtcp/pkg_operations/monitor/types"
       )
      
       func main() {
           // 服务端,接收客户端数据并格式化为指定数据结构,打印
           gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) {
               defer conn.Close()
               for {
                   data, err := conn.RecvPkg()
                   if err != nil {
                       if err.Error() == "EOF" {
                           glog.Println("client closed")
                       }
                       break
                   }
                   info := &types.NodeInfo{}
                   if err := json.Unmarshal(data, info); err != nil {
                       glog.Errorfln("invalid package structure: %s", err.Error())
                   } else {
                       glog.Println(info)
                       conn.SendPkg([]byte("ok"))
                   }
               }
           }).Run()
       }
      
    3. gtcp_monitor_client.go

      客户端。

       package main
      
       import (
           "encoding/json"
           "github.com/gogf/gf/g"
           "github.com/gogf/gf/g/net/gtcp"
           "github.com/gogf/gf/g/os/glog"
           "github.com/gogf/gf/g/os/gtime"
           "github.com/gogf/gf/geg/net/gtcp/pkg_operations/monitor/types"
       )
      
       func main() {
           // 数据上报客户端
           conn, err := gtcp.NewConn("127.0.0.1:8999")
           if err != nil {
               panic(err)
           }
           defer conn.Close()
           // 使用JSON格式化数据字段
           info, err := json.Marshal(types.NodeInfo{
               Cpu       : float32(66.66),
               Host      : "localhost",
               Ip        : g.Map {
                   "etho" : "192.168.1.100",
                   "eth1" : "114.114.10.11",
               },
               MemUsed   : 15560320,
               MemTotal  : 16333788,
               Time      : int(gtime.Second()),
           })
           if err != nil {
               panic(err)
           }
           // 使用 SendRecvPkg 发送消息包并接受返回
           if result, err := conn.SendRecvPkg(info); err != nil {
               if err.Error() == "EOF" {
                   glog.Println("server closed")
               }
           } else {
               glog.Println(string(result))
           }
       }
      
    4. 执行后
      • 客户端输出结果为:
          2019-05-03 13:33:25.710 ok
        
      • 服务端输出结果为:
          2019-05-03 13:33:25.710 &{66.66 localhost map[eth1:114.114.10.11 etho:192.168.1.100] 15560320 16333788 1556861605}
          2019-05-03 13:33:25.710 client closed