Pickle EOFError:从套接字接收时输入耗尽
我正在运行一个非常简单的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 个回答
如果你想接收服务器发送的所有数据,直到它关闭连接,可以试试这个方法:
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
自动关闭。
你的代码问题在于,使用 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.send
和 Connection.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'])