Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Netzwerke (https://www.delphipraxis.net/14-netzwerke/)
-   -   Delphi Probleme mit Server-Client-Programmierung (Indy10) (https://www.delphipraxis.net/85273-probleme-mit-server-client-programmierung-indy10.html)

albert1985 28. Jan 2007 12:57


Probleme mit Server-Client-Programmierung (Indy10)
 
Hi,

Also so langsam bin ich noch am verzweifeln ... Ich versuche schon längere Zeit mich mit der
Server-Client Programmierung zu beschäftigen, hab auch schon alle mögliche Demos etc., die ich gefunden hab
durchprobiert, aber leider sind die meistens für mich zu komplex ...
Ich bin absoluter Beginner und würde mich gerne mit indy10 näher beschäftigen. Leider
habe ich selbst Probleme die Schritte beim Connecten des Clienten zum Server zu verstehen ... :(

Hoffe ihr könnt mir dabei etwas behilflich sein :)
Hier erstmal der Teil den ich verstanden hab ... (hab zwei Edit Felder und ein Connect Button)
Delphi-Quellcode:
with IdTCPClient1 do try  
    Host := HostEdit.text;
    Port := StrToInt(portEdit.text);
    Connect;
Nun möchte ich es zB ermöglichen das ich einen befehl an den Server schicke, der zB einen "MessagDlg"
ausgibt ...

Es würde mir SEHR weiterhelfen, wenn mir jemand genau erklären könnte wie dass mit ReadLn/WriteLn etc. funktioniert ... Also auch so, dass ich verstehe, was diese "Befehle" überhaupt genau bewirken und wie ich das serverseitig "abfangen" kann ... Danke schonmal im Vorraus :)

M f G

albert1985 28. Jan 2007 16:15

Re: Probleme mit Server-Client-Programmierung (Indy10)
 
Also bin jetzt schon selbst etwas weitergekommen ! Gibt aber immer noch Probleme :( ...

Hab jetzt folgenden Quelltext für den Client:
Delphi-Quellcode:
procedure TForm1.NachrichtButtonClick(Sender: TObject);
begin
  if IdTCPClient1.Connected = true then begin
    with IdTCPClient1 do try
      IOHandler.WriteLn('MSG');
    finally end;
  end;
end;
und entsprechend für den reagierenden Client:

Delphi-Quellcode:
procedure TForm1.FormCreate(Sender: TObject);
begin
  IdTCPServer1.Active := True;
  with IdTCPServer1.Bindings.Add do
  begin
    IP:='127.0.0.1'; // zum Testen 127.0.0.1 ...
    Port:=3666; // Kann man hier einen beliebigen freien Port wählen?
  end;
end;

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
  Line : String;
begin
  Line := AContext.Connection.IOHandler.ReadLn;          
    if Line = 'MSG' then
    MessageDlg('Huhu - der Client hat sich verbunden',mtInformation,[mbOK],0);
end;
1. Ist dieser Code so in etwa richtig, wenn ich die besagte Meldung anzeigen lassen will??
2. Es erscheint wenn ich beide Programme ausführe ein Fehler der besagt : Connection Timeout / Connection refused
woran kann das liegen ?

MfG

inherited 28. Jan 2007 17:11

Re: Probleme mit Server-Client-Programmierung (Indy10)
 
Du musst im Client den Host auf 127.0.0.1 und den Port auf einen bliebigen Integerwert setzen. Bei dem Server Defaultport auf den gleichen wert und active auf true. Dann kannst du dir das mit den Bindings sparen und müsste funktionieren.

Der_Unwissende 28. Jan 2007 17:28

Re: Probleme mit Server-Client-Programmierung (Indy10)
 
Zitat:

Zitat von inherited
den Port auf einen bliebigen Integerwert setzen

Hi,
das ist so nicht korrekt. Kann zwar auf dem eigenen System klappen, muss es aber schon nicht. Es gibt verschiedene Gruppen von Ports, die Ports von 0-1023 sind sogenannte "Well-Known-Ports". Diese sind alle festen Diensten zugeordnet, die sich natürlich nicht an diese Ports halten müssen, aber es ist davon auszugehen, dass viele der Dienste auch wirklich auf diesen Ports laufen und entsprechend Anfragen auf diesen Ports gestellt werden.
Die Ports 1024 - 49151 sind dann die "registered-Ports". Diese Ports wurden bereits für bestimmte Anwendungen (besser gesagt ein Teil der Ports) registriert. Hier spricht einiges dafür, dass eine Verbindung auf einem dieser Ports auch zu der entsprechenden Anwendung gehört. Das heißt für Dich vor allem, dass hier schon ein Dienst laufen kann, aber eben auch, dass dein Programm durch den Port leicht für ein laufendes anderes gehalten werden könnte.
Die restlichen Ports 49152 - 65531 sind dann die "Private-Ports", hier gibt es keine festen Zuordnungen, schnapp Dir einen Port der frei ist und mach was Du willst :wink:

Gruß Der Unwisssende

albert1985 28. Jan 2007 17:42

Re: Probleme mit Server-Client-Programmierung (Indy10)
 
Danke für die Antworten ;)

Ich weiß jetzt auch was bei mir falsch war... Ich hätte anstatt "Port" "DefaultPort" schreiben müssen ...
Sonst kann das natürlich nicht funktioniern !

inherited 28. Jan 2007 18:05

Re: Probleme mit Server-Client-Programmierung (Indy10)
 
Zitat:

Zitat von Der_Unwissende
Zitat:

Zitat von inherited
den Port auf einen bliebigen Integerwert setzen

Hi,
das ist so nicht korrekt. Kann zwar auf dem eigenen System klappen, muss es aber schon nicht. Es gibt verschiedene Gruppen von Ports, die Ports von 0-1023 sind sogenannte "Well-Known-Ports". Diese sind alle festen Diensten zugeordnet, die sich natürlich nicht an diese Ports halten müssen, aber es ist davon auszugehen, dass viele der Dienste auch wirklich auf diesen Ports laufen und entsprechend Anfragen auf diesen Ports gestellt werden.
Die Ports 1024 - 49151 sind dann die "registered-Ports". Diese Ports wurden bereits für bestimmte Anwendungen (besser gesagt ein Teil der Ports) registriert. Hier spricht einiges dafür, dass eine Verbindung auf einem dieser Ports auch zu der entsprechenden Anwendung gehört. Das heißt für Dich vor allem, dass hier schon ein Dienst laufen kann, aber eben auch, dass dein Programm durch den Port leicht für ein laufendes anderes gehalten werden könnte.
Die restlichen Ports 49152 - 65531 sind dann die "Private-Ports", hier gibt es keine festen Zuordnungen, schnapp Dir einen Port der frei ist und mach was Du willst :wink:

Gruß Der Unwisssende

Dessen bin ich mir wohl bewusst, nur würde das zu erklären zu sehr vielen verwirrungen seiner seits führen, was widerrum nicht der Sinn eines Forums dieser Art sein sollte. Was also soll es, ihn jetzt mit so vielen neuen Begriffen zu verwirren?

Der_Unwissende 28. Jan 2007 18:33

Re: Probleme mit Server-Client-Programmierung (Indy10)
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hi, nochmal etwas ausführlicher zu deinem Problem:

Zitat:

Zitat von albert1985
Ich versuche schon längere Zeit mich mit der
Server-Client Programmierung zu beschäftigen, hab auch schon alle mögliche Demos etc., die ich gefunden hab
durchprobiert, aber leider sind die meistens für mich zu komplex ...
Ich bin absoluter Beginner und würde mich gerne mit indy10 näher beschäftigen. Leider
habe ich selbst Probleme die Schritte beim Connecten des Clienten zum Server zu verstehen ... :(

An sich ist die Arbeitsweise von einem Server einfach. Die einzigste Aufgabe, die ein Server hat liegt darin, dass er Anfragen entgegen nimmt und beantwortet. Der eigentliche Programmablauf des Servers besteht dabei in einer Endlosschleife, in der der Server eine Anfrage annimmt, irgendwas mit ihr tut und ggf. eine Antwort zurückschickt. Ein Protokoll regelt dabei natürlich wie eine Anfrage und wie die Antworten aussehen. Das Protokoll wäre der Teil, der auf dem TCP-Server aufbaut, hier also erstmal nicht weiter wichtig.

Ok, hoffe die grundsätzliche Arbeitsweise ist klar (die ist wirklich so einfach wie sie hier steht). Jetzt habe ich schon gesagt, dass die Anfragen und Antworten vom verwendeten Protokoll abhängen. Möchtest Du einen Webserver betreiben, wird das Protokoll sicherlich HTTP sein, betreibst Du aber einen FTP-Server, sieht das anders aus. Da die Jungs von Indy hier nicht jeden möglichen Fall abdecken können (Du kannst schließlich selbst ein eigenes Protokoll verwenden), müssen sie einem also die Möglichkeit geben, dass Du festlegst was passiert wenn eine Anfrage eingeht. Um es anders zu sagen, Du musst festlegen was in der Endlosschleife passiert. Dazu wurde die Eigenschaft OnExecute des TIdTcpServers vorgesehen. Diese Eigenschaft ist vom Typ TIdServerThreadEvent (eine Prozedur mit einem Parameter vom Typ TIdContext). Der Parameter AContext gibt dabei einfach (wie der Name schon sagt) den Context an, in dem ein Ereignis gerade stattfindet. Zu diesem Kontext gehört z.B. der Absender der Anfrage, der Empfänger und vieles mehr (die Beiden sind aber erstmal das Wichtigste!). Der Thread wird also in einer Endlosschleife immer diese Prozedur aufrufen und den Code der Prozedur aufrufen. Der Context wird dir automatisch übergeben. Nur wenn diese Eigenschaft zugewiesen wurde, kannst Du den Server starten (er wüßte sonst auch nicht was er tun soll).

Zu den Eigenschaften des AContext gehört unter anderem auch die Connection. Indy verwendet zum Senden von Daten über eine Verbindung das Framework der IOHandler. Ein IOHandler bietet für dich einfache Möglichkeiten Daten zu verschicken. Der eigentlche Vorteil für Dich besteht darin, dass Du viele Details des TCP/IP Kanals nicht zu sehen bekommst. So basiert die Kommunikation bei TCP/IP auf dem verschicken von Paketen. Diese Pakete bestehen wiederum aus deinen Nutzdaten und einer ganzen Menge Metadaten. Die Metadaten beinhalten unter anderen die Adressen von Sender und Absender, aber auch Daten zur Fehlererkennung. Wird ein Paket empfangen, so wird der Empfang bestätigt (ACK). Wird ein Paket empfangen, aber stimmt die Checksumme nicht, so wird eine negative Bestätigung verschickt (NAK). War ein Paket defekt oder wird sein Empfang nicht bestätigt, so wird es automatisch erneut verschickt. All das ist für transparent gekapselt. Auch musst Du dich nicht um die Zerlegung dein Daten in einzelne Pakete kümmern.
Dir stehen durch die IOHandler Methoden wie ReadLn oder WriteLn zur Verfügung. Die Methode Readln liest dabei blockierend einen String aus. Dazu wird auf Pakete mit Nutzdaten gewartet. Die Nutzdaten werden als String interpretiert. Wird hier ein bestimmtes Zeichen gefunden, dass das Ende einer Linie markiert (sollte wohl Carriage Return sein), wird der eingelesene String als Ergebnis der Funktion zurückgegeben. Die Funktion wird dabei erst verlassen, wenn eben dieses Zeichen gelesen wurde (oder ein Fehler, z.B. timeout, eintritt).
Wie jetzt das Lesen genau aussieht kann dem Beispiel entnommen werden, dass ich angehangen habe.

Jetzt sollte schon grob klar sein, wie der Server arbeitet. Sobald man ihn startet, wird er in einer Endlosschleife die übergebene Methode ausführen. In dieser bekommt man einen Kontext, in dem Daten ankommen. Über den Kontext lassen sich Absender und Empfänger bestimmen, sowie die Daten auslesen und verwenden.

Jetzt hatte ich mehrfach erwähnt, dass auch der Empfänger zu dem Kontext gehört. Das mag etwas verwirrend sein, zumal der Server der die Anfrage bekommt sicherlich sich selbst als Empfänger ansehen sollte. Das Problem dabei ist, dass ein Server auf einer Maschine unter ganz unterschiedlichen Adressen erreicht werden kann. So ist jeder Server z.B. über die Loopback Adresse 127.0.0.1 zu erreichen, aber auch jede Netzwerkkarte hat eine eigene Adresse. Verwendet eine Maschine z.B. zwei Netzwerkkarten, die in unterschiedliche Subnetze führen, so kann der Server hier lokale Anfragen (von der 127.0.0.1) und von den beiden Karten jeweils unterschiedlich behandeln wollen. Deswegen ist es wichtig, dass auch die Zieladresse der Anfrage mit übergeben wird.
Die Adresse besteht dabei aus der IP und dem Port. Somit kann ein Server natürlich auch mehr als einen Dienst anbieten, wobei der Dienst dann über IP und Port ermittelt werden kann.

Das ganze findet man dann auch in der Eigenschaft Bindings wieder. Die Bindungen geben die Kombinationen aus IP und Port an, auf denen der Server lauscht. Wie bereits gesagt, ist es durchaus möglich (und teilweise sinnvoll) auf mehr als einem Port und mehr als einer IP zu lauschen. Für diese Fälle kann über die Eigenschaft Bindings für jede Adresse eine eigene Bindung angegeben werden, auf die der Server dann reagiert. Welche dabei angesprochen wurde kann aus dem Kontext der Methode OnExecute ermittelt werden.

Damit sollte grob die komplette Arbeitsweise des Servers klar sein, er bindet Adressen und Ports an sich. Anfragen die auf diesen bebundenen Adresesn ankommen werden in ihrem Kontext an einer Prozedur übergeben. Diese nimmt die Anfragen in einer Endlosschleife entgegen und macht etwas mit ihnen (hängt von der eigenen Implementierung ab).

Auf der anderen Seite steht dann der Client. Dieser verbindet sich gezielt mit einer bestimmten Adresse (an deren Ende ein Server stehen muss). Antwortet kein Server, kommt keine Verbindung zu stande, was zu einem Fehler führt. Wurde eine Antwort empfangen, so kann der Client eine Anfrage abschicken und auf die Antwort durch den Server warten. Ob eine Antwort kommt und was für eine hängt wieder vom Protokoll ab. Im einfachsten Fall sendet der Client nur eine Anfrage ab. Auch dies geschieht über einen IOHandler, der einem diese Arbeit abnimmt.

Ja, ich hoffe dass es Dir etwas weiterhilft. Du hast leider nicht gesagt, welcher Teil der Demos Dir zu komplex war. Solltest Du etwas weiterhin nicht verstehen, frag ruhig gezielter nach (poste den Code den Du nicht verstehst), dann kann man Dir auch gezielter helfen. Anbei noch ein sehr einfaches Beispiel, besteht aus Server und Client. Wichtig ist, dass Du erst den Server startest, dann den Client. Der Client muss auch erst mit dem Server verbunden werden, bevor Du etwas verschickst. Fehler werden (unsauberer Weise) noch gar nicht abgefangen. Alles was Du vom Client aus verschickst, wird in einer ListBox im Server angezeigt.

Gruß Der Unwissende

albert1985 28. Jan 2007 19:01

Re: Probleme mit Server-Client-Programmierung (Indy10)
 
Wow, da hast du dir aber wirklich Mühe gemacht :) Vielen Dank !

Also die Probleme die ich oben angesprochen hatte sind jetzt ausgemerzt :)

Habe es jetzt auch hinbekommen, dass ich vom Client per WriteLn einen String an den Server
schicke und dass dieser wiederrum das ganze verarbeitet (in meinem Fall ein MessageDlg ausgeben).

Da hätte ich auch schon die nächste Frage: :D
Und zwar, wie kann ich nun vom Clienten aus eine z.B. über ein Edit-Feld eingegebene Nachricht
auf dem Server anzeigen lassen (MessageDlg wie im oberen von mir geposteten Quellcode) ?

Gibts da überhaupt eine Möglichkeit über WriteLn / ReadLn ? Weil
man müsste ja dann quasi einen String, auf den der Server reagieren soll mit einem "beliebigen" String
kombinieren ...
Delphi-Quellcode:
if Line = 'MSG' + beliebiger String  then
    MessageDlg('der "Inhalt" des beliebigen Strings',mtInformation,[mbOK],0);

inherited 28. Jan 2007 19:17

Re: Probleme mit Server-Client-Programmierung (Indy10)
 
Du kannst entweder ein Command, gefolgt von einem Trennzeichen und dann die Nachricht senden, oder du schickst es einzeln Hintereinander.
Fall eins, Client:
Delphi-Quellcode:
procedure TForm1.NachrichtButtonClick(Sender: TObject);
begin
  if IdTCPClient1.Connected then
  begin
    with IdTCPClient1 do
      IOHandler.WriteLn('MSG:'+Edit1.text);
  end;
end;
Fall eins, Server:
Delphi-Quellcode:
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
  Line, Command, Param : String;
begin
  Line := AContext.Connection.IOHandler.ReadLn;
  Command:= Copy(Line, 1, Pos(':', Line)-1);
  Param:= Copy(Line, Pos(':', Line)+1, Length(Line));        
  if Command = 'MSG' then
    MessageDlg(Param, mtInformation,[mbOK],0);
end;

albert1985 28. Jan 2007 19:53

Re: Probleme mit Server-Client-Programmierung (Indy10)
 
Sieht elegant aus, deine Lösung ;) Danke !


Alle Zeitangaben in WEZ +1. Es ist jetzt 13:55 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