如何解包长度前缀的字节串?

7 投票
6 回答
60071 浏览
提问于 2025-04-16 12:47

我使用了标准库中的 struct 模块,把一个 bytes 对象打包成了一个字符串,并在前面加上了长度信息:

>>> import struct
>>> string = b'-'
>>> t = struct.pack(">h%ds" % len(string), len(string), string)
>>> print(t)
b'\x00\x01-'

当然,我可以直接去掉长度信息,这样就能得到原来的数据。但是我该怎么做才能 解包 这个数据,并且 考虑到长度信息,以便得到 b'-' 呢?

6 个回答

1

struct模块是用来处理格式固定的数据块的。不过,你可以使用下面的代码:

import struct
t=b'\x00\x01-'
(length,)=struct.unpack_from(">h", t)
(text,)=struct.unpack_from("%ds"%length, t, struct.calcsize(">h"))
print text
2
import struct
string = b'-'
fmt=">h%ds" % len(string)

这里你把字符串的长度和字符串本身都打包在一起了:

t = struct.pack(fmt, len(string), string)
print(repr(t))
# '\x00\x01-'

所以当你解包的时候,应该期待得到两个值,也就是长度和字符串:

length,string2=struct.unpack(fmt,t)
print(repr(string2))
# '-'

一般来说,如果你不知道字符串是怎么打包的,那就没有可靠的方法来恢复数据。你只能猜!

如果你知道数据是由字符串的长度和字符串本身组成的,那么你可以尝试一些方法来找出答案:

import struct
string = b'-'
fmt=">h%ds" % len(string)
t = struct.pack(fmt, len(string), string)
print(repr(t))

for endian in ('>','<'):
    for fmt,size in (('b',1),('B',1),('h',2),('H',2),('i',4),('I',4),
                     ('l',4),('L',4),('q',8),('Q',8)):
        fmt=endian+fmt
        try:
            length,=struct.unpack(fmt,t[:size])
        except struct.error:
            pass
        else:
            fmt=fmt+'{0}s'.format(length)
            try:
                length,string2=struct.unpack(fmt,t)
            except struct.error:
                pass
            else:
                print(fmt,length,string2)
# ('>h1s', 1, '-')
# ('>H1s', 1, '-')

不过,可能会有一种模糊的字符串 t,它有多种有效的解包方式,可能会导致不同的 string2,但我不太确定。

5

通常情况下,你不会用 struct.pack 来把长度头和数据值放在一起。一般来说,你只需要用 struct.pack(">h", len(data)) 来打包数据的长度,然后把这个长度发送出去(比如在网络协议中),接着再发送实际的数据。这样就不需要创建一个新的字节缓冲区。

在你的情况下,你可以简单地这样做:

dataLength, = struct.unpack(">h", t[:2])
data = t[2:2+dataLength]

不过就像我说的,如果你有一个基于套接字的应用程序,比如说,它会是这样:

header = receive(2)
dataLength, = struct.unpack(">h", header)
data = receive(dataLength)

撰写回答