如何在 Go 中读取打包的二进制数据?
How to read packed binary data in Go?
我正在尝试找出在 Go 中读取由 Python 生成的压缩二进制文件的最佳方法,如下所示:
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)
}
}
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 打包没有明确指定字节顺序,但如果您确定 运行 系统生成小端二进制文件,这应该可以工作。
编辑: 添加了 main()
功能来解释我的意思。
编辑 2: 将结构字段大写,以便 binary.Read
可以写入它们。
正如我在我的 post 中提到的,我不确定这是在 Go 中执行此操作的惯用方法,但这是我在经过一些修补和调整后想出的解决方案不同的例子。再次注意,这将 4 字节和 2 字节的 int 分别解包为 Go int32 和 int16。发布以便在有人查看时提供有效答案。希望有人会 post 一种更惯用的方法来完成此操作,但就目前而言,这是可行的。
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)
}
}
处理该问题的一种便携且相当简单的方法是 Google's "Protocol Buffers"。虽然现在已经太晚了,因为你让它工作了,我花了一些努力来解释和编码它,所以我还是发布了它。
您可以在 https://github.com/mwmahlberg/ProtoBufDemo
上找到代码
您需要使用首选方法(pip、OS 包管理、源)为 python 和 Go
安装协议缓冲区
.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.py
和 data.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
尝试 binpacker 库。
示例:
示例数据:
buffer := new(bytes.Buffer)
packer := binpacker.NewPacker(buffer)
unpacker := binpacker.NewUnpacker(buffer)
packer.PushByte(0x01)
packer.PushUint16(math.MaxUint16)
解压:
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
我正在尝试找出在 Go 中读取由 Python 生成的压缩二进制文件的最佳方法,如下所示:
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)
}
}
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 打包没有明确指定字节顺序,但如果您确定 运行 系统生成小端二进制文件,这应该可以工作。
编辑: 添加了 main()
功能来解释我的意思。
编辑 2: 将结构字段大写,以便 binary.Read
可以写入它们。
正如我在我的 post 中提到的,我不确定这是在 Go 中执行此操作的惯用方法,但这是我在经过一些修补和调整后想出的解决方案不同的例子。再次注意,这将 4 字节和 2 字节的 int 分别解包为 Go int32 和 int16。发布以便在有人查看时提供有效答案。希望有人会 post 一种更惯用的方法来完成此操作,但就目前而言,这是可行的。
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)
}
}
处理该问题的一种便携且相当简单的方法是 Google's "Protocol Buffers"。虽然现在已经太晚了,因为你让它工作了,我花了一些努力来解释和编码它,所以我还是发布了它。
您可以在 https://github.com/mwmahlberg/ProtoBufDemo
上找到代码您需要使用首选方法(pip、OS 包管理、源)为 python 和 Go
安装协议缓冲区.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.py
和 data.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
尝试 binpacker 库。
示例:
示例数据:
buffer := new(bytes.Buffer)
packer := binpacker.NewPacker(buffer)
unpacker := binpacker.NewUnpacker(buffer)
packer.PushByte(0x01)
packer.PushUint16(math.MaxUint16)
解压:
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