Einzelnen Beitrag anzeigen

Der_Unwissende

Registriert seit: 13. Dez 2003
Ort: Berlin
1.756 Beiträge
 
#7

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

  Alt 28. Jan 2007, 19:33
Hi, nochmal etwas ausführlicher zu deinem Problem:

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
Angehängte Dateien
Dateityp: zip serverclient_609.zip (18,9 KB, 51x aufgerufen)
  Mit Zitat antworten Zitat