Python发送图像时出现套接字错误。无法解码字节/意外的EOF

3
我使用Python socket从我的笔记本电脑的摄像头(客户端)传输图像到我的树莓派(服务器)。下面是具体步骤:首先连接socket,然后客户端等待信号以拍照。当来自服务器的信号发送时,客户端会拍照,并按顺序发送以下数据:首先是代表图片大小的数字的位数长度(例如,对于10000字节,数字为5,对于100000字节,数字为6等),然后发送实际大小,并将照片作为字节串发送。该过程无限重复。
Client.py
import cv2
import socket
import os

def send_msg(s, msg):
    s.sendall(msg)


s = socket.socket()
port = 24999
ip = '192.168.1.3'
s.connect((ip, port))

video = cv2.VideoCapture(0)

s.recv(1) #Wait until data is sent
while True:
    _, img = video.read()
    img = cv2.resize(img, (0,0), fx = 0.4, fy = 0.4) #reduce image size
    cv2.imwrite("test.jpg", img) #save img
    size = os.path.getsize("test.jpg") #get image size
    img_str = cv2.imencode('.jpg', img)[1].tostring() #convert to bytes string
    sizenum = str(len(str(size))) #how many digits the image size contains
    send_msg(s, sizenum.encode())
    send_msg(s, str(size).encode('utf-8')) #send actual image size
    send_msg(s, img_str) #finally send the image
    s.recv(1) #Wait until data is sent

Server.py

import socket
import ast

def send_msg(client, msg):
    client.sendall(msg+b'\r\n')

def recvall(sock, n):
    # Helper function to recv n bytes or return None if EOF is hit
    data = bytearray()
    while len(data) < n:
        packet = sock.recv(n - len(data))
        if not packet:
            return None
        data.extend(packet)
    return data

def take_photo(cl):
    #RECEIVE LENGTH OF SIZE
    while True:
        lensize = cl.recv(1)
        lensize = lensize.decode('utf-8')
        if lensize != "":
            break
    print("Size is a",lensize,"-digit number")
    lensize = lensize.replace("\n","").replace(" ","").replace("\r","")
    #RECEIVE SIZE
    size_pic = recvall(cl,ast.literal_eval(lensize)).decode('utf-8')
    size_pic = size_pic.replace("\n","").replace(" ","").replace("\r","")
    print("Exact size is",size_pic)
    #RECEIVE PHOTO
    return lensize,size_pic,bytearray(recvall(cl,ast.literal_eval(size_pic)))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("192.168.1.3",24999))
s.listen(1)

while True:
    cl_image, addr = s.accept()
    break

while True:
    try:
        send_msg(cl_image, b"1") #Send signal
        size1, size2, photo = take_photo(cl_image)
        print(photo)

    except KeyboardInterrupt:
        print("error")
        s.close()
问题 运行服务器后,几秒钟后发生了问题(有时仅运行一秒钟即抛出任何异常,有时运行5秒或更长时间)。

示例运行1(服务器)

Size is a 5 -digit number
Exact size is 21263
7294 from 21263 Not all bytes were read
18974 from 21263 Not all bytes were read
Size is a 5 -digit number
Exact size is 21226
2915 from 21226 Not all bytes were read
4375 from 21226 Not all bytes were read
11675 from 21226 Not all bytes were read
18975 from 21226 Not all bytes were read
Size is a 5 -digit number
Exact size is 21412
2915 from 21412 Not all bytes were read
7295 from 21412 Not all bytes were read
14595 from 21412 Not all bytes were read
Size is a . -digit number
Traceback (most recent call last):
  File "sending_test.py", line 46, in <module>
    size1, size2, photo = take_photo(cl_image)
  File "sending_test.py", line 27, in take_photo
    size_pic = recvall(cl,ast.literal_eval(lensize)).decode('utf-8')
  File "/usr/lib/python3.7/ast.py", line 46, in literal_eval
    node_or_string = parse(node_or_string, mode='eval')
  File "/usr/lib/python3.7/ast.py", line 35, in parse
    return compile(source, filename, mode, PyCF_ONLY_AST)
  File "<unknown>", line 1
    .

示例运行2(服务器)

Size is a 5 -digit number
Exact size is 20653
7294 from 20653 Not all bytes were read
14594 from 20653 Not all bytes were read
18974 from 20653 Not all bytes were read
Size is a 5 -digit number
Exact size is 20595
2915 from 20595 Not all bytes were read
8755 from 20595 Not all bytes were read
10215 from 20595 Not all bytes were read
18975 from 20595 Not all bytes were read
Traceback (most recent call last):
  File "sending_test.py", line 46, in <module>
    size1, size2, photo = take_photo(cl_image)
  File "sending_test.py", line 21, in take_photo
    lensize = lensize.decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte

我还在服务器的循环中插入了一个打印(photo)命令,以便查看正在发生的情况,这就是结果(请参见与其他行相比的最后一行)。
Size is a 3 -digit number
Exact size is 828
bytearray(b'\xff\xd8\xff.......\xff\xd9')
Size is a 3 -digit number
Exact size is 831
bytearray(b'\xff\xd8\xff......\xff\xd9')
Size is a 3 -digit number
Exact size is 831
bytearray(b'\xff\xd8\xff.......\xff\xd9383')

这意味着它读取了比应该多3个字节(因为3表示大小长度,83来自图像大小,大概是(83x))。

你使用 netstat 命令检查它们之间的连接了吗? - liorko
请再次阅读我的问题并查看我上传的示例运行。在一段时间内接收到字节(甚至制作完整的图像),但在一些循环后突然停止。 - Jordan
2
问题出在这一行:data += sock.receive(size),你不能一直请求 size 字节的数据。应该只请求仍然未接收到的消息内容,否则会抓取下一条消息的标头。因此请创建一个名为 bytesRemaining 的变量,在每次读取数据后从中减去已经接收到的字节数,然后只请求剩余的字节。 - Mark Setchell
@MarkSetchell 我更新了代码,但错误仍然存在。 - Jordan
我不明白的两件事是... 1)为什么你要将图像写入磁盘,然后获取大小,再调用imencode()将其转换回JPEG格式?2)为什么不像其他人一样发送图像长度作为网络顺序的4字节整数?(请参见我的早期评论) - Mark Setchell
显示剩余5条评论
1个回答

2

接下来根据我提出的评论作了以下几点修改:

  • 不再将JPEG图片写入磁盘,然后计算其大小,再编码到内存中并转换为字符串发送。而是直接将JPEG编码到内存缓冲区中,获取其大小并发送。

  • 不再发送一个字符串来表示第二个字符串中的字节数,而是直接以4字节网络序整数的形式发送字节数。这样更加方便。

在我的机器上似乎非常可靠。

以下是客户端代码:

#!/usr/bin/env python3

import cv2
import socket
import os
import struct

ip, port = '192.168.0.8', 24999
s = socket.socket()
s.connect((ip, port))

# Start video reader
video = cv2.VideoCapture(0)

while True:
    # Wait till data requested, as indicated by receipt of single byte
    s.recv(1)
    print('CLIENT: Image requested')

    # Read a frame of video and reduce size
    _, img = video.read()
    img = cv2.resize(img, (0,0), fx = 0.4, fy = 0.4)

    # JPEG-encode into memory buffer and get size
    _, buffer = cv2.imencode('.jpg', img)
    nBytes = buffer.size
    print(f'CLIENT: nBytes={nBytes}')

    # Send 4-byte network order frame size and image
    hdr = struct.pack('!i',nBytes)
    s.sendall(hdr)
    s.sendall(buffer)

这是服务器:

#!/usr/bin/env python3

import time
import socket
import struct

def recvall(sock, n):
    # Helper function to recv n bytes or return None if EOF is hit
    data = bytearray()
    while len(data) < n:
        packet = sock.recv(n - len(data))
        if not packet:
            return None
        data.extend(packet)
    return data

def take_photo(cl):
    # Get header with number of bytes
    header = cl.recv(4)
    nBytes = struct.unpack('!i',header)[0]

    # Receive actual image
    img = recvall(cl, nBytes)
    return img

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("192.168.0.8",24999))
s.listen(1)

while True:
    cl_image, addr = s.accept()
    break

while True:
    try:
        # Request image by sending a single byte
        cl_image.sendall(b'1')
        photo = take_photo(cl_image)
        time.sleep(1)
        print(f'SERVER: photo received, {len(photo)} bytes')

    except KeyboardInterrupt:
        print("error")
        s.close()

关键词: TCP 套接字,流式,基于流式的,基于消息的,分帧协议,帧,htonl,网络顺序,数据包,质数。


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