【问题标题】:Missing one-pixel row while transfering image with TCP socket使用 TCP 套接字传输图像时缺少一个像素行
【发布时间】:2020-04-03 18:55:58
【问题描述】:

我现在遇到一个奇怪的错误,我有 python 脚本,使用 TCP 套接字发送/接收数据,一切正常,但是当我尝试使用这个脚本下载图像时,它会下载它,但是缺少一个像素行。关于如何解决它的任何想法?

服务器下载脚本:

    def download(self, cmd):
        try:
            self.c.send(str.encode(cmd))
            command,filename=cmd.split(' ')
            nFile = open(filename, 'wb')
            i = self.c.recv(1024)
            while not ('complete' in str(i)):   
                nFile.write(i)
                i = self.c.recv(1024)
            nFile.close()
            self.reset = True
            print('\nGot that file')
        except Exception as e:
            print(e)

客户端上传脚本:

   def upload(self, filename):
    try:
        fd = open(filename, 'rb')
        data = fd.read(1024)
        while (data):
            self.s.sendall(data)
            data = fd.read(1024)
        self.s.send(str.encode('complete'))
        fd.close()
    except Exception as e:
        print(e)

示例 - 你可以看到,最后一行像素丢失了:

解决方案(1): 这不是解决方案,只是解决方法,使用另一个解决方案!

如果您之前删除了有效负载的完整部分会发生什么 将最后一块数据写入nFile? – mtrw

问题在于向服务器发送“完整”字符串,因为脚本没有足够的时间从图像中获取所有字节。因此,解决此问题的一种方法是将 sleep(0.2) 添加到脚本中。

客户端上传脚本:

   def upload(self, filename):
try:
    fd = open(filename, 'rb')
    data = fd.read(1024)
    while (data):
        self.s.sendall(data)
        data = fd.read(1024)
    sleep(0.2)
    self.s.send(str.encode('complete'))
    fd.close()
except Exception as e:
    print(e)

解决方案(2):

TCP 是一种没有消息边界的流协议。这意味着 一个recv调用可以接收多个发送,或者一个发送可以是 在多个 recv 调用中收到。

延迟解决方法可能无法可靠地工作。你需要划定 流中的消息。

– 马克西姆·埃戈鲁什金

服务器下载脚本:

try:
    msg_header = self.c.recv(4)
    while len(msg_header) != 4:
        msg_header += self.c.recv(4 - len(msg_header))
    file_len = struct.unpack('<I', msg_header)[0]
    nFile = open(filename, 'wb')
    data = self.c.recv(file_len)
    while len(data) != file_len:
        data += self.c.recv(file_len - len(data))
    nFile.write(data)
    nFile.close()
    print('\nGot that file')
except Exception as e:
    print(e)

客户端上传脚本:

try:
    file_len = os.stat(filename).st_size
    msg_header = struct.pack('<I', file_len)
    self.s.sendall(msg_header)
    fd = open(filename, 'rb')
    data = fd.read(file_len)
    while (data):
        self.s.sendall(data)
        data = fd.read(file_len)
    fd.close()
except Exception as e:
    print(e)

【问题讨论】:

  • 如果在将最后一个数据块写入nFile 之前删除负载的complete 部分会发生什么?
  • 哇...这么简单的解决方法,我现在感觉很愚蠢。但是,我仍然不明白为什么实际上将“创建”字解析到图像文件中,当它必须检查接收到的字节是否是“下载”时,如果它必须停止写入会话。
  • 我猜额外的字节会混淆图像解析器。图像标头可能包含有关预期字节数的一些信息,并且大于指定标头的文件会导致奇怪的行为。图像显示可能已经崩溃,可能通过忽略额外的字节显示正确的东西,真的不可能说会发生什么
  • 不需要在int(file_len[0]) 中转换为int,它已经解压了int。您还应该检查send 的返回值,因为不能保证发送整个缓冲区。换句话说,用循环实现sendall函数。
  • @JanVodenka 我是说int(...) 位是不必要的。只是file_len = struct.unpack('&lt;I', msg_header)[0]。虽然这是一件小事......

标签: python tcp


【解决方案1】:

问题在于向服务器发送“完整”字符串,因为脚本没有足够的时间从图像中获取所有字节。因此,解决此问题的一种方法是将 sleep(0.2) 添加到脚本中。

TCP 是一种没有消息边界的流协议。这意味着在一个recv 调用中可以接收多个sends,或者在多个recv 调用中可以接收一个send

延迟解决方法可能无法可靠地工作。您需要在流中分隔消息。

在流中划分消息有两种常用方法:

  1. 为消息加上标题前缀。
  2. 用后缀结束消息。

由于您要发送二进制数据,因此任何后缀都可以自然地出现在有效负载中。除非后缀长于有效载荷,否则不实用。

因此,您可能希望在这里为您的有效负载添加一个固定大小的标头。在这种特殊情况下,具有 4 字节二进制文件长度的标头就足够了。例如:

file_len = os.stat(filename).st_size
msg_header = struct.pack('<I', file_len)
self.s.sendall(msg_header)

接收方需要先读取header:

msg_header = self.s.recv(4)
while len(msg_header) != 4:
    msg_header += self.s.recv(4 - len(msg_header))
file_len = struct.unpack('<I', msg_header)

然后从套接字中准确读取file_len

了解所接收文件的大小还允许您预分配缓冲区以避免内存重新分配和/或预分配整个文件以最大限度地减少磁盘碎片或避免文件传输开始后出现磁盘空间不足的错误。

【讨论】:

  • 所以我已经成功地获得了文件的大小,但是我现在如何设置 while 循环,以保持循环直到下载整个图像?我错过了那个 msg_header 的要点,它看起来里面总是有 4 个字节,所以循环永远不会处于活动状态。请您再解释一下好吗?
  • @JanVodenka recv(n) 可能读取少于n,这就是为什么必须在循环中调用recv 直到准确读取n 字节。人们经常将此循环提取为recvall 函数。很有可能在一个 recv 调用中读取小 n 并且 while 循环不会执行,但这不能保证。
  • 好吧,我已经设法让它工作了,然后编辑了解决方案,感谢您的建议!
猜你喜欢
  • 2015-01-08
  • 2013-04-17
  • 2012-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-30
  • 2012-01-12
相关资源
最近更新 更多