Go 结构体和字节数组之间的转换

8

我是一名Go语言开发者,正在编写一个客户端-服务器应用程序。我想在Go语言中执行类似C语言的类型转换。

例如,在Go语言中:

type packet struct {
    opcode uint16
    data [1024]byte
}

var pkt1 packet
...
n, raddr, err := conn.ReadFromUDP(pkt1)  // error here

我希望能够执行类似于C语言的memcpy()函数,这样可以直接将收到的网络字节流映射到一个结构体中。

例如,使用上面收到的pkt1:

type file_info struct {
    file_size uint32       // 4 bytes
    file_name [1020]byte
}

var file file_info
if (pkt1.opcode == WRITE) {
    memcpy(&file, pkt1.data, 1024)
}

2
我建议先尝试用Go编写。你不会在Go中做任何像这样的事情。其中之一是,Go没有类型转换。uint也不是4个字节。Conn.Read接受一个[]byte,它在实质上是指向支持数组的智能大小指针。你只需在Go中编写此代码会更加轻松。 - Dustin
1
你是否已经查看了encoding/gob包中内置的二进制序列化功能? - dyoo
4个回答

20

unsafe.Pointer 是不安全的,实际上你在这里并不需要它。使用encoding/binary包代替:

// Create a struct and write it.
t := T{A: 0xEEFFEEFF, B: 3.14}
buf := &bytes.Buffer{}
err := binary.Write(buf, binary.BigEndian, t)
if err != nil {
    panic(err)
}
fmt.Println(buf.Bytes())

// Read into an empty struct.
t = T{}
err = binary.Read(buf, binary.BigEndian, &t)
if err != nil {
    panic(err)
}
fmt.Printf("%x %f", t.A, t.B)

Playground

正如您所看到的,它可以很好地处理大小和字节序。


5
我曾经遇到过相同的问题,通过使用" encoding/binary"包来解决。以下是一个例子:
package main

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

func main() {
  p := fmt.Println
  b := []byte{43, 1, 0}

  myStruct := MyStruct{}
  err := binary.Read(bytes.NewBuffer(b[:]), binary.BigEndian, &myStruct)

  if err != nil {
    panic(err)
  }

  p(myStruct)
}

type MyStruct struct {
  Num uint8
  Num2 uint16
}

这是一个可运行的例子: https://play.golang.org/p/Q3LjaAWDMh。这个例子与IT技术有关。

4
感谢您提供的答案,我相信它们完美地运作。但在我的情况下,我更加关注解析作为网络数据包接收到的[]byte缓冲区。我使用了以下方法来解析缓冲区。
var data []byte // holds the network packet received
opcode := binary.BigEndian.Uint16(data) // this will get first 2 bytes to be interpreted as uint16 number
raw_data := data[2:len(data)] // this will copy rest of the raw data in to raw_data byte stream

在从结构体构建 []byte 流时,您可以使用以下方法:

type packet struct {
    opcode uint16
    blk_no uint16
    data   string
}
pkt := packet{opcode: 2, blk_no: 1, data: "testing"}
var buf []byte = make([]byte, 50) // make sure the data string is less than 46 bytes
offset := 0
binary.BigEndian.PutUint16(buf[offset:], pkt.opcode)
offset = offset + 2
binary.BigEndian.PutUint16(buf[offset:], pkt.blk_no)
offset = offset + 2
bytes_copied := copy(buf[offset:], pkt.data)

我希望这篇文章可以给你一个大致的想法,关于如何将[]byte流转换为结构体,以及如何将结构体转换回[]byte流。


1

你必须使用不安全的代码,而且在64位系统上uint占用8个字节,如果你想要4个字节,就必须使用uint32。

这很丑陋,也很不安全,你必须自己处理字节序。

type packet struct {
    opcode uint16
    data   [1022]byte
}

type file_info struct {
    file_size uint32     // 4 bytes
    file_name [1018]byte //this struct has to fit in packet.data
}

func makeData() []byte {
    fi := file_info{file_size: 1 << 20}
    copy(fi.file_name[:], []byte("test.x64"))
    p := packet{
        opcode: 1,
        data:   *(*[1022]byte)(unsafe.Pointer(&fi)),
    }
    mem := *(*[1022]byte)(unsafe.Pointer(&p))
    return mem[:]
}

func main() {
    data := makeData()
    fmt.Println(data)
    p := (*packet)(unsafe.Pointer(&data[0]))
    if p.opcode == 1 {
        fi := (*file_info)(unsafe.Pointer(&p.data[0]))
        fmt.Println(fi.file_size, string(fi.file_name[:8]))
    }
}

play

的程序相关内容。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接