为什么我的客户端套接字在第一次发送后失效?

2 投票
3 回答
1394 浏览
提问于 2025-04-17 22:00

我正在尝试写一个简单的客户端套接字脚本。我的代码如下:

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 个回答

0

我只对客户端做了以下更改:

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

但是在这个解决方案中,我需要建立三次连接。有没有办法只建立一次连接,然后发送所有数据呢?

2

我觉得第一次发送的时候,三个数据都被发送了,但只有一个被加载。

我把服务器的 accept() 移到了 while 循环外面,并在客户端的发送循环里加了一个 sleep(0.01)。这样,updated_data 按照预期增长了。

这是一个一次性的解决方案,因为服务器只接受一个连接。

4

我刚刚测试了你的代码,发现你从循环中发送的所有消息都被服务器当作一条消息接收,而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也按预期增长。

撰写回答