Python反序列化时出现堆栈下溢错误

16

我一直在开发一个Python应用程序,客户端向服务器发送时钟信号,服务器则以音频信号作出响应。
我有两个按钮,一个用于启动时钟,另一个用于暂停音轨。

主类

# function I call when I hit the play button
def play(self):
    start_song = [250]
    global IS_FIRST_PLAY
    if IS_FIRST_PLAY:
        IS_FIRST_PLAY = False
        self.startClock()
    if IS_CONNECTED:
        client.sendMessage(start_song)

# here I start the clock and send a constant clock signal to the client
def startClock(self):
    clock = midi.startClock()
    for i in clock:
        if IS_CONNECTED:
            client.sendMessage(i)
    midi.playing = True

# here I pause the track
def pause(self):
    stop_song = [252]
    if IS_CONNECTED:
        client.sendMessage(stop_song)
    midi.sendMidiMessage(stop_song)
    midi.playing = False
    midi.mtClock = [0, 0, 0, 0]

客户端类

# this is the client.sendMessage() function
def sendMessage(self, message):
    self.s.sendall(pickle.dumps(message))

服务器类

# this is the class that handles the incoming clock signal for the server
class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
        global IS_FIRST_PLAY, IS_PLAYING      
        thread1 = threading.Thread(target=self.sendAudio)
        thread1.start()
        while True:
            # here throws python an error
            self.data = pickle.loads(self.request.recv(12).strip())
这一切都很好,除了一个随机的时刻,当我将暂停改为播放时,我会不断收到这个错误提示:
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/socketserver.py", line 306, in _handle_request_noblock
    self.process_request(request, client_address)
  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/socketserver.py", line 332, in process_request
    self.finish_request(request, client_address)
  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/socketserver.py", line 345, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/socketserver.py", line 666, in __init__
    self.handle()
  File "/Users/cedricgeerinckx/Dropbox/Redux/OSX/Server.py", line 85, in handle
    self.data = pickle.loads(self.request.recv(12).strip())
_pickle.UnpicklingError: unpickling stack underflow

这个问题可能是什么?


1
看起来你的pickle出了问题。为什么要使用recv(12).strip() - user2357112
我之前也尝试过使用pickle但没有使用strip(),但是并没有什么区别。我使用了send(1024)recv(1024)来发送我的数据,但是由于读取了1024个字节而不是一行,所以发送了错误的数据。也许我应该改成readline或者其他什么方式? - boortmans
1个回答

12

unpickling stack underflow 可能会在 pickle 对象意外结束时发生。

在这里,self.request.recv(12),你最多只能接收 12 个字节,如果你的 pickle 对象比 12 个字节长,则会被截断。

我不建议直接处理 TCP sockets,除非你非常熟悉网络和需要非常高的性能。我建议使用 HTTP 包装消息并使用 HTTP 库。

如果您确实必须直接处理 TCP,则有两个选择:

  1. 可以在客户端和服务器之间约定一个终止字符串,例如 '\0'(空字符); 消息将以此终止字符串分隔。终止字符串不能出现在消息体内(否则您将不得不想办法在消息体中转义终止字符串)。 您还需要缓冲数据包,以便在读取大小小于或大于对象的情况下接收整个消息,并在终止字符串上拆分消息。请注意,您还需要处理的情况是,如果快速连续发送多个小消息,则接收器可能在单个 .recv() 中接收到多个消息。

  2. 更简单的方法是,在发送所有消息的前四个字节中附加其长度。 接收方始终从流中读取四个字节,将其解码为整数,然后从流中读取这么多字节,即为一个完整的消息。

或者,如果发送者和接收者都在 Python 中,则可以重新设计你的程序以使用 multiprocessing Queue。

我认为使用 HTTP 库作为传输协议可能是最简单的方法,因为它将处理所有分块消息的详细信息,并且可用于多台机器和技术。


整数是最大值吗?还是需要精确匹配?因为一开始我使用send(1024)和recv(1024)发送我的数据,但由于读取了1024个字节而不是一行,所以发送了错误的数据。 - boortmans
@Barto:你传递给read()的整数是read()将返回的最大字节数,read()可能会返回比请求的少但永远不会超过。 - Lie Ryan
谢谢解释。我一有机会就会测试它! - boortmans
你还需要知道使用 TCP socket 进行通信时,单个 send() 发送的数据可能会出现在多个 recv() 调用中。例如,100 个字节的 send() 可能会被分成一个首次接收的 25 个字节和剩余的 75 个字节(或者极端情况下,分成 100 个每个字节的 recv())。如果必须使用 TCP,则最好采用长度前缀方式。 - Russell Borogove

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