乐趣区

关于go:golang网络数据交换

1. 问题形容

在 C /C++ 中解决构造体在网络上传输的解决

1.1 间接发送二进制的构造体数据

struct DataFormat {
    long arg1;
    long arg2;
};

struct Result {long sum;};

int main(int argc, char **argv) {

    ...

    char sendline[MAXLINE];
    struct DataFormat args;
    struct Result result;

    args.arg1 = 1;
    args.arg2 = 2;

    write(sockfd, &args, sizeof(args));
    if (readn(sockfd, &result, sizeof(result)) != 0)
        printf("%ld\n", result.sum);

    ...
}

这种做法有许多缺点,包含:

  • 发送的多字节类型(int,long 等)在不同架构的机器上的大小端形式可能不同,造成解析谬误
  • 即便是大小端统一的机器上,可能因为 int、long 等类型的机器字长不同而呈现谬误
  • 构造体在不同的编译器中的对其形式可能有所不同,这也会造成解析谬误

1.2 解决形式

针对以上 3 个问题,采纳的解决思路:

  • 对于多字节类型,采纳转换成对立的网络字节序进行解决, 应用 htons htonl 和 ntohs ntohl
  • 对于类型字长不统一的状况,对立采纳固定长度的类型,比方 uint8, uint16 等类型
  • 构造体的对其形式都设置成 packed,这样是的成员间接没有任何对其(在不同的编译器中示意形式不一样,在 gcc 中应用__attribute__((packed)))
struct DataFormat {
    int32_t arg1;
    int32_t arg2;
}__attribute__((packed));

struct Result {int32_t sum;}__attribute__((packed));

int main(int argc, char **argv) {

    ...

    char sendline[MAXLINE];
    struct DataFormat args;
    struct Result result;

    args.arg1 = htonl(1);
    args.arg2 = htonl(2);

    write(sockfd, &args, sizeof(args));
    if (readn(sockfd, &result, sizeof(result)) != 0)
        printf("%ld\n", ntohl(result.sum));

    ...
}

1.3 解决形式 2

还有一种更好的解决形式:咱们在两端发送和接管的时候都应用字符串进行,因为字符只有一个字节,在传输过程中不存在大小端的问题,另外字符的示意办法在各平台根本是统一的,也没有所谓的对齐问题,因而这种计划具备最大的通用性

struct DataFormat {
    int32_t arg1;
    int32_t arg2;
};

struct Result {int32_t sum;};

int main(int argc, char **argv) {

    //...

    char sendline[1024];
    struct DataFormat args;
    struct Result result;

    snprintf(sendline, sizeof(sendline), "%d%d\n", args.arg1,args.arg2);

    write(sockfd, sendline, strlen(sendline));

    //...
}

传输的过程是依照构造体中成员的程序顺次进行的,对端解析的时候也须要依照这个程序进行解决

2. golang 中的解决

在查阅了一些材料之后总结如下:

  • 应用 unsafe 包,应用相似于 C /C++ 的做法(不举荐)
  • 应用 encoding/binary 包进行字节的封包和解包
  • 应用更高层级的 marshal 和 unmarshal 的做法

2.1 应用 unsafe 包

这种形式因为波及到底层许多 C 和 golang 互操作的细节,我临时没有趣味和需要理解,先略过

贴一个简略的示例,能够感触基本上和 C /C++ 指针形式相似

package main

import (
    "fmt"
    "unsafe"
)

type TestStructTobytes struct {data int64}
type SliceMock struct {
    addr uintptr
    len  int
    cap  int
}

func main() {var testStruct = &TestStructTobytes{100}
    Len := unsafe.Sizeof(*testStruct)
    testBytes := &SliceMock{addr: uintptr(unsafe.Pointer(testStruct)),
        cap:  int(Len),
        len:  int(Len),
    }
    data := *(*[]byte)(unsafe.Pointer(testBytes))
    fmt.Println("[]byte is :", data)

    var ptestStruct *TestStructTobytes = *(**TestStructTobytes)(unsafe.Pointer(&data))
    fmt.Println("ptestStruct.data is :", ptestStruct.data)
}

2.2 应用 encoding/binary

在浏览文档之后理解到,encoding/binary 库有一些缺点:

  • 仅反对定长变量和构造体的 encoding 和 decoding
  • 反对的数据类型是 Numeric 类型的(也就是整数型、浮点型)

于是上面这样的构造体是 不反对的

type Info struct {
    ID   uint32
    Desc string
}

起因在于构造体的长度是变长的

然而上面这种构造体是能够反对的(因为构造体中的字节数组的长度固定是 3)

type Data struct {
    ID        uint32
    Timestamp uint64
    Value int16
    Desc      [3]byte
}

因为 binary 包的这个个性,因而它比拟适宜于做一些简略的封装,如果咱们须要封装蕴含有变长的构造,那么咱们还须要本人一一字段进行封装(而不能应用 binary 提供的 Read 和 Write 办法一次性将构造体进行封包和解包)

  1. 示例 1: 固定长度构造体
type message struct {
    Id   int32
    Len  int32
    Data [4]byte
}

func unpack(data []byte) *message {msg := &message{}
    dataio := bytes.NewReader(data)

    binary.Read(dataio, binary.LittleEndian, msg)

    // 下面这一行 binary.Read(dataio, binary.LittleEndian, msg)能够
    // 用上面的 3 行替换,成果是一样的
    // binary.Read(dataio, binary.LittleEndian, &msg.Id)
    // binary.Read(dataio, binary.LittleEndian, &msg.Len)
    // binary.Read(dataio, binary.LittleEndian, &msg.Data)

    return msg
}
func pack(msg *message) []byte {databufio := bytes.NewBuffer([]byte{})

    binary.Write(databufio, binary.LittleEndian, msg)

    // 下面这一行 binary.Write(databufio, binary.LittleEndian, msg)
    // 能够用上面 3 行替换
    // binary.Write(databufio, binary.LittleEndian, msg.Id)
    // binary.Write(databufio, binary.LittleEndian, msg.Len)
    // binary.Write(databufio, binary.LittleEndian, msg.Data)
    return databufio.Bytes()}

func main() {bindata := []byte{}

    msg := &message{
        Id:   1,
        Len:  4,
        Data: [4]byte{'h', 'a', 'h', 'h'},
    }

    bindata = pack(msg)
    fmt.Println("struct-to-byte array")
    fmt.Println(bindata)

    msg2 := unpack(bindata)
    fmt.Println("byte-array-to-struct =")
    fmt.Printf("%v\n", msg2)
}

有一些须要留神的点:

  • 如果是独自的一个整型值,能够应用 PutUvarint 等间接转换即可,不须要应用 Read 和 Write,后者次要用于对构造体进行转换
  • 在构造体中的成员必须应用首字母大写的形式(导出),否则在 Read 和 Write 的时候会报错
  1. 可变长构造体

如果构造体中蕴含可变长的字段,那么就须要咱们手动进行解决

type message struct {
    id   int32
    len  int32
    data []byte}

func unpackHead(byteValue []byte) *message {msg := &message{}
    dataio := bytes.NewReader(byteValue)
    binary.Read(dataio, binary.LittleEndian, &msg.id)
    binary.Read(dataio, binary.LittleEndian, &msg.len)
    return msg
}

func pack(msg *message) []byte {databufio := bytes.NewBuffer([]byte{})

    binary.Write(databufio, binary.LittleEndian, msg.id)
    binary.Write(databufio, binary.LittleEndian, msg.len)
    binary.Write(databufio, binary.LittleEndian, msg.data)
    return databufio.Bytes()}

func main() {

    msg := &message{
        id:   1,
        len:  4,
        data: []byte{'h', 'a', 'h', 'h'},
    }

    bindata := pack(msg)
    fmt.Println("packed size:", len(bindata))
    fmt.Println("struct-to-byte-array", bindata)

    msg2 := unpackHead(bindata)
    beginIndex := unsafe.Sizeof(msg2.id) + unsafe.Sizeof(msg2.len)
    msg2.data = make([]byte, 4)
    binary.Read(bytes.NewReader(bindata[beginIndex:]), binary.LittleEndian, &msg2.data)
    fmt.Println("byte-array-to-strcut")
    fmt.Printf("%v\n", msg2)
}

以上仅仅是解决了一个构造体的情景,可想而知如果是一个可变长的构造体的 slice,那么解决的复杂度会晋升十分多,因而不太倡议应用这种形式来解决简单的类型

3 更高层级的做法

数据在网络传输中是一些二进制的数据流而已,从发送端将程序中定义的数据结构体转换成字节流,接收端接管到数据流之后须要反向转换回原来的数据结构,这一过程个别称之为 Serialization 和 Deserialization,在 golang 中个别称之为 marshalling 和 unmarshalling

golang 提供了多种 marshling 和 unmarshaling 的办法,包含

  • GOB(encoding/gob 包,仅 golang 语言可用)
  • JSON(JavaScript Object Notation)(encoding/json 包)
  • ASN.1 (Abstract Syntax Notation One) (encoding/asn1 包)
  • 其余 …

3.1 GOB

(1)特点:

  • golang specific,不能够跨语言
  • 反对 golang 内置的绝大部分类型(除 channel、function、interface 外)
  • 反对接管方数据结构的一些兼容字段转换

(2)应用办法

发送端申请一个 Encoder,接管方申请一个 Decoder

(3)扩大自定义类型

如果自定义类型须要解决,能够实现 BinaryMarshaler 接口 (MarshalBinary) 和 BinaryUnmarshaler(UnmarshalBinary)接口

示例程序:

type P struct {
    X, Y, Z int
    Name    string
}

type Q struct {
    X, Y *int32
    Name string
}

func main() {
    var network bytes.Buffer
    enc := gob.NewEncoder(&network)
    dec := gob.NewDecoder(&network)

    // Encode (send) some values.
    err := enc.Encode(P{3, 4, 5, "Pythagoras"})
    if err != nil {log.Fatal("encode error:", err)
    }
    // Decode (receive) and print the values.
    var q Q
    err = dec.Decode(&q)
    if err != nil {log.Fatal("decode error 1:", err)
    }
    fmt.Printf("%q: {%d, %d}\n", q.Name, *q.X, *q.Y)
}

能够看到 gob 相比之前的 binary 的不仅反对的数据类型丰盛,而且编码方式简略太多了,举荐应用这种形式在过程间传输数据

3.2 JSON

JSON 这种形式也 gob 有点相似,只不过它是给予 JSON 这种格局标准来进行解决 Encode 和 Decode,因为 JSON 格局的一些限度(比方对象的名称只能是字符串等)因而相对来说它提供的反对类型比 gob 要少一些,然而它提供了对于 json 的一些操作函数

示例

//marshal
func main() {
    type ColorGroup struct {
        ID     int
        Name   string
        Colors []string}
    group := ColorGroup{
        ID:     1,
        Name:   "Reds",
        Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
    }
    b, err := json.Marshal(group)
    if err != nil {fmt.Println("error:", err)
    }
    os.Stdout.Write(b)
}


//unmarshal
func main() {var jsonBlob = []byte(`[{"Name": "Platypus", "Order": "Monotremata"},
    {"Name": "Quoll",    "Order": "Dasyuromorphia"}
]`)
    type Animal struct {
        Name  string
        Order string
    }
    var animals []Animal
    err := json.Unmarshal(jsonBlob, &animals)
    if err != nil {fmt.Println("error:", err)
    }
    fmt.Printf("%+v", animals)
}

3.3 ASN.1

ASN.1 是一个 1984 年推出来的通信畛域的协定,也是用于数据的替换,它定义的规定绝对比较复杂,golang 中应用在 X.509 certificates 中,golang 中的 asn1 包次要提供以下两个函数来进行封包和解包

func Marshal(val interface{}) ([]byte, os.Error)
func Unmarshal(val interface{}, b []byte) (rest []byte, err os.Error)

简略的应用示例

func main() {mdata, err := asn1.Marshal(13)
    checkError(err)

    var n int
    _, err1 := asn1.Unmarshal(mdata, &n)
    checkError(err1)

    fmt.Println("After marshal/unmarshal:", n)
}

func checkError(err error) {
    if err != nil {fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
}

更多内容请参考 golang 规范库

4. 参考资料

1. 打造本人的字节序转换函数(16 位、32 位和 64 位)

2.packing struct in golang in bytes to talk with C application

3.Equivalent of C++ reinterpret_cast a void* to a struct in Golang

4.Go 语言构造体与二进制数组转换

5.decoding binary data when structures include strings

6. golang standard library gob package

退出移动版