如何在Go中读取打包的二进制数据?

时间:2015-12-03 23:43:44

标签: go binaryfiles

我试图找出读取由Python生成的Go中的压缩二进制文件的最佳方法,如下所示:

import struct
f = open('tst.bin', 'wb')
fmt = 'iih' #please note this is packed binary: 4byte int, 4byte int, 2byte int
f.write(struct.pack(fmt,4, 185765, 1020))
f.write(struct.pack(fmt,4, 185765, 1022))
f.close()

我一直在修补我在Github.com和其他一些来源上看到的一些例子,但我似乎无法正常工作(更新节目工作方法)。 在Go中执行此类操作的惯用方法是什么?这是多次尝试之一

更新和工作

package main

    import (
            "fmt"
            "os"
            "encoding/binary"
            "io"
            )

    func main() {
            fp, err := os.Open("tst.bin")

            if err != nil {
                    panic(err)
            }

            defer fp.Close()

            lineBuf := make([]byte, 10) //4 byte int, 4 byte int, 2 byte int per line

            for true {
                _, err := fp.Read(lineBuf)

                if err == io.EOF{
                    break
                }

                aVal := int32(binary.LittleEndian.Uint32(lineBuf[0:4])) // same as: int32(uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24)
                bVal := int32(binary.LittleEndian.Uint32(lineBuf[4:8]))
                cVal := int16(binary.LittleEndian.Uint16(lineBuf[8:10])) //same as: int16(uint32(b[0]) | uint32(b[1])<<8)
                fmt.Println(aVal, bVal, cVal)
            }
    }

4 个答案:

答案 0 :(得分:3)

Python格式字符串为iih,表示两个32位有符号整数和一个16位有符号整数(请参阅docs)。您可以简单地使用您的第一个示例,但将结构更改为:

type binData struct {
    A int32
    B int32
    C int16
}

func main() {
        fp, err := os.Open("tst.bin")

        if err != nil {
                panic(err)
        }

        defer fp.Close()

        for {
            thing := binData{}
            err := binary.Read(fp, binary.LittleEndian, &thing)

            if err == io.EOF{
                break
            }

            fmt.Println(thing.A, thing.B, thing.C)
        }
}

请注意,Python打包没有明确指定字节顺序,但是如果你确定运行它的系统生成了little-endian二进制文件,那么这应该可行。

修改:添加了main()功能来解释我的意思。

修改2:大写的结构字段,以便binary.Read可以写入它们。

答案 1 :(得分:2)

Google's "Protocol Buffers"是一种轻松便携且易于处理问题的方法。虽然这已经过时,但是你已经过时了,我花了一些精力来解释和编码它,所以无论如何我都会发布它。

您可以在https://github.com/mwmahlberg/ProtoBufDemo

上找到代码

您需要使用首选方法(pip,OS包管理,源代码)和Go

安装python的协议缓冲区

.proto文件

对于我们的示例,.proto文件非常简单。我叫它data.proto

syntax = "proto2";
package main;

message Demo {
  required uint32  A = 1;
  required uint32 B = 2;

  // A shortcomning: no 16 bit ints
  // We need to make this sure in the applications
  required uint32 C = 3;
}

现在你需要在文件上调用protoc并让它为Python和Go提供代码:

protoc --go_out=. --python_out=. data.proto

生成文件data_pb2.pydata.pb.go。这些文件提供对协议缓冲区数据的特定语言访问。

使用github中的代码时,您需要做的就是发出

go generate

在源目录中。

Python代码

import data_pb2

def main():

    # We create an instance of the message type "Demo"...
    data = data_pb2.Demo()

    # ...and fill it with data
    data.A = long(5)
    data.B = long(5)
    data.C = long(2015)


    print "* Python writing to file"
    f = open('tst.bin', 'wb')

    # Note that "data.SerializeToString()" counterintuitively
    # writes binary data
    f.write(data.SerializeToString())
    f.close()

    f = open('tst.bin', 'rb')
    read = data_pb2.Demo()
    read.ParseFromString(f.read())
    f.close()

    print "* Python reading from file"
    print "\tDemo.A: %d, Demo.B: %d, Demo.C: %d" %(read.A, read.B, read.C)

if __name__ == '__main__':
    main()

我们导入protoc生成的文件并使用它。这里没什么了不起的。

Go文件

package main

//go:generate protoc --python_out=. data.proto
//go:generate protoc --go_out=. data.proto
import (
    "fmt"
    "os"

    "github.com/golang/protobuf/proto"
)

func main() {

    // Note that we do not handle any errors for the sake of brevity
    d := Demo{}
    f, _ := os.Open("tst.bin")
    fi, _ := f.Stat()

    // We create a buffer which is big enough to hold the entire message
    b := make([]byte,fi.Size())

    f.Read(b)

    proto.Unmarshal(b, &d)
    fmt.Println("* Go reading from file")

    // Note the explicit pointer dereference, as the fields are pointers to a pointers
    fmt.Printf("\tDemo.A: %d, Demo.B: %d, Demo.C: %d\n",*d.A,*d.B,*d.C)
}

请注意,我们不需要显式导入,因为data.proto的包是main

结果

生成所需文件并编译源代码后,发出

$ python writer.py && ./ProtoBufDemo

结果是

* Python writing to file
* Python reading from file
    Demo.A: 5, Demo.B: 5, Demo.C: 2015
* Go reading from file
    Demo.A: 5, Demo.B: 5, Demo.C: 2015

请注意,存储库中的Makefile提供了一个用于生成代码,编译.go文件并运行这两个程序的缩略图:

make run

答案 2 :(得分:0)

正如我在帖子中提到的,我不确定这是在Go中执行此操作的惯用方法,但这是我在经过一些修补和调整几个不同示例后想出的解决方案。再次注意,这将分别将4和2字节int解包到Go int32和int16中。发布以便在有人来看时有一个有效的答案。希望有人会发布一种更惯用的方式来实现这一目标,但现在,这有效。

package main

    import (
            "fmt"
            "os"
            "encoding/binary"
            "io"
            )

    func main() {
            fp, err := os.Open("tst.bin")

            if err != nil {
                    panic(err)
            }

            defer fp.Close()

            lineBuf := make([]byte, 10) //4 byte int, 4 byte int, 2 byte int per line

            for true {
                _, err := fp.Read(lineBuf)

                if err == io.EOF{
                    break
                }

                aVal := int32(binary.LittleEndian.Uint32(lineBuf[0:4])) // same as: int32(uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24)
                bVal := int32(binary.LittleEndian.Uint32(lineBuf[4:8]))
                cVal := int16(binary.LittleEndian.Uint16(lineBuf[8:10])) //same as: int16(uint32(b[0]) | uint32(b[1])<<8)
                fmt.Println(aVal, bVal, cVal)
            }
    }

答案 3 :(得分:0)

试试binpacker图书馆。

实施例

示例数据:

var val1 byte
var val2 uint16
var err error
val1, err = unpacker.ShiftByte()
val2, err = unpacker.ShiftUint16()

解包:

var val1 byte
var val2 uint16
var err error
unpacker.FetchByte(&val1).FetchUint16(&val2)
unpacker.Error() // Make sure error is nil

或者:

{{1}}