Python socket crossplatform EOF错误

时间:2017-12-20 07:29:03

标签: python-3.x sockets

我正在创建一个客户端和服务器发送彼此消息的程序。我想让它成为跨平台,所以Linux操作系统可以发送消息到Windows操作系统,反之亦然签证。在同一个平台上一切正常,但当我想发送到另一个平台时,我遇到了一个EOF错误。

服务器

import socket
import sys
import pickle
import struct

HOST = '192.168.168.116'  # Symbolic name, meaning all available interfaces
PORT = 8889  # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('Socket created')

# Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error as msg:
    print('Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1])
    sys.exit()

print('Socket bind complete')

# Start listening on socket
s.listen(10)
print('Socket now listening')

# now keep talking with the client

while 1:
    # wait to accept a connection - blocking call
    conn, addr = s.accept()
    print('Connected with ' + addr[0] + ':' + str(addr[1]))
    size = struct.calcsize("L")
    size = conn.recv(size)
    size = socket.ntohl(struct.unpack("L", size)[0])
    buff = ""
    final = ""
    while len(buff) < size:
        buff = conn.recv(size - len(buff))
        final = pickle.loads(buff)
    print(final)
s.close()

客户端

import socket
import sys
import pickle
import struct


host = '192.168.168.116'
port = 8889
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setblocking(1)
sock.connect((host, port))
names = ["hello", "john", "hi", 55555, "end"]
buffer = pickle.dumps(names)
value = socket.htonl(len(buffer))
size = struct.pack("L", value)
sock.send(size)
sock.send(buffer)
print(buffer)

客户端在ubuntu和Windows 10上的服务器上运行。正如我提到的,当我在同一平台上执行这些脚本时,一切正常

1 个答案:

答案 0 :(得分:1)

您的服务器端循环搞砸了。假设为了参数,正在发送的缓冲区的大小是100。

buff = ""
final = ""
while len(buff) < size:
    buff = conn.recv(size - len(buff))
    final = pickle.loads(buff)

现在让我们假设您对recv的第一次调用返回五个字节,因此在该语句的末尾,数据的前五个字节位于buff。然后你在缓冲区上调用pickle.loads。但是,您的缓冲区中还没有完整的pickle字节字符串,因此loads会引发EOFError

此外,你的循环的下一次迭代不是追加到当前缓冲区,而是替换(覆盖)它。因此,如果您在第一次迭代中获得的字节少于size个字节(并且您的loads还没有被淘汰),那么您永远不会离开循环。

现在,为什么你的整个缓冲区没有作为一个单元交付我并不完全清楚(这绝不是保证但通常使用一个小缓冲区,它作为一个单元交付)。

无论如何,你的接收循环应该是这样的:

buff = b""             # For python3, recv returns bytes not str
while len(buff) < size:
    tbuf = conn.recv(size - len(buf))
    if not tbuf:
        raise MyException("Connection closed before all data received")
    buff += tbuf
final = pickle.loads(buff)

这样,您可以根据需要通过多次recv调用累积缓冲区 - 从而确保在尝试pickle.loads之前已经收到了整个缓冲区。

其他一些可能有帮助的说明:

  1. 虽然send通常会将大多数短缓冲区作为一个整体提供,但也不能保证。因此,在客户端,您应该使用sendall来确保发送整个缓冲区。

  2. 使用struct在系统之间序列化数据时,指定字节顺序很重要。如果你没有指定一个,那么&#34; native&#34;使用当前平台的字节排序。如果发送系统和接收系统不同意,那么您发送的size将进行字节交换,这在接收方将是无意义的。您可以使用您喜欢的任何字节顺序,但常见的惯例是使用&#34;网络字节顺序&#34;系统之间的(大端)。这只是意味着使用struct.pack("!L", value) - 并且在unpack中也是如此。 (在这种情况下,您的ubuntu系统和您的Windows系统可能都具有相同的本机字节顺序,因此这里可能无法解释您的问题,但始终指定的是最佳做法无论如何都是字节顺序。)

  3. 您可能希望在服务器一侧使用SO_REUSEADDR套接字选项,而不是客户端。服务器端是每次启动时将重新使用相同端口的服务器端。 (客户端不是bind因此操作系统将为其选择一个未使用的端口,您永远不需要SO_REUSEADDR。)

  4. 您不需要在服务器端指定IP地址。如果您提供空白主机名(HOST = ''),则可以在所有有效服务器IP地址上accept