为什么我的客户端套接字在第一次发送后失效?
我正在尝试写一个简单的客户端套接字脚本。我的代码如下:
import pickle
import socket
pole = ["mail","firewall","fria"]
c=False
while c==False:
try:
client = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
client.connect ( ( 'localhost', 1234 ) )
print("established")
c=True
except:
print(".")
i = (len(pole)-1)
while i>=0:
client.send(pickle.dumps(pole[i]))
pole.remove(pole[i])
i-=1
client.close()
在另一边是一个“永远”运行的服务器。但是服务器只接收到一条数据。为什么没有接收到所有的字段?客户端的“while”循环应该运行三次,所以它应该发送所有的数据(["mail","firewall","fria"])。这是服务器的输出:
然后客户端就结束了。为什么?客户端应该在发送完所有数据后再结束。为什么在发送一次后连接就关闭了?
谢谢你的帮助
编辑 ---> server.py:
import pickle
import socket
updated_data = []
server = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
server.bind ( ( '', 1234 ) )
server.listen ( 5 )
while 1:
channel, details = server.accept()
print ('Received connection:', details [ 0 ])
updated_data.append(pickle.loads(channel.recv(1024)))
print (updated_data)
channel.close()
3 个回答
我只对客户端做了以下更改:
i = (len(pole)-1)
while i>=0:
c=False
while c==False:
try:
client = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
client.connect ( ( 'localhost', 1234 ) )
print("established")
c=True
except:
print(".")
client.send(pickle.dumps(pole[i]))
pole.remove(pole[i])
i-=1
但是在这个解决方案中,我需要建立三次连接。有没有办法只建立一次连接,然后发送所有数据呢?
我觉得第一次发送的时候,三个数据都被发送了,但只有一个被加载。
我把服务器的 accept() 移到了 while 循环外面,并在客户端的发送循环里加了一个 sleep(0.01)。这样,updated_data 按照预期增长了。
这是一个一次性的解决方案,因为服务器只接受一个连接。
我刚刚测试了你的代码,发现你从循环中发送的所有消息都被服务器当作一条消息接收,而pickle
只加载了其中的第一条。简单来说,接收到的消息是
b'\x80\x03X\x04\x00\x00\x00friaq\x00.\x80\x03X\x08\x00\x00\x00firewallq\x00.\x80\x03X\x04\x00\x00\x00mailq\x00.'
你可以看到,所有的pole
元素都在这个字符串里。这意味着客户端在连接关闭之前发送了所有数据,而服务器也接收到了客户端的所有数据。
解决这个问题的方法是给消息加上“边界”,也就是要明确区分每条消息的开始和结束。这个问题相对比较常见(你可以在谷歌上搜索tcp 消息边界和tcp 消息框架)。
我觉得这篇博客非常有帮助。它提到:
常用的消息框架有两种方法:长度前缀和分隔符。
长度前缀是指在每条消息前面加上这条消息的长度。长度前缀的格式(和长度)必须明确说明;“4字节有符号小端”格式(也就是C#中的“int”)是一个常见的选择。发送消息时,发送方首先将消息转换为字节数组,然后发送字节数组的长度,接着发送字节数组本身。
接收带长度前缀的消息比较复杂,因为可能会出现部分接收的情况。首先,必须将消息的长度读入一个缓冲区,直到缓冲区满(例如,如果使用“4字节有符号小端”,这个缓冲区就是4字节)。然后分配一个第二个缓冲区,将数据读入这个缓冲区。当第二个缓冲区满时,就表示一条完整的消息已经到达,然后再去读取下一条消息的长度。
分隔符的处理更复杂。发送时,数据中的任何分隔符字符必须被替换,通常会使用转义函数。接收代码无法预测即将到来的消息大小,因此必须将所有接收到的数据追加到接收缓冲区的末尾,并根据需要扩展缓冲区。当找到分隔符时,接收方可以对接收缓冲区应用反转义函数来获取消息。如果消息中永远不会包含分隔符,那么可以跳过转义和反转义函数。
在你的具体情况下,我会使用Python的list
(作为“自动分隔的容器”),并用pickle
进行序列化,因此我需要稍微修改一下你的代码。
客户端
import pickle
import socket
pole = ["mail","firewall","fria"]
c = False
while not c:
try:
client = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
client.connect ( ( 'localhost', 1234 ) )
print("established")
c = True
except:
print(".")
# - i = (len(pole)-1)
# - import time
# - while i>=0:
# - client.send(pickle.dumps(pole[i]))
# - pole.remove(pole[i])
# - i-=1
# +
client.send(pickle.dumps(pole))
client.close()
服务器
import pickle
import socket
updated_data = []
server = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
server.bind ( ( '', 1234 ) )
server.listen ( 5 )
while 1:
channel, details = server.accept()
print ('Received connection:', details[0])
data = channel.recv(1024)
if data:
# Deserialize
data = pickle.loads(data)
# We're able to extend the list with another list:
# [1, 2, 3].extend([4, 5, 6]) is [1, 2, 3, 4, 5, 6]
updated_data.extend(data)
print(updated_data)
channel.close()
现在代码可以正常工作,服务器输出
Received connection: 127.0.0.1
['mail', 'firewall', 'fria']
而updated_data
也按预期增长。