Entwickler-Ecke

Internet / Netzwerk - Polling via UDPSockUtil auf eine Server-DatenQuelle


Bastler - Mi 22.02.12 18:42
Titel: Polling via UDPSockUtil auf eine Server-DatenQuelle
Hallo Leute,

ich habe die Aufgabe, Daten eines Messgerätes zu visualiesieren. Das Messgerät verhält sich wie ein Server.. Man kann dem Gerät auf Schlüsselworte hin Daten entlocken. Man schickt beispielsweise ein "A" und erhält ein Antwort-Paket zurück. (Die Befehle sind durch $A getrennt) Die Kommunikation läuft über UDP. Die Kommunikation läuft in einem geschlossenen Netz ohne weitere Rechner (nur der Server und der Client).

Das Tool UDPSockutil von Narses wirkt schön schlank und übersichtlich. Ich möchte gern das Tool einsetzen. habe aber probleme damit.

Ideal wäre, wenn ich einen Befehl senden und sofort die Antwort auswerten könnte (ich will auf die Antworten reagieren können)
Ich nutze den Timer von Delphi und schicke in einem bestimmten Takt Sequenzen von Befehlen.

Jetzt das Problem:

- verwende ich den Eventbetrieb des Tools, kommt die erste Antwort erst, wenn die Timer-Routine komplett abgearbeitet wurde und dann in einem Rutsch hintereinander weg. (keine Reaktion möglich)
- verwende ich send und recieveString ohne Events in der Timer-Routine, dann passt die Antwort nicht zum Befehl. etwa so:
sende(A)
receive(-leer-)
sende(B)
receive(Antwort zu A)
Sende(C)
receive(Antwort zu b)
...

Hat jemand Erfahrung? Wo liegt mein Denkfehler?

Gruß

Udo


Narses - Mi 22.02.12 23:58

Moin und :welcome: in der EE!

user profile iconBastler hat folgendes geschrieben Zum zitierten Posting springen:
Hat jemand Erfahrung?
Aufzeig! ;) (zumindest mit der Komponente :P)

user profile iconBastler hat folgendes geschrieben Zum zitierten Posting springen:
Ideal wäre, wenn ich einen Befehl senden und sofort die Antwort auswerten könnte (ich will auf die Antworten reagieren können)
Hier liegt dein "Denkfehler": UDP ist verbindungslos, es gibt keinen Zusammenhang zwischen verschiedenen Datenpaketen, auch nicht (oder erst recht nicht) bei Antworten auf Pakete. Es spielt dabei auch keine Rolle, ob nur diese beiden Geräte in einem Netz sind. :idea:

Wenn der embedded Controler keine Antwortkennung auf eine Anfrage anbietet, dann hast du ja nur das Timing als Zuordnungsmöglichkeit, und das ist bei UDP in einem hohen Takt wenig zuverlässig oder sinnvoll. :nixweiss:

user profile iconBastler hat folgendes geschrieben Zum zitierten Posting springen:
- verwende ich den Eventbetrieb des Tools, kommt die erste Antwort erst, wenn die Timer-Routine komplett abgearbeitet wurde und dann in einem Rutsch hintereinander weg. (keine Reaktion möglich)
Das ist normal und liegt an der Ereignisverarbeitung von Windows. ;) Der Timer erzeugt beim Feuern ein Ereignis, und erst wenn dessen Handler abgearbeitet ist, wird der Handler für das Empfangsereignis aufgerufen.

user profile iconBastler hat folgendes geschrieben Zum zitierten Posting springen:
- verwende ich send und recieveString ohne Events in der Timer-Routine, dann passt die Antwort nicht zum Befehl. etwa so:
sende(A)
receive(-leer-)
sende(B)
receive(Antwort zu A)
Sende(C)
receive(Antwort zu b)
...
Auch das ist "normal", du sendest Daten und willst sofort aus dem Socket lesen - da ist aber noch nix drin. :nixweiss: Dein Gerät muss erstmal das Paket kriegen, verarbeiten und was zurück senden, das dauert selbst bei einem sehr schnellen EC schon ein paar Millisekunden. :idea:

user profile iconBastler hat folgendes geschrieben Zum zitierten Posting springen:
Man kann dem Gerät auf Schlüsselworte hin Daten entlocken. Man schickt beispielsweise ein "A" und erhält ein Antwort-Paket zurück. (Die Befehle sind durch $A getrennt)
Ich denke, hier liegt möglicherweise ein Schlüssel für das Problem: du hast das Protokoll des Gerätes möglicherweise noch nicht ganz oder gut genug verstanden. Aus dem einen Nebensatz kann ich aber auch nicht wirklich was erkennen. :?

user profile iconBastler hat folgendes geschrieben Zum zitierten Posting springen:
Ich nutze den Timer von Delphi und schicke in einem bestimmten Takt Sequenzen von Befehlen.
Ansatz: verwende den asynchronen Modus der Komponente und benutze von mir aus auch den Timer. Allerdings musst du mehr ereignisorientiert denken: Im Timerereignis wird gesendet, im Empfangsereignis wird ausgewertet, nicht beides zusammen! ;) Stelle also beim Senden die Anfragen in eine Warteschlange ein und orientiere dich beim Empfangen daran.

cu
Narses


Bastler - Do 23.02.12 11:27

Moin, Moin!
Danke für die Denkanstöße und Tips! Insebesondere der Tip zur Warteschlange klingt interessant!
Ich muss meine "Denke" wohl wirklich mehr auf Ereignisorientierung umstellen!
Ich probiere es mal aus!

bye for now!

Udo


Bastler - Do 23.02.12 20:30

Beim Probieren bin ich in diesem Zusammenhang auf ein Problem mit dem Broadcast gestoßen. (Ziel vom Broadcast ist es bei mir zu prüfen, ob die Verbindung steht und die Ziel-IP zu ermitteln ). In meiner Testapplikation habe ich einen Button zum Senden von Schlüsselworten (s.o.) und einen der Button, der den Broadcast auslöst.
Senden funktioniert prächtig, der Broadcast liefert nichts zurück, obwohl WireShark anzeigt, dass das Gerät auf dem gleichen Port antwortet auf dem gesendet wurde (übrigens nicht auf Localport 40000)
Wieder ein Denkfehler?

Hier ein Codeauszug:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
procedure TForm1.FormCreate(Sender: TObject);
begin
   form1.UdpSockUtil1.LocalPort := 40000;
   form1.UdpSockUtil1.RemotePort:= 3490;
   form1.UdpSockUtil1.RemoteHost:= '192.168.1.88';
   form1.UdpSockUtil1.Open;
end;

// Nachricht an den angegebenen Host senden
procedure TForm1.BtnSendClick(Sender: TObject);
begin
  UdpSockUtil1.SendText(Input.Text+#$A);
end;

// Broadcast senden
procedure TForm1.Button1Click(Sender: TObject);
begin
  form1.UdpSockUtil1.BroadcastText(Input.Text+#$A);
end;


gruß

Udo

Moderiert von user profile iconMartok: Delphi-Tags hinzugefügt


Narses - Do 23.02.12 23:27

Moin!

user profile iconBastler hat folgendes geschrieben Zum zitierten Posting springen:
Beim Probieren bin ich in diesem Zusammenhang auf ein Problem mit dem Broadcast gestoßen.
[...]
Senden funktioniert prächtig, der Broadcast liefert nichts zurück, obwohl WireShark anzeigt, dass das Gerät auf dem gleichen Port antwortet auf dem gesendet wurde (übrigens nicht auf Localport 40000)
Wieder ein Denkfehler?
Nein, das ist leider by-design so... :? Für den Broadcast wird ein zweiter Socket geöffnet (der dann einen anderen lokalen Port hat), weil man bei der WSA nicht transparent zwischen einem Uni- und einem Broadcast-Socket wechseln kann. :idea:

Um es kurz zu machen: Für diesen Zweck (Broadcast-Discover für ECs, die dann auf dem gleichen Port antworten), ist diese Komponente nicht geeignet. :nixweiss:

cu
Narses


Bastler - Do 23.02.12 23:47

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
Für den Broadcast wird ein zweiter Socket geöffnet (der dann einen anderen lokalen Port hat), weil man bei der WSA nicht transparent zwischen einem Uni- und einem Broadcast-Socket wechseln kann.

mit dem zweiten lokalen Port kann ich leben. Wichtig ist, dass ich den gleichen Zielport verwenden kann. Kann ich die Komponente dann dazu bringen, den gefundenen EC zurückzumelden?

Gruß

Udo

Moderiert von user profile iconNarses: Zitat gekürzt.


Narses - Do 23.02.12 23:55

Moin!

user profile iconBastler hat folgendes geschrieben Zum zitierten Posting springen:
mit dem zweiten lokalen Port kann ich leben.
Nein, kannst du nicht, oder vielmehr, willst du nicht, das ist nämlich der Grund für das unerwünschte Verhalten. ;)

user profile iconBastler hat folgendes geschrieben Zum zitierten Posting springen:
Wichtig ist, dass ich den gleichen Zielport verwenden kann. Kann ich die Komponente dann dazu bringen, den gefundenen EC zurückzumelden?
Wie ich schon schrieb, das geht im Moment mit der Komponente nicht. Dazu müsste der WSA der Broadcast-Port als Signalgeber für das Empfangsereignis mitgegeben werden, das kann man aber auch wieder nicht transparent umschalten (dämliche WSA :roll:). Ich habe das schon länger auf der ToDo-Liste :oops: so ist das nicht (du bist nicht der Erste, der mit der Kompo einen EC ansteuern möchte). Ich habe aber noch keinen wirklich guten Ansatz gefunden, wie man dieses Problem lösen kann. :nixweiss: Und deshalb: leider gibt´s da aktuell keine Möglichkeit über die Komponenten-API zu. Diesen Teil (Broadcast-Discover) müsstest du dann erstmal direkt über die WSA machen und dann erst mit einer IP für die Unicasts wieder auf der Kompo aufsetzen. :idea:

Ja, vorsichtig gesagt "unbefriedigend", leider habe ich bei der Entwicklung nicht bereits an diese Problematik bei EC-Discover-Tasks gedacht, so dass das jetzt im Nachhinein nicht mehr gut zu fassen ist, sorry. :|

cu
Narses


Bastler - Fr 24.02.12 09:52

Moin, Moin,

Schade, trotzdem Danke für die schnelle, umfassende Antwort!

Gruß

Udo


Narses - Fr 24.02.12 10:40

Moin!

Hm :gruebel: mir ist da so über Nacht im Bettchen liegend eine Idee gekommen, vielleicht kann man das als "Workaround" billig umsetzen. Ich schau mir das mal am WE an, vielleicht habe ich am Montag was, womit man das gelöst kriegt. ;)

cu
Narses


Narses - Di 28.02.12 02:25

Moin!

Es ist zwar nicht (mehr) Montag, dafür war es aber auch nicht "billig" umzusetzen... :nut: :lol: :| egal, war aber wohl langsam mal fällig. :nixweiss:

Hier vorab die Version 2.0 des TUdpSockUtil für dich zum Testen, bis ich die Anleitung aktualisiert habe, dann aktualisiere ich das auch im entsprechenden Thread. Changelog im Quelltext. Quick-Hints:cu
Narses

//EDIT: Download der neuen Komponenten-Version in diesem Thread [http://www.delphi-forum.de/viewtopic.php?t=55339].


Bastler - Di 28.02.12 20:31

Moin, Moin!
das ist ja mal ein Service: eine neue Version zu meinem Problem!!

ich habe das Update installiert und damit gearbeitet. Ich bekomme tatsächlich ein Receive-Ereignis allerdings ist es leer. Hier mein Sourcebeispiel:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
procedure TForm1.FormCreate(Sender: TObject);
begin
  UdpSockUtil1.RemotePort := 3490;
  UdpSockUtil1.LocalPort  := 3490;
  UdpSockUtil1.Open;
  udpsockutil1.Listen := True;
end;

procedure TForm1.UdpSockUtil1Receive(Sender: TObject);
var
  vonIP : in_addr;
  puffer: String;
begin
 puffer := form1.UdpSockUtil1.ReceiveText(vonIP);
 Form1.Memo1.Lines.Add('Receive  '+IntToStr(Timecount)+' '+'  E: '+puffer+'     IP='+ inet_ntoa(vonIP));
end;

procedure TForm1.Button3Click(Sender: TObject);
Var abriss : boolean;
    vonIP : in_addr;
    port  : integer;

begin
  form1.UdpSockUtil1.Broadcast := True;
  form1.UdpSockUtil1.SendText('A'+#$A);
  form1.UdpSockUtil1.Broadcast := False;
end;
Wireshark zeigt, dass der Broadcast kommt und der Server antwortet. Leider ist die Antwort und die IP im Receive-Event leer.
Das Memofeld Memo1 zeigt:
Zitat:
Receive 0 E: IP=0.0.0.0

Wo hängt's?

Moderiert von user profile iconNarses: Delphi-Tags hinzugefügt


Narses - Di 28.02.12 23:19

Moin!

user profile iconBastler hat folgendes geschrieben Zum zitierten Posting springen:
das ist ja mal ein Service: eine neue Version zu meinem Problem!!
:beer: Wie ich schon angedeutet habe, warst du jetzt nicht der Erste mit diesem Problem. Irgendwann hätte ich eh ran gemusst, da ich die Kompo natürlich auch selbst verwende und mich da schon länger einiges gestört hat. :nixweiss: Also, zur richtigen Zeit am richtigen Ort gewesen. ;)

user profile iconBastler hat folgendes geschrieben Zum zitierten Posting springen:
Wo hängt's?
Wie schon angedeutet ist das Konzept der API jetzt etwas anders.

So sollte das jetzt eigentlich laufen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
// bei Programmstart
procedure TForm1.FormCreate(Sender: TObject);
begin
  UdpSockUtil1.LocalPort  := 3490;
  UdpSockUtil1.RemotePort := 3490;
  UdpSockUtil1.Broadcast  := True; // dauerhaft mit dem Broadcast-Mode arbeiten (erst ab v2.00 der Kompo!)
  UdpSockUtil1.Open; // Listener aktivieren (ab jetzt sind Ereignisse möglich)
end;

// Broadcast-Discover
procedure TForm1.Button1Click(Sender: TObject);
begin
  UdpSockUtil1.RemoteHost := '255.255.255.255'// Broadcast-Adresse für Broadcast-Sendungen
  UdpSockUtil1.SendText('A'+#$A); // ich schätze mal, das heißt "wer ist da?" ;)
end;

// Empfangsereignis
procedure TForm1.UdpSockUtil1Receive(Sender: TObject);
  var
    vonIP : in_addr;
    vonPort: Integer;
    puffer: String;
begin
 puffer := UdpSockUtil1.ReceiveText(vonIP, vonPort); // IP und Port beim Lesen mit abfragen
 Memo1.Lines.Add('Receive  '+IntToStr(Timecount)+' '+'  E: '+puffer+'  IP='+inet_ntoa(vonIP)+':'+IntToStr(vonPort));
end;

// Unicast-Senden
procedure TForm1.Button2Click(Sender: TObject);
begin
  UdpSockUtil1.RemoteHost := '192.168.0.1'// Unicast geht auch im Broadcast-Mode, hängt von der Adresse ab!
  UdpSockUtil1.SendText('B'+#$A); // das Kommando 'B' hab ich jetzt mal nur als Demo benutzt
end;
Ich bin gespannt. ;)

cu
Narses


Bastler - Do 01.03.12 19:13

Moin, Moin,

ich hab's endlich ausprobieren können und jawoll: es geht!!! :beer:
Heissen Dank! jetzt kann's losgehen mit der Kommunikation mit dem EC!

Gruß

Udo


Narses - So 18.03.12 16:50

Moin!

Falls du es übersehen haben solltest: es gibt jetzt hier das offizielle Release [http://www.delphi-forum.de/topic_TUdpSockUtil+v201++Alternative+zum+IndyUDPClientServer_55339.html], du solltest die v2.0 besser nicht produktiv verwenden. :idea: ;)

cu
Narses