【问题标题】:Python TCP socket send receive large delayPython TCP套接字发送接收延迟大
【发布时间】:2018-10-12 19:03:58
【问题描述】:

我使用 python 套接字在我的 Raspberry Pi 3 (Raspbian) 上创建了一个服务器,并在我的笔记本电脑 (Windows 10) 上创建了一个客户端。服务器以 10fps 的速率将图像流式传输到笔记本电脑,如果我推送它可以达到 15fps。问题是当我希望笔记本电脑根据图像发回命令时,帧速率急剧下降到 3fps。流程是这样的:

Pi 发送 img => 笔记本电脑接收 img => 快速处理 => 根据处理结果发送命令 => Pi 接收命令,打印它 => Pi 发送 img => ...

每帧的处理时间不会导致这种情况(每帧最多0.02s),所以目前我不知道为什么帧率下降这么多。图像相当大,大约 200kB,命令只是一个 3B 的短字符串。图像为矩阵形式,发送前进行腌制,命令按原样发送。

谁能解释一下为什么发回这么短的命令会使帧率下降这么多?如果可能的话,这个问题的解决方案。我尝试做了两台服务器,一台专门用于发送图像,一台用于接收命令,但结果是一样的。

服务器:

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

【问题讨论】:

  • 这取决于您发送数据的方式,但您可能会遇到buffering issues
  • How to create a Minimal, Complete, and Verifiable example。如果您可以消除问题(即使您制作了发送大量随机数据的服务器/客户端),那么其他人可以尝试重现您的问题。如果没有看到演示问题的代码,除了推测之外,其他任何事情都将非常困难
  • @Tom Dalton 谢谢你,我尽量把它去掉。现在这仅包含发送图像和接收图像,然后发送命令和接收命令。不包括其他作业,它仍然以 3 fps 运行。如果我删除 send-receive 命令部分,它会立即跳回到 10 fps。

标签: python sockets


【解决方案1】:

我认为问题有两个方面。首先,您正在序列化所有活动:服务器正在发送一个完整的图像,而不是继续发送下一个图像(这更符合“流”的定义),它正在停止,等待前一个的所有字节图像通过网络到达客户端,然后客户端接收图像的所有字节,解压缩它,发送响应,然后响应通过线路到达服务器。

您是否有理由需要他们像这样步调一致?如果不是,请尝试使两侧平行。让您的服务器创建一个单独的线程来侦听返回的命令(或简单地使用select 来确定命令套接字何时有东西要接收)。

其次,您可能会被 Nagle 的算法 (https://en.wikipedia.org/wiki/Nagle%27s_algorithm) 所困扰,该算法旨在防止通过网络发送大量具有小负载(但开销很大)的数据包。因此,您的客户端内核已经获取了您的三字节命令数据并将其缓冲,等待您在将数据发送到服务器之前提供更多数据(它最终还是会在延迟后发送它)。要改变这一点,您需要在客户端使用TCP_NODELAY 套接字选项(请参阅https://stackoverflow.com/a/31827588/1076479)。

【讨论】:

  • 其实是的,我就是用那些车架来控制一辆小型电动车。如果命令与帧不同步,那将是灾难性的。关于Nagle的算法问题,我明天会尝试,因为所有硬件都在大学里。谢谢!
猜你喜欢
  • 2013-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多