TCP-сокет Python отправляет получать большую задержку - PullRequest
0 голосов
/ 02 мая 2018

Я использовал python socket для создания сервера на моем Raspberry Pi 3 (Raspbian) и клиента на моем ноутбуке (Windows 10). Сервер передает изображения на ноутбук со скоростью 10 кадров в секунду и может достигать 15 кадров в секунду, если я нажму его. Проблема в том, что когда я хочу, чтобы ноутбук отправил обратно команду, основанную на изображении, частота кадров резко упала до 3 кадров в секунду. Процесс такой:

Pi send img => Ноутбук получит img => Быстрый процесс => Отправить команду на основе результата процесса => Команда Pi, распечатать ее => Pi send img => ...

Время обработки для каждого кадра не вызывает этого (не более 0,02 с для каждого кадра), поэтому в настоящее время я не понимаю, почему частота кадров так сильно падает. Изображение довольно большое, около 200 кБ, а команда представляет собой только короткую строку в 3B. Изображение находится в матричной форме и перед отправкой обрабатывается, а команда отправляется как есть.

Может кто-нибудь объяснить мне, почему при отправке такой короткой команды частота кадров так сильно падает? И если возможно, решение этой проблемы. Я попытался создать 2 сервера, один для отправки изображений и один для получения команды, но результат тот же.

Сервер:

import socket
import pickle
import time
import cv2
import numpy as np
from picamera.array import PiRGBArray
from picamera import PiCamera
from SendFrameInOO import PiImageServer

def main():
    # initialize the server and time stamp
    ImageServer = PiImageServer()
    ImageServer2 = PiImageServer()
    ImageServer.openServer('192.168.0.89', 50009)
    ImageServer2.openServer('192.168.0.89', 50002)

    # Initialize the camera object
    camera = PiCamera()
    camera.resolution = (320, 240)
    camera.framerate = 10 # it seems this cannot go higher than 10
                          # unless special measures are taken, which may
                          # reduce image quality
    camera.exposure_mode = 'sports' #reduce blur
    rawCapture = PiRGBArray(camera)

    # allow the camera to warmup
    time.sleep(1)

    # capture frames from the camera
    print('<INFO> Preparing to stream video...')
    timeStart = time.time()
    for frame in camera.capture_continuous(rawCapture, format="bgr",
                                           use_video_port = True):
        # grab the raw NumPy array representing the image, then initialize 
        # the timestamp and occupied/unoccupied text
        image = frame.array 
        imageData = pickle.dumps(image) 
        ImageServer.sendFrame(imageData) # send the frame data

        # receive command from laptop and print it
        command = ImageServer2.recvCommand()
        if command == 'BYE':
            print('BYE received, ending stream session...')
            break
        print(command)

        # clear the stream in preparation for the next one
        rawCapture.truncate(0) 

    print('<INFO> Video stream ended')
    ImageServer.closeServer()

    elapsedTime = time.time() - timeStart
    print('<INFO> Total elapsed time is: ', elapsedTime)

if __name__ == '__main__': main()

Клиент:

from SupFunctions.ServerClientFunc import PiImageClient
import time
import pickle
import cv2

def main():
    # Initialize
    result = 'STP'
    ImageClient = PiImageClient()
    ImageClient2 = PiImageClient()

    # Connect to server
    ImageClient.connectClient('192.168.0.89', 50009)
    ImageClient2.connectClient('192.168.0.89', 50002)
    print('<INFO> Connection established, preparing to receive frames...')
    timeStart = time.time()

    # Receiving and processing frames
    while(1):
        # Receive and unload a frame
        imageData = ImageClient.receiveFrame()
        image = pickle.loads(imageData)        

        cv2.imshow('Frame', image)
        key = cv2.waitKey(1) & 0xFF

        # Exit when q is pressed
        if key == ord('q'):
            ImageClient.sendCommand('BYE')
            break

        ImageClient2.sendCommand(result)

    ImageClient.closeClient()

    elapsedTime = time.time() - timeStart
    print('<INFO> Total elapsed time is: ', elapsedTime)
    print('Press any key to exit the program')
    #cv2.imshow('Picture from server', image)
    cv2.waitKey(0)  

if __name__ == '__main__': main()

PiImageServer и PiImageClient:

import socket
import pickle
import time

class PiImageClient:
    def __init__(self):
        self.s = None
        self.counter = 0

    def connectClient(self, serverIP, serverPort):
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.connect((serverIP, serverPort))

    def closeClient(self):
        self.s.close()

    def receiveOneImage(self):
        imageData = b''
        lenData = self.s.recv(8)
        length = pickle.loads(lenData) # should be 921764 for 640x480 images
        print('Data length is:', length)
        while len(imageData) < length:
            toRead = length-len(imageData)
            imageData += self.s.recv(4096 if toRead>4096 else toRead)
            #if len(imageData)%200000 <= 4096:
            #    print('Received: {} of {}'.format(len(imageData), length))
        return imageData

    def receiveFrame(self):        
        imageData = b''
        lenData = self.s.recv(8) 
        length = pickle.loads(lenData)
        print('Data length is:', length)
        '''length = 921764 # for 640x480 images
        length = 230563 # for 320x240 images'''
        while len(imageData) < length:
            toRead = length-len(imageData)
            imageData += self.s.recv(4096 if toRead>4096 else toRead)
            #if len(imageData)%200000 <= 4096:
            #    print('Received: {} of {}'.format(len(imageData), length))
        self.counter += 1
        if len(imageData) == length: 
            print('Successfully received frame {}'.format(self.counter))                
        return imageData

    def sendCommand(self, command):
        if len(command) != 3:
            print('<WARNING> Length of command string is different from 3')
        self.s.send(command.encode())
        print('Command {} sent'.format(command))


class PiImageServer:
    def __init__(self):
        self.s = None
        self.conn = None
        self.addr = None
        #self.currentTime = time.time()
        self.currentTime = time.asctime(time.localtime(time.time()))
        self.counter = 0

    def openServer(self, serverIP, serverPort):
        print('<INFO> Opening image server at {}:{}'.format(serverIP,
                                                            serverPort))
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.bind((serverIP, serverPort))
        self.s.listen(1)
        print('Waiting for client...')
        self.conn, self.addr = self.s.accept()
        print('Connected by', self.addr)

    def closeServer(self):
        print('<INFO> Closing server...')
        self.conn.close()
        self.s.close()
        #self.currentTime = time.time()
        self.currentTime = time.asctime(time.localtime(time.time()))
        print('Server closed at', self.currentTime)

    def sendOneImage(self, imageData):
        print('<INFO> Sending only one image...')
        imageDataLen = len(imageData)
        lenData = pickle.dumps(imageDataLen)
        print('Sending image length')
        self.conn.send(lenData)
        print('Sending image data')
        self.conn.send(imageData)

    def sendFrame(self, frameData):
        self.counter += 1
        print('Sending frame ', self.counter)
        frameDataLen = len(frameData)
        lenData = pickle.dumps(frameDataLen)        
        self.conn.send(lenData)        
        self.conn.send(frameData)

    def recvCommand(self):
        commandData = self.conn.recv(3)
        command = commandData.decode()
        return command

1 Ответ

0 голосов
/ 02 мая 2018

Я считаю, что проблема двоякая. Сначала вы сериализуете все действия: сервер отправляет полный образ, затем, вместо того, чтобы продолжить отправку следующего изображения (что лучше соответствовало бы определению «потоковой передачи»), он останавливается, ожидая всех байтов предыдущего изображение, чтобы сделать себя через сеть к клиенту, а затем для клиента, чтобы получить все байты изображения, распаковать его, отправить ответ и затем получить ответ через сервер на сервер.

Есть ли причина, по которой вам нужно, чтобы они были в таком положении? Если нет, попробуйте распараллелить две стороны. Пусть ваш сервер создаст отдельный поток для прослушивания возвращающихся команд (или просто используйте select, чтобы определить, когда командному сокету есть, что получить).

Во-вторых, вы, вероятно, укушены алгоритмом Нагла (https://en.wikipedia.org/wiki/Nagle%27s_algorithm),), который предназначен для предотвращения отправки множества пакетов с небольшими полезными нагрузками (но с большими накладными расходами) по сети. Итак, ваше клиентское ядро ​​получило ваши три байта командных данных и буферизировали их, ожидая, пока вы предоставите больше данных, прежде чем отправлять данные на сервер (в конечном итоге он отправит их в любом случае, после задержки). Чтобы изменить это, вы захотите использовать TCP_NODELAY опция сокета на стороне клиента (см. https://stackoverflow.com/a/31827588/1076479).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...