【问题标题】:Pickle EOFError: Ran out of input when recv from a socketPickle EOFError:从套接字接收时用尽输入
【发布时间】:2014-07-13 19:43:20
【问题描述】:

我正在为一个学校项目(不适用于现实世界)运行一个非常简单的 python (3.x) 客户端-服务器程序(都在我的 PC 上本地),它只是来回发送消息(如视图客户,添加客户,删除客户等......真正的基本)。

有时数据可以是我存储为 namedTuples 的多条记录(这很有意义),然后沿着使用 Pickle 传输的路径走下去。

例如,在客户端上我会这样做:

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

现在我经常收到以下错误:

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

我担心这与我的套接字 (TCP) 传输有关,也许不知何故我没有及时为我的 pickle.loads 获取所有数据 - 有意义吗?如果不是这样,我真的不知道为什么会如此不一致地发生。

但是,即使我是对的,我也不确定如何(快速)修复它,我正在考虑放弃泡菜并只使用字符串(但这不会遭受同样的命运吗)?有人有什么建议吗?

真的,我的信息非常基本 - 通常只是一个命令和一些小数据,如“1=John”,这意味着命令 (1),它是 FIND 命令,然后是“John”,它返回记录(姓名、年龄等...) 的 John(作为 namedTuple - 但老实说这不是强制性的)。

任何建议或帮助将不胜感激,正在寻找快速解决方案...

【问题讨论】:

  • 如果来源不受信任,则不应使用 pickle,并且在网络上,请不要信任任何人,引用文档:“警告 pickle 模块并非旨在防止错误或恶意构造的数据。永远不要解开从不受信任或未经身份验证的来源收到的数据。"
  • 您可能会考虑自己不使用套接字的可能性(自己使用套接字意味着要注意消息长度,您在这里没有做什么)。我将提倡使用 HTTP、JSON 消息,并且我们在这里,所有这些 RESTful。
  • @JulienPalard 鉴于这是一个学校项目,“受信任”的想法并不重要,项目要求是使用套接字 (TCP) 进行通信 - 遗憾的是我无法改变这一点
  • @JulienPalard 知道它都是本地的(在同一台 PC 上),没有办法解决处理长度的问题吗?如果我使用 STRINGS 而不是 Pickled 数据会怎样?有没有其他方法可以避免这个问题?
  • 您可以使用 pickle 版本 1(人类可读)或 json 轻松调试,您会看到哪里出了问题。关闭连接是可以的,不必关心长度。

标签: python sockets pickle


【解决方案1】:

您的代码的问题在于,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 标准库已经支持面向消息的 pickling 套接字,即multiprocessing.Connection,它支持分别使用Connection.sendConnection.recv 轻松发送和接收pickle。

因此您可以将您的服务器编码为

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'])

【讨论】:

  • 我认为直接使用socket 是分配所施加的限制的一部分。 :-)
  • 无论如何,对于任何想要在任务之外执行此操作的人,这是在 python 中执行此操作的方法;)
  • 或者如果你很聪明,你可以从multiprocessing/connection.py复制东西:P
  • @AnttiHaapala 可以在服务器以某种方式使用 socketserver 时做同样的事情吗?
  • 第一个,当然,只是发送实际消息成功的消息长度。
【解决方案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 编码的响应中。它使用TCPServer 提供的更方便的文件(如对象)来从/向连接读取和写入,而不是低级别的套接字操作。在handle()方法完成后,连接被TCPServer关闭。

【讨论】:

  • 明确一点,SERVER在完成.sendall(...)之后会关闭客户端建立的连接吗?我正在使用 SocketServer 并且我的所有代码都在 Handler 中处理......服务器如何终止客户端建立的连接?并且客户端不会收到他们的连接被切断的错误吗?
  • @JSchwartz StreamRequestHandler 在处理请求后关闭连接。客户端在从关闭的连接中读取时不会收到错误消息。正如我所说,recv() 然后返回一个空字节对象。也许 Python 文档中的 Socket Programming HOWTO 在这里会有所帮助。
  • @JSchwartz 我已将示例扩展为可运行代码,并为服务器端添加了代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-11
  • 1970-01-01
相关资源
最近更新 更多