Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Netzwerke (https://www.delphipraxis.net/14-netzwerke/)
-   -   IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark? (https://www.delphipraxis.net/183759-idudpserver-hat-manchmal-datenverlust-wie-macht-das-wireshark.html)

Poolspieler 2. Feb 2015 18:44

IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Hallo zusammen,
ich benutze die Indy-Komponenten für einen UDP-Server.
Im Prinzip funktioniert das ganze recht gut. Leider ist aber bei hoher Last (sowohl hohe CPU-Last, als auch viele UDP-Pakete innerhalb kurzer Zeit) immer wieder ein Datenverlust vorhanden. Es landen anscheinend nicht alle UDP-Pakete im Puffer meines UDP-Servers.
Ein Pufferüberlauf ist nicht vorhanden - das habe ich bereits überprüft.
Wenn ich mit dem Wireshark mitlogge, dann kommen die Pakete beim Rechner definitiv an. Nur der UDP-Server hat sie nicht alle im Puffer :(
OK, ich stelle mit den Daten recht viel an:
- Realtimeausgabe der Daten in einem Chart (im Chart (TChart) sind bis zu 500.000 Punkte, die aktualisiert werden müssen)
- gleichzeitig werden die Daten in Textfiles mitgeschrieben

Ich habe den UDP-Server in einem eigenen Thread laufen - und synchronisieren dann die Daten (wenn sie komplett sind) zur Oberfläche. Und es scheint, dass die Datenverluste genau zu diesem Zeitpunkt auftreten - genau kann ich das aber nicht sagen...

Es scheint aber mit der Ausgabe im Chart sehr stark zu tun zu haben. Wenn ich anstatt von TFastlineseries z.B. TLineseries verwende, dann sind die Datenverluste deutlich größer!

Ich weiß schon, dass UDP nicht verbindungsorientiert ist - und es keine Flusskontrolle (ausser IP) gib. Und dass es deshalb auch Datenverluste geben kann.
ABER: WIE macht das der Wireshark (früher Ethereal) --> Der kann Millionen von Paketen mitloggen, parsen und darstellen - OHNE dass auch nur ein einziges Paket verloren geht.

Hat jemand einen Hinweis für mich? Ich würde mich freuen.



Viele Grüße,

Poolspieler

Zacherl 2. Feb 2015 18:46

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Du weißt aber schon, dass bei UDP generell nicht garantiert ist, dass ein Paket ankommt? Nichtmal die korrekte Reihenfolge der Pakete muss zwangsweise eingehalten werden. Wenn du aus irgendeinem Grund auf keinen Fall TCP verwenden willst, musst du dich um Sequenzierung und eventuelles Neu-Anfordern von verlorengegangenen Paketen selbst softwareseitig kümmern.

Bernhard Geyer 2. Feb 2015 18:53

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Zitat:

Zitat von Poolspieler (Beitrag 1288640)
Ich habe den UDP-Server in einem eigenen Thread laufen - und synchronisieren dann die Daten (wenn sie komplett sind) zur Oberfläche. Und es scheint, dass die Datenverluste genau zu diesem Zeitpunkt auftreten - genau kann ich das aber nicht sagen...

Da hast du doch schon den Implementierungsfehler. Wenn du synchronisierst wird den Thread angehalten und die Abarbeitung im Hauptthread weiter gemacht. Während dieser Zeit gehen natürlich alle ankommenden Daten verloren da ja keiner (Dein Thread ist ja durch den Hauptthread blockiert) die Daten annimmt.

Du musst also die Übergabe an die Darstellungsthread besser von deinem Empfangsthread entkoppeln.
Da ich hier wenig Erfahrung habe (bzw. sehr lange her) hier ein (evtl. fehlerhafter) Ansatz:

Statt normal zu synchronisieren triggerst du (z.B. über PostMessage) den Hauptthread. Der holt sich die Daten dann aus einem threadsicheren Datenbereich (das Abholen darf natürlich nicht zu blockieren deines UDP-Threads führen). evtl. einfach nur für jedes vollständige Datenpacket einen "Aufhänger" einen simple TThreadList übergeben.

Sir Rufo 2. Feb 2015 18:57

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Der Wireshark hängt sich gaaanz tief in die Netzwerkkarte (Treiber) rein und er bekommt die Daten quasi bevor die an das System weitergereicht werden.

Um die Verluste zu minimieren solltest du statt
Delphi-Quellcode:
Synchronize
eher
Delphi-Quellcode:
Queue
versuchen. Das kann dann aber auch die Windows-Warteschlange zum Überlaufen bringen.

Ansonsten bleibt dir noch die Daten vom Epfangsthread in einen weiteren Thread zu übergeben, der sich dann um die Weitergabe kümmert. Um die Daten aufzunehmen hat dieser Thread 2 Queues. Eine zum Empfangen und die andere zum Weiterreichen.

Ist die Weiterreich-Queue leer, dann tauscht man die Plätze und die alte Empfangsqueue wird nun die Weiterreichqueue und umgekehrt. Der Tausch geht sehr fix und lässt sich per
Delphi-Quellcode:
TInterlocked.Exchange
threadsicher durchführen. Ein weiteres Locking ist dann nicht erforderlich (wenn es nur einen Empfangsthread gibt).

Poolspieler 2. Feb 2015 19:07

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Hallo Zusammen,
danke für Eure Antworten.

@Zacherl: Ja, das weiß ich. Leider kann ich Systembedingt (und Kundenanforderung) nicht auf TCP umsteigen. Ein zweites Anfordern der Daten ist auch nicht erwünscht bzw. möglich.
Die Sache mit der Reihenfolge tritt IMHO nur auf, wenn Router bzw. mehrere Hops zwischen den Hosts sind. In meinem Fall ist das ausgeschlossen. In den meisten Fällen ist zwischen dem UDP-Server und den Hosts nur ein Switch. Trotzdem danke für Deinen Hinweis!

@Bernhard:
Ich denke in der gleichen Richtung wie Du. Nur bisher hatte ich keinen guten Lösungsansatz, der auch stabil und robust läuft. Bisher habe ich versucht, die Sychnronisation so performant wie möglich zu machen... Die ist natürlich nur ein Workaround...
Das Problem mit dem Hauptthread, der sich die Daten aus einem Threadsicheren Datenbereich holt, ist doch, dass der Server-Thread nicht weiß, wann er die Daten freigeben kann - oder wie geht das?
Gibt es vielleicht ein Beispiel, oder weitere Info dazu im Netz?

@Sir Rufo:
Danke für deinen konkreten Lösungsansatz! Das klingt richtig gut! Ich werde das weiter verfolgen und ggf. später noch konkrete Fragen dazu stellen. :-D
Hast Du eventuell einen Link zu einem Demoprojekt oder weiterer Info?
Falls nicht, kein Problem, dann Google ich gleich selbst...?



Viele Grüße,

Poolspieler

Sir Rufo 2. Feb 2015 19:13

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Ob Google das hat weiß ich nicht, ist mir gerade so eingefallen :stupid:

jaenicke 2. Feb 2015 19:22

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Eine Möglichkeit ist auch ein Ringpuffer.
Kurz gesagt: Da muss der Empfangsthread die meiste Zeit über nur an die durch einen Pointer bezeichnete Stelle zu schreiben und den Pointer inkrementieren (AtomicIncrement). Am Ende des Puffers angekommen, gehts zurück zum Anfang. Und der verarbeitende Thread kann dann einfach lesend darauf zugreifen.

Poolspieler 2. Feb 2015 19:30

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Hallo jaenicke,
das ist auch ein guter Ansatz.
Aber woher weiß der Empfangsthread, ob der verarbeitende Thread die Daten schon abgeholt hat? Stichwort "Pufferüberlauf"...
Das aktualisieren der Pointer muss aber trotzdem synchronisiert erfolgen oder?
Oder sind Pointer threadsave?

Gruß,

Poolspieler

Sir Rufo 2. Feb 2015 19:58

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Hier mal so ein grober Entwurf, der aber schon tut :)

UPDATE
Eine kurze Erklärung

Der
Delphi-Quellcode:
TMessageQueueThread<T>
ist selber nicht threadsafe und nur dazu gedacht von einem einzigen Thread gefüttert zu werden. Sollen die Nachrichten von mehreren Threads zusammengeführt werden, so wird zusätzlich noch eine weitere threadsafe Queue benötigt, die dann von den
Delphi-Quellcode:
TMessageQueueThread
gefüttert wird, jetzt aber entkoppelt vom eigentlichen Tread, der die Nachrichten produziert.

Jede Art der Synchronisierung würde den eigentlichen Thread wieder ausbremsen und das gilt es hier ja ausdrücklich zu vermeiden.

Delphi-Quellcode:
unit Unit1;

interface

uses
  System.SysUtils,
  System.Classes,
  System.SyncObjs,
  System.Generics.Collections;

type
  TMessageQueueThread<T> = class( TThread )
  private
    FQueueProc: TProc<T>;
    FInQueue, FOutQueue: TQueue<T>;
    FEvent: TEvent;
  protected
    procedure Execute; override;
    procedure DoProcessQueue( AQueue: TQueue<T> );
    procedure TerminatedSet; override;
  public
    constructor Create( AQueueProc: TProc<T> );
    destructor Destroy; override;

    procedure Enqueue( const AItem: T );
  end;

  TReceiveThread = class( TThread )
  private
    FMessageQueue: TMessageQueueThread<string>;
    FFMTset: TFormatSettings;
  protected
    procedure Execute; override;
  public
    constructor Create( AProc: TProc<string> );
    destructor Destroy; override;
  end;

implementation

{ TRelayQueueThread<T> }

constructor TMessageQueueThread<T>.Create( AQueueProc: TProc<T> );
begin
  inherited Create( False );
  FEvent := TEvent.Create( nil, False, False, '' );
  FQueueProc := AQueueProc;
  FInQueue := TQueue<T>.Create;
  FOutQueue := TQueue<T>.Create;
end;

destructor TMessageQueueThread<T>.Destroy;
begin
  inherited;
  FInQueue.Free;
  FOutQueue.Free;
  FEvent.Free;
end;

procedure TMessageQueueThread<T>.DoProcessQueue( AQueue: TQueue<T> );
begin
  while AQueue.Count > 0 do
    begin
      FQueueProc( AQueue.Peek );
      AQueue.Dequeue;
    end;
end;

procedure TMessageQueueThread<T>.Enqueue( const AItem: T );
begin
  FInQueue.Enqueue( AItem );
  FEvent.SetEvent;
end;

procedure TMessageQueueThread<T>.Execute;
begin
  inherited;
  while not Terminated do
    begin
      FEvent.WaitFor( );

      if Terminated
      then
        Exit;

      // Queues tauschen
      FOutQueue := TInterlocked.Exchange < TQueue < T >> ( FInQueue, FOutQueue );
      // Queue verarbeiten
      DoProcessQueue( FOutQueue );
      // Queue leeren, nur für alle Fälle, sollte ja eh jetzt leer sein :o)
      FOutQueue.Clear;
    end;
end;

procedure TMessageQueueThread<T>.TerminatedSet;
begin
  inherited;
  FEvent.SetEvent;
end;

{ TReceiveThread }

constructor TReceiveThread.Create( AProc: TProc<string> );
begin
  inherited Create( False );
  FFMTset := TFormatSettings.Create( '' );
  FMessageQueue := TMessageQueueThread<string>.Create( AProc );
end;

destructor TReceiveThread.Destroy;
begin
  inherited;
  FMessageQueue.Free;
end;

procedure TReceiveThread.Execute;
var
  LMsg: string;
begin
  inherited;
  while not Terminated do
    begin
      LMsg := FormatDateTime( 'hh:nn:ss.zzz', Now, FFMTset );
      FMessageQueue.Enqueue( LMsg );
    end;
end;

end.
Der ReceiveThread ist jetzt nur zum Testen des MessageQueueThread da

Und der Test mit der Synchronisierung
Delphi-Quellcode:
unit Form.Main;

interface

uses
  Unit1,

  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class( TForm )
    Button1: TButton;
    ListBox1: TListBox;
    procedure Button1Click( Sender: TObject );
  private
    FTestThread: TReceiveThread;
    procedure ReceiveMessage( Arg: string );
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click( Sender: TObject );
begin
  if Assigned( FTestThread )
  then
    FreeAndNil( FTestThread )
  else
    FTestThread := TReceiveThread.Create( ReceiveMessage );
end;

procedure TForm1.ReceiveMessage( Arg: string );
begin
  TThread.Synchronize( nil,
      procedure
    begin
      ListBox1.Items.Add( Arg + ' - ' + FormatDateTime( 'hh:nn:ss.zzz', Now ) );
    end );
end;

end.
Und nach dem Druck auf den Button, relativ schnell wieder den Button drücken, der Thread haut dir gnadenlos die ListBox voll ;)

OlafSt 2. Feb 2015 22:31

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Ich habe so ein ähnliches Problem vor geraumer Zeit gehabt. Als erstes habe ich die Indy-UDP-Komponenten entsorgt und eine Dritt-Implementation von UDP eingesetzt (UdpSockUtil, zu finden auf entwickler-ecke.de).

Im Empfängerthread wird dann eine TQueue mit den Empfangsdaten gefüllt. Nach jedem Empfang wird mit Postmessage der Hauptthread verständigt. Dieser wiederum liest dann solange Daten aus der Queue, bis diese leer ist.

Ich habe die Synchronisation manuell mit TCriticalSections gemacht, so das ich exakte Kontrolle darüber habe, wann wer wie wo was liest oder schreibt - vor allem blockiere ich so die Queue nur für den einzelnen Lese/Schreibvorgang und gebe sie sofort wieder "frei".

Falls die Windows-Messagequeue überzulaufen beginnt, kann man die Postmessages einfach auf jeden zweiten/dritten Eintrag begrenzen. Der Hauptthread arbeitet dann eh alles ab.

Poolspieler 2. Feb 2015 22:42

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Hallo OlafSt,
ich werde mir Deinen Ansatz genau anschauen - und das für mich zutreffende umsetzen. Danke!

Welches Argument spricht gegen Indy?
Bzw. welche Vorteile bietet UdpSockUtil?

Ich persönlich (aber das ist nur meine Meinung...) versuche immer so viel wie möglich mit den Bordmitteln von Delphi zu programmieren.
Ich nutze z.B. TChart Prof. Und da fängt das Problem schon an. Bei jeder neuen Delphiversion muss ich erstmal ein bis zwei Monate warten, bis es eine lauffähige Version von Steema für die neue Delphiversion gibt.
Für manche Projekte habe ich auch schon die TMS-Komponenten genutzt - da ist es nicht viel anders.

Deshalb versuche ich neue (und auch laufende) Projekte möglichst nicht mit Drittanbietern zu "belasten".

Viele Grüße,

Poolspieler

OlafSt 2. Feb 2015 23:30

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Die XE4-Implementation der Indy-UDP-Komponenten hat ne Macke. Habe das nicht weiter verfolgt (mir war das Fixen, neukompilieren und neu-Einbinden einfach zu kompliziert und ich hatte reichtlich Termindruck) und das ganze auf UdpSockUtil ungestellt. Die lassen sich problemlos von Version zu Version mitnehmen und gepflegt werden die auch noch ;)

UdpSockUtils arbeitet direkt mit Sockets, direkter am System gehts kaum noch. Womit sich auch das mitschleppen dicker Units erübrigt. Aber - das ist jedem selbst überlassen.

Ansonsten bin ich da ganz bei dir - so wenig Fremdkomponenten wie möglich. Nur: Wirklich toll aussehende Anwendungen kriegt man damit nicht hin ;)

Poolspieler 2. Feb 2015 23:40

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Hallo,
ich gebe Dir in allen Punkten Recht. :-D
Was mit XE4 und Indy war, weiß ich nicht mehr. Aber irgend ein Update gab es damals... Aktuell nutze ich XE7.

Ich werde mein Glück mit Indy noch etwas weiter versuchen (weil es ja prinzipiell schon mal damit läuft) - optional kann ich mir mal UdpSockUtils anschauen. :thumb:

Zitat:

Ansonsten bin ich da ganz bei dir - so wenig Fremdkomponenten wie möglich. Nur: Wirklich toll aussehende Anwendungen kriegt man damit nicht hin
:arrow: Stimmt, deswegen nutze ich manchmal z.B. die TMS Komponenten...

Viele Grüße,

Poolspieler

mensch72 3. Feb 2015 00:47

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
TChart 500T Datenpunkte... wenn die Verarbeitung / Anzeige teilweise nicht hinter her kommt, worauf kommt es denn dann in deiner Anwendung an?

Lieber mal ein paar Daten "weglassen" und dafür aber die Anzeige möglichst immer wieder "realtime" synchron halten, ODER eine Art "Gummiband" mit Dynamischen Puffer, welcher alles "irgendwann" dann eben verzögert darstellt und erst wenn wenig los mal wieder realtime synchron ist.

Fall 1 wird gerne bei Börsendaten genommen, denn da zählt letztendlich nur der aktuelle Kurs, wenn da mal einer zwischendurch fehlt ist das zwar nicht schön, aber kein Kurs ist da teils besser wie ein alter und nun schon falscher Kurs, von dem man nicht weiß, das er "alt" ist.

Meine Lösung: 3 "Block"-Puffer mit 4 möglichen States:"Write","Data","Read","Free"
- nur die StateZugriffe müssen synchronisiert werden, eine einfache kurze CriticalSection reicht hier, sind ja nur 1 oder 2 Variablenzugriffe zu schützen
- der Empfangsthread setzt synchronisiert den Puffer mit "WriteState" auf "DataState" und wechselt zyklisch den anderen Puffer von "Free/Data-State" auf "WriteState", ein Pufferwechsel wird dem WorkThread "signalisiert"
- der WorkThread setzt synchronisiert den Puffer mit "ReadState" auf "FreeState" und setzt den Puffer mit "DataState" auf "ReadState", dann werden die Daten des neuen "ReadState" Puffers beliebig langsam verarbeitet/angezeigt
- wenn die Anzeige sagen wir mal 3sec dauert und der Empfangsthread alle 1sec den Puffer wechselt, dann werden eben mal 2sec keine Empfangsdaten angezeigt, dann aber dafür sofort wieder die aktuellsten... für Visuelle Aufgaben ist das oft die eleganteste Lösung... für vollständige Datenlogs ist das aber ungeeignet, da muss es dann ein dynamischer FiFo sein. => Bei FiFo-Lösungen aber immer einen Empfangs-TimeStamp mit abspeichern! Nur so kann man bei der "späteren" Verarbeitung/Anzeige dann erkennen, das es alte Daten mit Zeitverzug sind

Poolspieler 3. Feb 2015 01:31

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Hallo mensch72,
die Anwendung ist eine Art Realtime-Oszilloskop auf bis zu 40 Kanälen.
Daten kann man leider nicht auslassen, da man ja sowohl den "Brumm", als auch das gleitende Mittel sehen will --> beides wird angezeigt. Die "Rohdaten" und ein gleitendes Mittel über X Werte.
Für die Anwendung sind auch einzelne Peaks (Ausreißer) wichtig.
Normalerweise macht es keinen Sinn, alle Kanäle gleichzeitig anzuzeigen - es kann aber Situationen (Fehlersuche) geben, in denen das schon Sinn macht.

Auch Deinen Lösungsansatz (vielen Dank dafür!:thumb:) werde ich mir genau anschauen. Der klingt auch sehr gut.
Ich werde Euch mitteilen, welche Lösung für mich die sinnvollste war, bzw. welche ich umgesetzt habe!

Viele Grüße,

Poolspieler

jaenicke 3. Feb 2015 07:41

AW: IdUDPServer hat manchmal Datenverlust --> wie macht das Wireshark?
 
Zitat:

Zitat von Poolspieler (Beitrag 1288650)
Aber woher weiß der Empfangsthread, ob der verarbeitende Thread die Daten schon abgeholt hat? Stichwort "Pufferüberlauf"...

Das muss man prüfen. Da mit den Atomic...-Funktionen die Zugriffe threadsicher passieren, ist aber keine weitere Synchronisation nötig. Und die atomaren Funktionen werden direkt in Assembler umgesetzt, das ist Compilermagic. So entsteht wenig Aufwand für die Synchronisation, auch wenn man noch weitere Sachen prüfen muss.

Zitat:

Zitat von Sir Rufo (Beitrag 1288652)
Hier mal so ein grober Entwurf, der aber schon tut :)

Auf den ersten Blick würde ich sagen, dass der Austausch der Pointer der beiden Queues in TMessageQueueThread<T>.Execute genau kurz nach dem Assemblerbefehl passieren kann, der den bisherigen Pointer in TReceiveThread.Execute holt. So würde dann Enqueue auf der Queue ausgeführt, die in DoProcessQueue verarbeitet wird. Oder sehe ich das falsch?


Alle Zeitangaben in WEZ +1. Es ist jetzt 17:18 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz