UDP Datenstau vermeiden

  • Hi, ich hab' mal wieder eine Frage:
    Im Netzwerk habe ich ein Gerät, dieses Gerät sendet Messwerte an einen PC im Netzwerk per UDP. Ein AutoItscript empfängt die Daten und wertet sie aus und zeigt sie live an:

    [autoit]

    ; ...
    Do
    $data = UDPRecv($socket, 50)
    If $data <> "" Then
    ; Auswertung und Anzeige
    EndIf
    Until GUIGetMsg() = -3

    [/autoit]

    Das Problem bei der Sache ist: Das Script funktioniert nur gescheit, wenn die Frequenz, mit der das Messgerät sendet, 10 Hz nicht übersteigt (die Frequenz ist einstellbar). Sobald ich die Sendefrequenz erhöhe, kommt es zu einem Datenstau. Das heißt: Das Messgerät sendet mit einer Frequenz von sagen wir mal 40 Hz, das Empfängerscript kann jedoch nur alle 10 ms einen eingetrudelten Messwert empfangen. Blöderweise gibt UDPRecv nicht den Wert zurück, der zuletzt am PC eingetroffen ist, sondern immer den ältesten Wert, der noch nicht mit UDPRecv abgefragt wurde. Die Live-Dastellung kann man dann natürlich verhacken. Abhängig von der Sendefrequenz und von der Zeit, die das Script bereits läuft, kann es zu Verzögerungen von mehreren Sekunden kommen.
    Kann ich die gespeicherten alten Daten nicht irgendwie ins Nirwana schicken um einen solchen Datenstau irgendwie zu umgehen?

    Danke im Voraus :)

    • Offizieller Beitrag

    Bau mal das Script um auf den OnEventMode. Beim MessageLoopModus wird das Script langsamer, wenn man die Maus nicht bewegt bzw. nicht auf der Tastatur tippt.
    Eventuell liegt es schon daran. 40 Hz halte ich nicht für so viel. Das sollte mit einem halbwegs aktuellem PC zu schaffen sein. Wieviel Zeit braucht denn Deine Auswertungs-/Anzeigefunktion? Hast Du mal gemessen?

  • OK, ich hab' jetzt nochmal ein wenig gebastelt und jetzt brauch das Script im Durchschnitt 30, im Extremfall 40 ms um einen Datensatz anzuzeigen.
    Trotzdem: 1000/30 = 33,3 (Der Sender unterstützt übrigens bis zu 100 Hz)
    Also kann ich diesen Stau nur vermeiden, indem ich das Script optimiere?

  • Hi,
    zeig mal den relevanten Teil von deinem Script, ich würde auch sagen, dass die reine Datenanzeige wesentlich schneller als 40ms machbar ist, ggf. gibts ja "tuning"-Potential 8o

  • OK, hier mal das komplette Script. Es kann gut sein, dass es da noch Optimierungsbedarf gibt, mit GDI+ bin ich noch nicht so ganz vertraut:

    Spoiler anzeigen
    [autoit]

    #include <GDIPlus.au3>
    #include <Color.au3>
    #NoTrayIcon

    [/autoit] [autoit][/autoit] [autoit]

    ;-----
    $ip = @IPAddress1
    $port = 10552
    ;-----
    Opt("GUIOnEventMode", 1)

    [/autoit] [autoit][/autoit] [autoit]

    Dim $zPoints[161][2]
    Dim $xPoints[161][2]
    Dim $yPoints[161][2]

    [/autoit] [autoit][/autoit] [autoit]

    UDPStartup()
    $socket = UDPBind(@IPAddress1, 10552)
    ConsoleWrite ("IP:" & @IPAddress1 & " Port: " & $port & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    $hGUI = GUICreate("Seismometer", 400, 400)
    GUISetOnEvent(-3, "Ende")
    GUISetBkColor(0xFFFFFF)
    GUISetState()

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_Startup()
    $hGraphic = _GDIPlus_GraphicsCreateFromHWND($hGUI)
    $linien = _GDIPlus_GraphicsCreateFromHWND($hGUI)
    $background = _GDIPlus_ImageLoadFromFile("bg.png")
    $bitmap = _GDIPlus_BitmapCreateFromGraphics(400, 400, $hGraphic)
    $backbuffer = _GDIPlus_ImageGetGraphicsContext($bitmap)
    $rot = _GDIPlus_PenCreate(0xFFFF0000, 1)
    $blau = _GDIPlus_PenCreate(0xFF0000FF, 1)
    _GDIPlus_GraphicsSetSmoothingMode($backbuffer, 2)
    _GDIPlus_GraphicsSetSmoothingMode($hGraphic, 2)

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_GraphicsDrawImageRect($hGraphic, $background, 0, 0, 400, 400)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    linien ($hGraphic)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    $zPoints[0][0] = 160
    For $i = 1 To 160
    $zPoints[$i][0] = $i * 2.5
    $zPoints[$i][1] = 50
    Next

    [/autoit] [autoit][/autoit] [autoit]

    $xPoints[0][0] = 160
    For $i = 1 To 160
    $xPoints[$i][0] = $i * 2.5
    $xPoints[$i][1] = 150
    Next

    [/autoit] [autoit][/autoit] [autoit]

    $yPoints[0][0] = 160
    For $i = 1 To 160
    $yPoints[$i][0] = $i * 2.5
    $yPoints[$i][1] = 250
    Next

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_GraphicsDrawCurve($hGraphic, $zPoints)
    _GDIPlus_GraphicsDrawCurve($hGraphic, $xPoints)
    _GDIPlus_GraphicsDrawCurve($hGraphic, $yPoints)

    [/autoit] [autoit][/autoit] [autoit]

    While 1
    $data = UDPRecv($socket, 50)
    If $data <> "" Then
    $timer = TimerInit ()
    ConsoleWrite($data)
    $werte = StringSplit($data, ",")
    ; id,timestamp,x,y,z
    $x = $werte[3]
    $y = $werte[4]
    $z = $werte[5]
    $ztemp = $zPoints
    $xtemp = $xPoints
    $ytemp = $yPoints

    [/autoit] [autoit][/autoit] [autoit]

    $zPoints[160][1] = 50 - $z * 15
    For $i = 1 To 159
    $zPoints[$i][1] = $ztemp[$i + 1][1]
    Next

    [/autoit] [autoit][/autoit] [autoit]

    $xPoints[160][1] = 150 - $x * 15
    For $i = 1 To 159
    $xPoints[$i][1] = $xtemp[$i + 1][1]
    Next

    [/autoit] [autoit][/autoit] [autoit]

    $yPoints[160][1] = 250 - $y * 15
    For $i = 1 To 159
    $yPoints[$i][1] = $ytemp[$i + 1][1]
    Next

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_GraphicsDrawImageRect($backbuffer, $background, 0, 0, 400, 400)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    linien ($backbuffer)
    _GDIPlus_GraphicsDrawCurve($backbuffer, $zPoints)
    _GDIPlus_GraphicsDrawCurve($backbuffer, $xPoints)
    _GDIPlus_GraphicsDrawCurve($backbuffer, $yPoints)
    _GDIPlus_GraphicsDrawImageRect($hGraphic, $bitmap, 0, 0, 400, 400)
    ConsoleWrite (TimerDiff ($timer) & @CRLF)
    EndIf
    WEnd

    [/autoit] [autoit][/autoit] [autoit]

    Func ende ()

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_GraphicsDispose($hGraphic)
    _GDIPlus_GraphicsDispose($backbuffer)
    _GDIPlus_BitmapDispose ($bitmap)
    _GDIPlus_Shutdown()
    _GDIPlus_PenDispose ($rot)
    _GDIPlus_PenDispose ($blau)
    UDPCloseSocket($socket)
    UDPShutdown()
    Exit
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    Func linien ($graphic)
    _GDIPlus_GraphicsDrawLine($graphic, 0, 20, 400, 20, $rot)
    _GDIPlus_GraphicsDrawLine($graphic, 0, 35, 400, 35, $rot)
    _GDIPlus_GraphicsDrawLine($graphic, 0, 50, 400, 50, $blau)
    _GDIPlus_GraphicsDrawLine($graphic, 0, 65, 400, 65, $rot)
    _GDIPlus_GraphicsDrawLine($graphic, 0, 80, 400, 80, $rot)

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_GraphicsDrawLine($graphic, 0, 120, 400, 120, $rot)
    _GDIPlus_GraphicsDrawLine($graphic, 0, 135, 400, 135, $rot)
    _GDIPlus_GraphicsDrawLine($graphic, 0, 150, 400, 150, $blau)
    _GDIPlus_GraphicsDrawLine($graphic, 0, 165, 400, 165, $rot)
    _GDIPlus_GraphicsDrawLine($graphic, 0, 180, 400, 180, $rot)

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_GraphicsDrawLine($graphic, 0, 220, 400, 220, $rot)
    _GDIPlus_GraphicsDrawLine($graphic, 0, 235, 400, 235, $rot)
    _GDIPlus_GraphicsDrawLine($graphic, 0, 250, 400, 250, $blau)
    _GDIPlus_GraphicsDrawLine($graphic, 0, 265, 400, 265, $rot)
    _GDIPlus_GraphicsDrawLine($graphic, 0, 280, 400, 280, $rot)
    EndFunc

    [/autoit]
  • Hi,
    die Funktion "Linien" kannst du komplett weglassen, wenn du einmal den Backbuffer (per "Linien"-Funktion auf den Background) komplett erstellst, und diesen einfach nur als Hintergrund nimmst.

    Weiterhin berechnest du den kompletten Linienzug in jedem Schleifendurchgang neu aus den "alten" Daten, auch das ist völlig unnötig,

    Schreibe die Daten so lange, bis der erste "Bildschirm" (Buffer1) voll ist.
    Dann schiebst du dieses komplette Bild (Buffer1) EIN PIXEL nach links.
    Jetzt muss nur noch der aktuelle Wert (Pixel) an den rechten Rand geschrieben werden, bzw linie vom "letzten" Pixel zum aktuellen.
    Du zeichnest also pro empfangenen Wert exakt einmal eine Linie, ggf ist es schneller 3-4 empfangene Werte zusammen zu fassen und diesen Linienzug zu zeichnen...

  • die Funktion "Linien" kannst du komplett weglassen, wenn du einmal den Backbuffer (per "Linien"-Funktion auf den Background) komplett erstellst, und diesen einfach nur als Hintergrund nimmst.

    Das zweite verstehe ich, aber das kapiere ich nicht ganz. Linien kann ich doch nur auf Graphicobjekte zeichnen, oder? Kannst du mir vl. mal ein Beispiel geben, wie du das meinst? Danke :)

  • Du musst dir nur eine zweite Bitmap erstellen. Auf diese zeichnest du dann zu Beginn alle Hintergrund Linien/Bilder und dann wird sie nicht mehr verändert. Jedes mal bevor du dann das nächste Frame Kurven auf den Backbuffer zeichnest zeichnest du vorher einfach noch das Hintergrund-Bitmap auf den Backbuffer... So ist es dann nicht nötig den Hintergrund für jedes Frame neu zu rendern...

    LG
    Christoph :)

  • Hallo Cheater-Dieter,

    genau das Problem hatte ich auch vor kurzem:

    10 Hz UDP mit nur 5 Hz empfangen

    Die Lösung die ich am Ende benutzt habe ist die, dass ich von Zeit zu Zeit in einer Schleife Daten abfrage bis ich einen Timeout bekomme. Dann weiß ich dass die letzten gültigen Daten aktuell sind. Allerdings bedeutet ein Timeout, dass ich 100 ms verliere und ich danach genau 100ms hinten dran bin(die Zeit bis zum Timeout kann man leider nicht verstellen, nur bei TCP). in meinem Fall hab ich beschlossen dass ich damit leben kann.

    Die Schleife sieht dann so aus:

    [autoit]

    func UDP_Daten_durchrattern()
    while 1
    $data = UDPRecv($socket, 50)
    If $data <> "" Then
    $letzte_gueltige_Daten = $data
    else
    ExitLoop
    EndIf
    WEnd
    return $letzte_gueltige_Daten
    endfunc

    [/autoit]

    Im englischem Forum gibt es einen Eintrag zu diesem Problem und in der Antwort ist ein Link zu einer UDF für asynchrone UDP Sockets, die scheint das Problem auch zu lösen. Aber das war mir ehrlich gesagt zu hoch...

    Vielleicht hilft es dir ja weiter!

  • So währe es auch einem möglichkeit. Hier kannst du einstellen, wann es spätestens angezeigt werden soll, aber wenn du es nicht zu shcnell sendest kommt es so zu keinem Datenstau.

    [autoit]

    Global $hTimer, $sDataOld

    [/autoit][autoit][/autoit][autoit]

    Do
    $hTimer = TimerInit()
    Do
    $sDataOld = $data
    $data = UDPRecv($socket, 50)
    Until $data = '' Or TimerDiff($hTimer) > 1234
    ; Auswertung und Anzeige von $sDataOld
    Until GUIGetMsg() = -3

    [/autoit]
  • Danke Smincke und pipo28, ich denke mal, ich denke mal, da lässt sich was draus machen. Ich werde mir auch mal die UDF im engl. Forum anschauen

    Du musst dir nur eine zweite Bitmap erstellen. Auf diese zeichnest du dann zu Beginn alle Hintergrund Linien/Bilder und dann wird sie nicht mehr verändert. Jedes mal bevor du dann das nächste Frame Kurven auf den Backbuffer zeichnest zeichnest du vorher einfach noch das Hintergrund-Bitmap auf den Backbuffer... So ist es dann nicht nötig den Hintergrund für jedes Frame neu zu rendern...

    Ich kapiere es immer noch nicht (und ja, ich hab' mich damit beschäftigt). Wie kann ich mit _GDIPlus_GraphicsDrawLine auf ein Bitmap zeichnen?

  • Zitat

    Ich kapiere es immer noch nicht (und ja, ich hab' mich damit
    beschäftigt). Wie kann ich mit _GDIPlus_GraphicsDrawLine auf ein Bitmap
    zeichnen?

    Ich denke er meint sowas:


    [autoit]

    $hGraphic = _GDIPlus_GraphicsCreateFromHWND($hGUI)
    $hBitmap = _GDIPlus_BitmapCreateFromGraphics( 100,100, $hGraphic) ; Buffer erzeugen Schritt 1
    $hBuffer = _GDIPlus_ImageGetGraphicsContext($hBitmap) ; Buffer erzeugen Schritt 2
    _GDIPlus_GraphicsDrawLine($hBuffer,1,1,100,100) ; auf Buffer statt auf $hGraphic zeichnen
    _GDIPlus_GraphicsDrawString($hBuffer,"Hallo Welt",10,60)
    _GDIPlus_GraphicsDrawImageRect($hGraphic, $hBitmap, 0, 0, 100, 100) ;schließlich Buffer auf $hGraphic, alles wird auf einmal gezeichnet statt Schritt für Schritt und du gewinnst Zeit

    [/autoit]

    Einfach alles was du zeichnen willst auf den Buffer zeichnen.

  • Puh, in Sachen GDI+ besteht bei mir wohl noch Nachholbedarf... Naja, jedenfalls danke vielmals für das Beispiel, ich hab's jetzt verstanden.
    Ich kann jetzt einen Stau weitestgehend verhindern. Danke euch allen :)

    Edit: Ich hab' soeben die optimale Lösung gefunden, um einen Stau zu verhindern: Per AdlibRegister rufe ich alle 10 ms eine Funktion auf, die den Socket schließt und dann sofort wieder einen neuen aufbaut. Somit wird ein Ansstauen der Daten verhindert und ich kann mit hohen Datenraten senden.

  • Edit: Ich hab' soeben die optimale Lösung gefunden, um einen Stau zu verhindern: Per AdlibRegister rufe ich alle 10 ms eine Funktion auf, die den Socket schließt und dann sofort wieder einen neuen aufbaut. Somit wird ein Ansstauen der Daten verhindert und ich kann mit hohen Datenraten senden.


    Das dies optmal ist wage ich zu bezweifeln, da dnn doch lieber nur jeden x. Wert 2., 3. etc,) an die GDO-Routunen zum anzeigen weitergeben, oder evt. ,ehrere Werte mitteln und dann weitergeben

    mfg autoBert