在go中使用来自网络的原始字节

dro*_*man 3 network-programming go

(对不起,很长的问题!)我最近一直在尝试使用Go而不是C++作为一个游戏服务器模拟器,我正在做一个侧面项目,并质疑我是否在合理的Go术语中实现它.正如您所料,服务器通过发送符合特定协议规范的原始数据包(TCP)与一个或多个游戏客户端进行通信.相关部分是这样的:

接收标题 - >解密它 - > recv字节,直到达到标题长度 - >解密剩余的数据包 - >调度到处理程序 - >解码数据包 - >根据需要处理 - >发送响应

协议是按照小端顺序的字节定义的,所以在我的C++实现中,包头看起来像这样(我知道,它只适用于LE机器):

struct pkt_header {
    uint16_t length;
    uint16_t type;
    uint32_t flags;
};
Run Code Online (Sandbox Code Playgroud)

recv()和解密此标头后,我将提取字段:

// client->recv_buffer is of type u_char[1024]
header = (pkt_header*) client->recv_buffer;

if (client->recv_size < header->length) {
    // Recv some more
}
// Decrypt and so on
Run Code Online (Sandbox Code Playgroud)

在处理程序本身中,我可以将上面的头结构嵌套在其他数据包结构定义中,并将它们转换到byte []缓冲区数组,以便直接访问字段.从我所读到的,结构对齐(不出所料)很难/不可能,并且在Go中非常气馁.

不知道还能做什么,我写了这个函数来从任意的Struct - > []字节:

// Serializes the fields of a struct to an array of bytes in the order in which the fields are
// declared. Calls panic() if data is not a struct or pointer to struct.
func StructToBytes(data interface{}) []byte {
    val := reflect.ValueOf(data)
    valKind := val.Kind()
    if valKind == reflect.Ptr {
        val = reflect.ValueOf(data).Elem()
        valKind = val.Kind()
    }

    if valKind != reflect.Struct {
        panic("data must of type struct or struct ptr, got: " + valKind.String())
    }

    bytes := new(bytes.Buffer)
    for i := 0; i < val.NumField(); i++ {
        field := val.Field(i)

        switch kind := field.Kind(); kind {
        case reflect.Struct:
            binary.Write(bytes, binary.LittleEndian, StructToBytes(field.Interface()))
        case reflect.Array, reflect.Slice:
            binary.Write(bytes, binary.LittleEndian, field.Interface())
        case reflect.Uint8:
            binary.Write(bytes, binary.LittleEndian, uint8(field.Uint()))
        case reflect.Uint16:
            binary.Write(bytes, binary.LittleEndian, uint16(field.Uint()))
        // You get the idea
        }
    }
    return bytes.Bytes()
}
Run Code Online (Sandbox Code Playgroud)

并将在处理程序中执行此操作:

type Header struct {
    length uint16
    size uint16
    flags uint32
}
newHeader := new(Header)
// Initialization, etc
client.Conn.Write(StructToBytes(newHeader)) // ex. [C8 00 03 00 00 00 01 00]  
Run Code Online (Sandbox Code Playgroud)

作为Go新手,关于如何更有效地实现这一点的反馈非常受欢迎.到目前为止,它运作良好,但现在我面临着如何做相反的挑战:从[] byte-> Struct(例如,[C8 00 03 00 00 01 00 00]转到标题{length = C8,size = 03,flags = 0100}

我是否需要实现相反的方法,或者是否有更好的方法从字节数组转换到任意结构(反之亦然,而不是我的函数)?如果有任何额外的清晰度有帮助,请告诉我.

Nic*_*ood 5

go方式是使用编码/二进制文件,它在内部完成你上面所写的内容.

(游乐场)

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "log"
)

type Header struct {
    Length uint16
    Size   uint16
    Flags  uint32
}

func main() {
    header := &Header{Length: 0xC8, Size: 3, Flags: 0x100}
    fmt.Printf("in = %#v\n", header)
    buf := new(bytes.Buffer)

    err := binary.Write(buf, binary.LittleEndian, header)
    if err != nil {
        log.Fatalf("binary.Write failed: %v", err)
    }
    b := buf.Bytes()
    fmt.Printf("wire = % x\n", b)

    var header2 Header
    buf2 := bytes.NewReader(b)
    err = binary.Read(buf2, binary.LittleEndian, &header2)
    if err != nil {
        log.Fatalf("binary.Read failed: %v", err)
    }
    fmt.Printf("out = %#v\n", header2)
}
Run Code Online (Sandbox Code Playgroud)

哪个打印

in = &main.Header{Length:0xc8, Size:0x3, Flags:0x100}
wire = c8 00 03 00 00 01 00 00
out = main.Header{Length:0xc8, Size:0x3, Flags:0x100}
Run Code Online (Sandbox Code Playgroud)