【问题标题】:Working with raw bytes from a network in go在 go 中处理来自网络的原始字节
【发布时间】:2015-01-07 07:27:51
【问题描述】:

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

接收报头 -> 解密它 -> 接收字节直到达到报头长度 -> 解密数据包的其余部分 -> 分派到处理程序 -> 解码数据包 -> 必要时处理 -> 发送响应

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

struct pkt_header {
    uint16_t length;
    uint16_t type;
    uint32_t flags;
};

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

// 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

在处理程序本身中,我可以将上述标头结构嵌套在其他数据包结构定义中,并将它们转换为 byte[] 缓冲区数组,以便直接访问字段。根据我的阅读,结构对齐(不出所料)在 Go 中是困难的/不可能的并且非常不鼓励。

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

// 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()
}

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

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]  

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

我是否只需要实现与此相反的操作,或者是否有更好的方法可以从字节数组转换为任意结构(反之亦然,而不是我的函数)?请让我知道是否有任何额外的说明会有所帮助。

【问题讨论】:

    标签: go network-programming


    【解决方案1】:

    最好的方法是使用encoding/binary,它在内部完成了您上面所写的工作。

    (playground)

    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)
    }
    

    打印出来的

    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}
    

    【讨论】:

    • 太好了,谢谢!我应该在去写我自己的函数之前问清楚。至少它很有趣......
    猜你喜欢
    • 1970-01-01
    • 2019-10-14
    • 1970-01-01
    • 2013-02-11
    • 1970-01-01
    • 2019-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多