GoFrame 连接对象-消息包处理

2022-04-15 13:38 更新

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

简单协议

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

数据长度(16bit)|数据字段(变长)

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

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

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

操作方法

https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp

type Conn
    func (c *Conn) SendPkg(data []byte, option ...PkgOption) error
    func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) error
    func (c *Conn) SendRecvPkg(data []byte, option ...PkgOption) ([]byte, error)
    func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) ([]byte, error)
    func (c *Conn) RecvPkg(option ...PkgOption) (result []byte, err error)
    func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) ([]byte, error)

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

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

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

使用示例

示例1,基本使用

package main

import (
	"fmt"
	"github.com/gogf/gf/v2/net/gtcp"
	"github.com/gogf/gf/v2/os/glog"
	"github.com/gogf/gf/v2/util/gconv"
	"time"
)

func main() {
	// Server
	go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) {
		defer conn.Close()
		for {
			data, err := conn.RecvPkg()
			if err != nil {
				fmt.Println(err)
				break
			}
			fmt.Println("receive:", data)
		}
	}).Run()

	time.Sleep(time.Second)

	// Client
	conn, err := gtcp.NewConn("127.0.0.1:8999")
	if err != nil {
		panic(err)
	}
	defer conn.Close()
	for i := 0; i < 10000; i++ {
		if err := conn.SendPkg([]byte(gconv.String(i))); err != nil {
			glog.Error(err)
		}
		time.Sleep(1*time.Second)
	}
}

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

receive: [48]
receive: [49]
receive: [50]
receive: [51]
...

示例2,自定义数据结构

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

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

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

实际场景中,开发者往往需要自定义包解析协议,或者采用较通用的​protobuf​二进制包封装/解析协议。

  • types/types.go

数据结构定义。

package types


import "github.com/gogf/gf/v2/frame/g"


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

  • gtcp_monitor_server.go

服务端。

package main


import (
    "encoding/json"
    "github.com/gogf/gf/v2/net/gtcp"
    "github.com/gogf/gf/v2/os/glog"
    "github.com/gogf/gf/.example/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.Errorf("invalid package structure: %s", err.Error())
            } else {
                glog.Println(info)
                conn.SendPkg([]byte("ok"))
            }
        }
    }).Run()
}

  • gtcp_monitor_client.go

客户端。

package main


import (
    "encoding/json"
    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/net/gtcp"
    "github.com/gogf/gf/v2/os/glog"
    "github.com/gogf/gf/v2/os/gtime"
    "github.com/gogf/gf/.example/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.Timestamp()),
    })
    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))
    }
}

  • 执行后

客户端输出结果为:

  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


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号