我可以避免Python中的线程UDP套接字丢弃数据吗?

时间:2010-03-16 17:39:31

标签: python multithreading sockets gtk pygtk

首先,我是Python新手,在工作中学习,所以要温柔!

我正在尝试为Windows编写一个线程Python应用程序,从UDP套接字(thread-1)读取数据,将其写入文件(thread-2),并将实时数据(thread-3)显示为widget(gtk.Image使用gtk.gdk.pixbuf)。我正在使用队列在线程之间传递数据。

我的问题是,如果我只启动线程1和3(因此暂时跳过文件写入),似乎在前几个样本之后丢失了一些数据。这次下降后看起来很好。即使在运行线程3之前让线程1完成,这个明显的下降仍然存在。

为代码片段的长度道歉(我删除了写入文件的线程),但我觉得删除代码只会提示问题。希望有人能说清楚: - )

import socket
import threading
import Queue
import numpy
import gtk
gtk.gdk.threads_init()
import gtk.glade
import pygtk


class readFromUDPSocket(threading.Thread):

    def __init__(self, socketUDP, readDataQueue, packetSize, numScans):
        threading.Thread.__init__(self)
        self.socketUDP = socketUDP
        self.readDataQueue = readDataQueue
        self.packetSize = packetSize
        self.numScans = numScans

    def run(self):
        for scan in range(1, self.numScans + 1):
            buffer = self.socketUDP.recv(self.packetSize)
            self.readDataQueue.put(buffer)
        self.socketUDP.close()
        print 'myServer finished!'


class displayWithGTK(threading.Thread):

    def __init__(self, displayDataQueue, image, viewArea):
        threading.Thread.__init__(self)
        self.displayDataQueue = displayDataQueue
        self.image = image
        self.viewWidth = viewArea[0]
        self.viewHeight = viewArea[1]
        self.displayData = numpy.zeros((self.viewHeight, self.viewWidth, 3), dtype=numpy.uint16)

    def run(self):
        scan = 0
        try:
            while True:
                if not scan % self.viewWidth: scan = 0
                buffer = self.displayDataQueue.get(timeout=0.1)
                self.displayData[:, scan, 0] = numpy.fromstring(buffer, dtype=numpy.uint16)
                self.displayData[:, scan, 1] = numpy.fromstring(buffer, dtype=numpy.uint16)
                self.displayData[:, scan, 2] = numpy.fromstring(buffer, dtype=numpy.uint16)
                gtk.gdk.threads_enter()
                self.myPixbuf = gtk.gdk.pixbuf_new_from_data(self.displayData.tostring(), gtk.gdk.COLORSPACE_RGB,
                                                        False, 8, self.viewWidth, self.viewHeight, self.viewWidth * 3)
                self.image.set_from_pixbuf(self.myPixbuf)
                self.image.show()
                gtk.gdk.threads_leave()
                scan += 1
        except Queue.Empty:
            print 'myDisplay finished!'
            pass


def quitGUI(obj):
    print 'Currently active threads: %s' % threading.enumerate()
    gtk.main_quit()


if __name__ == '__main__':

    # Create socket (IPv4 protocol, datagram (UDP)) and bind to address
    socketUDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    host = '192.168.1.5'
    port = 1024
    socketUDP.bind((host, port))

    # Data parameters
    samplesPerScan = 256
    packetsPerSecond = 1200
    packetSize = 512
    duration = 1  # For now, set a fixed duration to log data
    numScans = int(packetsPerSecond * duration)

    # Create array to store data
    data = numpy.zeros((samplesPerScan, numScans), dtype=numpy.uint16)

    # Create queue for displaying from
    readDataQueue = Queue.Queue(numScans)

    # Build GUI from Glade XML file
    builder = gtk.Builder()
    builder.add_from_file('GroundVue.glade')
    window = builder.get_object('mainwindow')
    window.connect('destroy', quitGUI)
    view = builder.get_object('viewport')
    image = gtk.Image()
    view.add(image)
    viewArea = (1200, samplesPerScan)

    # Instantiate & start threads
    myServer = readFromUDPSocket(socketUDP, readDataQueue, packetSize, numScans)
    myDisplay = displayWithGTK(readDataQueue, image, viewArea)

    myServer.start()
    myDisplay.start()

    gtk.gdk.threads_enter()
    gtk.main()
    gtk.gdk.threads_leave()
    print 'gtk.main finished!'

5 个答案:

答案 0 :(得分:4)

UDP不验证目标收到它(就像TCP一样) - 如果要确保所有数据都到达,则必须在应用程序中实现重传等。你控制发送UDP源吗?

答案 1 :(得分:2)

根据定义,UDP 不可靠。您不得编写期望UDP数据报始终通过的程序。

数据包也一直在TCP中丢弃,但是您的程序不需要关心,因为TCP应用程序无法处理数据包; TCP堆栈向您的应用程序显示字节的。那里有很多机器可以确保如果你发送字节'ABCD',你会在结尾看到'A''B''C''D'。当然,您可能会得到任何可能的数据包集合:'ABC','D'或'AB',CD'等。或者您可能只看到'ABC',然后什么都没有。

TCP不“可靠”,因为它可以神奇地使您的网络电缆永不失败或中断;它提供的保证是直到流中断的点,你将按顺序看到一切。在溪流断裂后,你什么都看不见。

在UDP中没有这样的保证。如果您发送四个UDP数据报,'AB','CD','EF''GH',您可能会收到所有这些数据报,或者没有一个,或者其中一半,或者只是其中一个。您可以按任何顺序收到它们。 UDP尝试提供的唯一保证是您不会看到包含“ABCD”的消息,因为这些字节位于不同的数据报中。

总结一下:这与Python,线程或GTK无关。这只是基于物理现实的网络生活中的一个基本事实:有时,电线的电气特性不利于将信息传递到它们之间。

您可以使用Twisted,特别是the listenUDP API来降低程序的复杂性,因为这样您就不需要处理线程或与GTK的交互:您可以使用datagramReceived方法直接在相关小部件上调用方法。但这不会解决您的根本问题:UDP有时会丢弃数据,周期。真正的解决方案是说服您的数据源改为使用TCP。

答案 2 :(得分:-1)

编辑 - 敲出听/接受句子,谢谢丹尼尔,当我看到你的评论时,我刚刚将其删除:)

我建议这是一个网络编程问题,而不是python本身。

您已设置每秒数据包速率和持续时间,以定义您对UDP套接字执行的recv调用次数。 我没有看到listenaccept对套接字的调用,我假设recv处理那个,因为你说你收到了一些数据。你没有提到数据的生成。

你已经定义了你期望做多少次读取,所以我假设代码在退出之前会有很多读取,所以我的结论是你的recv packetSize不够,因此read不会拉动整个数据报,然后后续的recv会拉出前一个数据报的下一部分。

您不能查看收到的数据并确定缺少的数据吗?你输了什么数据?你怎么知道丢失了?

此外,您可以使用wireshark验证您的主机是否在验证数据报大小的同时实际接收数据。将捕获与您的recv线程提供的数据相匹配。


<强>更新

你说你丢失的数据,但不是它的数据。我看到了数据丢失的两种可能性:

  • 截断数据包
  • 丢弃数据包

您已经说过,有效负载大小与您传递给recv的大小相同,所以我会认为你没有截断它。

因此丢弃数据包的因素​​是接收速率,读取接收缓冲区速率和接收缓冲区大小的组合。

您拨打Queue.put的电话可能会降低您的阅读速度。

因此,首先确定您可以通过将readFromUDPSocket修改为Queue.put来读取每秒1200个数据包,但要计算接收次数和报告时间。

一旦您确定可以以足够快的速度致电recv,下一步就是找出让您失望的原因。我怀疑它可能是您使用Queue,我建议将有效负载分配到N个大小的组中以放置在Queue上,这样您就不会尝试以12Hz调用put。 / p>

看到你想维持每秒1200次读取的速度,我认为通过增加套接字上的接收缓冲区,你不会走得太远。

答案 3 :(得分:-1)

首先;你可以为套接字设置recv缓冲区大小吗?如果是这样,请将其设置为非常大的值,因为这将使UDP堆栈为您缓冲更多的数据报。

其次;如果你可以使用异步I / O,那么一次发布多个recv调用(同样这允许堆栈在它开始丢弃之前服务更多的数据报)。

第三;您可以尝试展开您的循环并在将它们放入队列之前读取多个数据报;队列上的锁定是否会导致recv线程慢慢运行?

最后;数据报可能会被丢弃在网络的其他地方,你可能没有什么可以做的,那就是UDP中的U ...

答案 4 :(得分:-1)

似乎问题出在源头上。有两个问题:

  1. 看看Wireshark,源不能始终如一地传输1200个数据包。可能正如Len指出的那样,出站堆栈丢失数据存在问题。 BTW源是一个可编程卡,以太网端口连接到我的机器。

  2. 另一个问题是,在前15个数据包之后,数据总会有所下降。我发现如果我在readFromUDPSocket线程的初始化部分中收集了20个数据包,那么我可以很好地读取数据,例如

  3. class readFromUDPSocket(threading.Thread):
    
        def __init__(self, socketUDP, readDataQueue, packetSize, numScans):
            threading.Thread.__init__(self)
            self.socketUDP = socketUDP
            self.readDataQueue = readDataQueue
            self.packetSize = packetSize
            self.numScans = numScans
            for i in range(0, 20):
                buffer = self.socketUDP.recv(self.packetSize)
    
        def run(self):
            for scan in range(1, self.numScans + 1):
                buffer = self.socketUDP.recv(self.packetSize)
                self.readDataQueue.put(buffer)
            self.socketUDP.close()
            print 'myServer finished!'
    

    不确定这指向什么?!我认为所有这些都排除了不能恢复和放得足够快的速度。