使用TCP套接字传输图像时缺失一个像素行

5

我现在遇到了一个奇怪的错误。我有一个使用TCP套接字发送/接收数据的Python脚本,一切正常,但是当我尝试使用这个脚本下载图像时,它会下载,但是缺少一个像素行。你有什么解决方法吗?

服务器下载脚本:

    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

问题出在发送“complete”字符串到服务器上,因为脚本没有足够的时间来获取图像中的所有字节。所以修复此问题的一种方法是向脚本添加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是一种没有消息边界的流协议。这意味着多个发送可以在一个接收调用中接收,或者一个发送可以在多个接收调用中接收。

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

– Maxim Egorushkin

服务器下载脚本:

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)

1
如果在将最后一块数据写入nFile之前删除负载的complete部分,会发生什么? - mtrw
我猜测额外的字节让图像解析器感到困惑。图像头可能包含了一些有关预期字节数的信息,而文件大小超过头部指定的大小会导致奇怪的行为。图像显示可能已经崩溃,也可能通过忽略额外的字节来正确显示,真的很难说会发生什么。 - mtrw
int(file_len[0]) 中进行 int 转换是不必要的,因为它已经是一个 int。你还应该检查 send 的返回值,因为不能保证会发送整个缓冲区。换句话说,使用循环来实现 sendall 函数。 - Maxim Egorushkin
嗯,在我的程序中,当我解包file_len时,它是一个元组。 - Jan Vodenka
1
@JanVodenka 我的意思是 int(...) 部分是不必要的。只需要 file_len = struct.unpack('<I', msg_header)[0] 即可。虽然这只是一个小细节... - Maxim Egorushkin
显示剩余5条评论
1个回答

3
问题在于向服务器发送“complete”字符串时,脚本没有足够的时间从图像中获取所有字节。因此,解决此问题的一种方法是将sleep(0.2)添加到脚本中。
TCP是一种没有消息边界的流协议。这意味着多个发送可能在一个接收调用中接收,或一个发送可以在多个接收调用中接收。
延迟解决方法可能无法可靠地工作。您需要在流中分隔消息。
有两种在流中分隔消息的常见方法:
1.使用标头前缀消息。 2.以后缀结束消息。
由于您正在发送二进制数据,因此任何后缀都可以自然地出现在有效负载中。除非后缀比有效负载更长,这是不实际的。
因此,在此处您可能希望在有效负载前加上固定大小的标头。在这种特殊情况下,具有4字节二进制文件长度的标头就足够了。例如:
file_len = os.stat(filename).st_size
msg_header = struct.pack('<I', file_len)
self.s.sendall(msg_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个字节,因此循环从未激活。你能否请再详细解释一下呢? - Jan Vodenka
@JanVodenka recv(n) 可能读取少于 n 字节,这就是为什么必须在循环中调用 recv 直到恰好读取了 n 个字节。人们经常将此循环提取为 recvall 函数。有很高的可能性在一个 recv 调用中读取小的 n,并且 while 循环不执行,但这并不保证。 - Maxim Egorushkin
好的,我已经成功让它工作了,编辑了解决方案,谢谢你的建议! - Jan Vodenka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接