Pickle EOFError:从套接字接收时输入耗尽

0 投票
2 回答
7879 浏览
提问于 2025-04-18 13:10

我正在运行一个非常简单的Python(3.x)客户端-服务器程序(都是在我自己的电脑上),这是为了一个学校项目(并不是为了实际应用),这个程序只是用来发送一些简单的消息(比如查看客户、添加客户、删除客户等等,真的很基础)。

有时候,数据可能会有多个记录,我把这些记录存储为命名元组(这样做比较合理),然后我选择使用Pickle来传输这些数据。

比如在客户端,我会做类似这样的操作:

s.send(message.encode('utf-8'))
pickledResponse = s.recv(4096);
response = pickle.loads(pickledResponse)

但是,有时候我会遇到以下错误:

response = pickle.loads(pickledResponse)
EOFError: Ran out of input

我担心这可能和我的socket(TCP)传输有关,可能是我没有及时接收到所有的数据,以至于在进行pickle.loads时出错——这样说你能理解吗?如果不理解,我真的不知道为什么会出现这样不一致的问题。

不过,即使我猜对了,我也不太确定该怎么快速解决这个问题。我在考虑放弃使用pickle,直接用字符串(但这样也可能会遇到同样的问题吧)?有没有人有什么建议?

其实我的消息很简单——通常只是一个命令和一些小数据,比如“1=John”,这表示命令(1)是查找命令,然后是“John”,这样就能返回John的记录(名字、年龄等等,作为命名元组——不过老实说这不是必须的)。

任何建议或帮助都非常感谢,我在寻找一个快速的解决办法……

2 个回答

0

如果你想接收服务器发送的所有数据,直到它关闭连接,可以试试这个方法:

import json
import socket
from functools import partial


def main():
    message = 'Test'

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect(('127.0.0.1', 9999))

        sock.sendall(message.encode('utf-8'))
        sock.shutdown(socket.SHUT_WR)

        json_response = b''.join(iter(partial(sock.recv, 4096), b''))

    response = json.loads(json_response.decode('utf-8'))
    print(response)


if __name__ == '__main__':
    main()

我使用了 sendall(),因为 send()recv() 有同样的问题:它不能保证所有数据都被发送出去。send() 会返回实际发送的字节数,程序员需要确保这个数字和要发送的数据长度一致。如果不一致,就得继续发送剩下的数据,直到全部发送完。发送完数据后,连接的一端会被关闭(用 shutdown()),这样服务器就知道客户端不会再发送数据了。之后,服务器会继续发送数据,直到它关闭自己的连接,这时 recv() 会返回一个空的字节对象。

这里有一个适合客户端的 socketserver.TCPServer

import json
from socketserver import StreamRequestHandler, TCPServer


class Handler(StreamRequestHandler):

    def handle(self):
        print('Handle request...')
        message = self.rfile.read().decode('utf-8')
        print('Received message:', message)
        self.wfile.write(
            json.dumps(
                {'name': 'John', 'age': 42, 'message': message}
            ).encode('utf-8')
        )
        print('Finished request.')



def main():
    address = ('127.0.0.1', 9999)
    try:
        print('Start server at', address, '...')
        server = TCPServer(address, Handler)
        server.serve_forever()
    except KeyboardInterrupt:
        print('Stopping server...')


if __name__ == '__main__':
    main()

它会从客户端读取完整的数据,并将其放入一个包含其他固定项的 JSON 编码响应中。与其直接进行底层的 socket 操作,不如利用 TCPServer 提供的更方便的文件对象来进行读写。连接会在 handle() 方法执行完后由 TCPServer 自动关闭。

5

你的代码问题在于,使用 recv(4096) 从 TCP 套接字接收数据时,返回的数据量可能和你预期的不一样,因为数据是按照数据包的边界切分的。

一个简单的解决办法是,在每条消息前面加上长度信息;发送时可以这样做:

import struct
packet = pickle.dumps(foo)
length = struct.pack('!I', len(packet)
packet = length + packet

接收时可以这样:

import struct

buf = b''
while len(buf) < 4:
    buf += socket.recv(4 - len(buf))

length = struct.unpack('!I', buf)[0]
# now recv until at least length bytes are received,
# then slice length first bytes and decode.

不过,Python 的标准库已经提供了一个支持消息导向的序列化套接字的功能,也就是 multiprocessing.Connection,它可以轻松地使用 Connection.sendConnection.recv 来发送和接收序列化的数据。

因此,你可以这样编写服务器代码:

from multiprocessing.connection import Listener

PORT = 1234
server_sock = Listener(('localhost', PORT))
conn = server_sock.accept()

unpickled_data = conn.recv()

客户端代码可以这样写:

from multiprocessing.connection import Client

client = Client(('localhost', 1234))
client.send(['hello', 'world'])

撰写回答