我正在创建一个客户端和服务器发送彼此消息的程序。我想让它成为跨平台,所以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上的服务器上运行。正如我提到的,当我在同一平台上执行这些脚本时,一切正常
答案 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
之前已经收到了整个缓冲区。
其他一些可能有帮助的说明:
虽然send
通常会将大多数短缓冲区作为一个整体提供,但也不能保证。因此,在客户端,您应该使用sendall
来确保发送整个缓冲区。
使用struct
在系统之间序列化数据时,指定字节顺序很重要。如果你没有指定一个,那么&#34; native&#34;使用当前平台的字节排序。如果发送系统和接收系统不同意,那么您发送的size
将进行字节交换,这在接收方将是无意义的。您可以使用您喜欢的任何字节顺序,但常见的惯例是使用&#34;网络字节顺序&#34;系统之间的(大端)。这只是意味着使用struct.pack("!L", value)
- 并且在unpack
中也是如此。 (在这种情况下,您的ubuntu系统和您的Windows系统可能都具有相同的本机字节顺序,因此这里可能无法解释您的问题,但始终指定的是最佳做法无论如何都是字节顺序。)
您可能希望在服务器一侧使用SO_REUSEADDR
套接字选项,而不是客户端。服务器端是每次启动时将重新使用相同端口的服务器端。 (客户端不是bind
因此操作系统将为其选择一个未使用的端口,您永远不需要SO_REUSEADDR
。)
您不需要在服务器端指定IP地址。如果您提供空白主机名(HOST = ''
),则可以在所有有效服务器IP地址上accept
。