mirror of
https://gitee.com/johng/gf.git
synced 2024-11-30 19:27:46 +08:00
180 lines
5.6 KiB
Go
180 lines
5.6 KiB
Go
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
|
//
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
// If a copy of the MIT was not distributed with this file,
|
|
// You can obtain one at https://github.com/gogf/gf.
|
|
|
|
package gtcp
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"github.com/gogf/gf/errors/gcode"
|
|
"github.com/gogf/gf/errors/gerror"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
pkgHeaderSizeDefault = 2 // Header size for simple package protocol.
|
|
pkgHeaderSizeMax = 4 // Max header size for simple package protocol.
|
|
)
|
|
|
|
// PkgOption is package option for simple protocol.
|
|
type PkgOption struct {
|
|
// HeaderSize is used to mark the data length for next data receiving.
|
|
// It's 2 bytes in default, 4 bytes max, which stands for the max data length
|
|
// from 65535 to 4294967295 bytes.
|
|
HeaderSize int
|
|
|
|
// MaxDataSize is the data field size in bytes for data length validation.
|
|
// If it's not manually set, it'll automatically be set correspondingly with the HeaderSize.
|
|
MaxDataSize int
|
|
|
|
// Retry policy when operation fails.
|
|
Retry Retry
|
|
}
|
|
|
|
// SendPkg send data using simple package protocol.
|
|
//
|
|
// Simple package protocol: DataLength(24bit)|DataField(variant)。
|
|
//
|
|
// Note that,
|
|
// 1. The DataLength is the length of DataField, which does not contain the header size.
|
|
// 2. The integer bytes of the package are encoded using BigEndian order.
|
|
func (c *Conn) SendPkg(data []byte, option ...PkgOption) error {
|
|
pkgOption, err := getPkgOption(option...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
length := len(data)
|
|
if length > pkgOption.MaxDataSize {
|
|
return gerror.NewCodef(
|
|
gcode.CodeInvalidParameter,
|
|
`data too long, data size %d exceeds allowed max data size %d`,
|
|
length, pkgOption.MaxDataSize,
|
|
)
|
|
}
|
|
offset := pkgHeaderSizeMax - pkgOption.HeaderSize
|
|
buffer := make([]byte, pkgHeaderSizeMax+len(data))
|
|
binary.BigEndian.PutUint32(buffer[0:], uint32(length))
|
|
copy(buffer[pkgHeaderSizeMax:], data)
|
|
if pkgOption.Retry.Count > 0 {
|
|
return c.Send(buffer[offset:], pkgOption.Retry)
|
|
}
|
|
return c.Send(buffer[offset:])
|
|
}
|
|
|
|
// SendPkgWithTimeout writes data to connection with timeout using simple package protocol.
|
|
func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) (err error) {
|
|
if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil {
|
|
return err
|
|
}
|
|
defer c.SetSendDeadline(time.Time{})
|
|
err = c.SendPkg(data, option...)
|
|
return
|
|
}
|
|
|
|
// SendRecvPkg writes data to connection and blocks reading response using simple package protocol.
|
|
func (c *Conn) SendRecvPkg(data []byte, option ...PkgOption) ([]byte, error) {
|
|
if err := c.SendPkg(data, option...); err == nil {
|
|
return c.RecvPkg(option...)
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// SendRecvPkgWithTimeout writes data to connection and reads response with timeout using simple package protocol.
|
|
func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) ([]byte, error) {
|
|
if err := c.SendPkg(data, option...); err == nil {
|
|
return c.RecvPkgWithTimeout(timeout, option...)
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// RecvPkg receives data from connection using simple package protocol.
|
|
func (c *Conn) RecvPkg(option ...PkgOption) (result []byte, err error) {
|
|
var buffer []byte
|
|
var length int
|
|
pkgOption, err := getPkgOption(option...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Header field.
|
|
buffer, err = c.Recv(pkgOption.HeaderSize, pkgOption.Retry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch pkgOption.HeaderSize {
|
|
case 1:
|
|
// It fills with zero if the header size is lesser than 4 bytes (uint32).
|
|
length = int(binary.BigEndian.Uint32([]byte{0, 0, 0, buffer[0]}))
|
|
case 2:
|
|
length = int(binary.BigEndian.Uint32([]byte{0, 0, buffer[0], buffer[1]}))
|
|
case 3:
|
|
length = int(binary.BigEndian.Uint32([]byte{0, buffer[0], buffer[1], buffer[2]}))
|
|
default:
|
|
length = int(binary.BigEndian.Uint32([]byte{buffer[0], buffer[1], buffer[2], buffer[3]}))
|
|
}
|
|
// It here validates the size of the package.
|
|
// It clears the buffer and returns error immediately if it validates failed.
|
|
if length < 0 || length > pkgOption.MaxDataSize {
|
|
return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid package size %d`, length)
|
|
}
|
|
// Empty package.
|
|
if length == 0 {
|
|
return nil, nil
|
|
}
|
|
// Data field.
|
|
return c.Recv(length, pkgOption.Retry)
|
|
}
|
|
|
|
// RecvPkgWithTimeout reads data from connection with timeout using simple package protocol.
|
|
func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) (data []byte, err error) {
|
|
if err := c.SetreceiveDeadline(time.Now().Add(timeout)); err != nil {
|
|
return nil, err
|
|
}
|
|
defer c.SetreceiveDeadline(time.Time{})
|
|
data, err = c.RecvPkg(option...)
|
|
return
|
|
}
|
|
|
|
// getPkgOption wraps and returns the PkgOption.
|
|
// If no option given, it returns a new option with default value.
|
|
func getPkgOption(option ...PkgOption) (*PkgOption, error) {
|
|
pkgOption := PkgOption{}
|
|
if len(option) > 0 {
|
|
pkgOption = option[0]
|
|
}
|
|
if pkgOption.HeaderSize == 0 {
|
|
pkgOption.HeaderSize = pkgHeaderSizeDefault
|
|
}
|
|
if pkgOption.HeaderSize > pkgHeaderSizeMax {
|
|
return nil, gerror.NewCodef(
|
|
gcode.CodeInvalidParameter,
|
|
`package header size %d definition exceeds max header size %d`,
|
|
pkgOption.HeaderSize, pkgHeaderSizeMax,
|
|
)
|
|
}
|
|
if pkgOption.MaxDataSize == 0 {
|
|
switch pkgOption.HeaderSize {
|
|
case 1:
|
|
pkgOption.MaxDataSize = 0xFF
|
|
case 2:
|
|
pkgOption.MaxDataSize = 0xFFFF
|
|
case 3:
|
|
pkgOption.MaxDataSize = 0xFFFFFF
|
|
case 4:
|
|
// math.MaxInt32 not math.MaxUint32
|
|
pkgOption.MaxDataSize = 0x7FFFFFFF
|
|
}
|
|
}
|
|
if pkgOption.MaxDataSize > 0x7FFFFFFF {
|
|
return nil, gerror.NewCodef(
|
|
gcode.CodeInvalidParameter,
|
|
`package data size %d definition exceeds allowed max data size %d`,
|
|
pkgOption.MaxDataSize, 0x7FFFFFFF,
|
|
)
|
|
}
|
|
return &pkgOption, nil
|
|
}
|