Hallo zusammen
Ich probier nun schon seit Tagen (bald Wochen) an einem Client-Server Programm mit D6 Pro und den Indy9 Komponenten (TIdTCPClient, TIdTCPServer). Das ganze ist zurzeit Neuland für mich. Da ich in diesem Forum am meisten zu den
Indy Komponenten gefunden habe, möchte ich mein Projekt hier zur Begutachtung (und Vorschläge zur Verbesserung entgegennehmen) unterbreiten.
Das Projekt baut auf einem sehr guten Forumsbeitrag vom Jan 2004 von negaH auf.
Da ich kein Profiprogrammierer bin hat es viele Unschönheiten im Code. Ich bitte Euch das Augenmerk vor allem auf die
IP-Kommunikation zwischen Server und Client und umgekehrt zu richten.
Die aktuellen Unklarheiten:
- Im Client habe ich eine Schleife 'Repeat .... until False' eingebaut. Wahrscheinlich würde das auch besser anders gehen.
- Die Function 'Readkundendatei' ist praktisch gleich wie die Function 'ReadString' (im Client). Die liessen sich wahrscheinlich in einer Function zusammenfassen (wenn man wüsste wie, in den Functionen ReadByte und ReadString sehe ich nicht so durch).
- Ich sende zuerst die Grössenangabe der zu sendenden Datei an den Client, anschliessend wird die Datei mit dieser Grössenangabe vom Client entgegengenommen. Das lässt sich sicher viel besser machen.
Idee des Projektes:
Der Client meldet sich mit seiner Kennung am Server an. Nach der Antwort vom Server sendet der Client
dem Server eine Kundennummer und ein bestimmtes Merkmal (z.B. 000456765ALL (das mit dem Merkmal ist in der vorliegenden QuellcodeForm noch nicht drin)). Der Server nimmt die Kundennnummer entgegen liest die zugehörige Textdatei von der Disk ein. Filtert die entsprechenden Datensätze je nach Merkmal heraus und sendet das Ergebnis dem Client. Der Client nimmt die Daten entgegen, sendet dem Server eine Trennanforderung, und zeigt die erhaltenen Daten dem Bediener an.
Bemerkung zur Beilage:
Das Projekt liegt dem Beitrag als Zip-Datei (mit Quellcode und kompiliert) bei. Die Zipdatei bitte mit Ordnerstruktur entpacken, dann funktionierts auf Anhieb.
Untenstehend habe ich den Code des Servers und Client gepostet. Wahrscheinlich könnt Ihr aufgrund dessen bereits ein paar Tips abgeben.
Danke für Eure Stellungnahme, Kommentare und Hilfe.
Gruess
Dani
Quellcode des Servers für IP- Kommunikation:
Delphi-Quellcode:
procedure TFrmHaupt.IdTCPServer1Execute(AThread: TIdPeerThread);
var
SessionID: Integer;
// ----------------
procedure DoLog(
const Msg:
String);
// Log schreiben
var
S:
String;
I: Integer;
DatumZeit :
String;
begin
DateTimeToString (DatumZeit,'
hh:nn:ss:zzz', Now);
// Aktuelles Datum eintragen zzz ist millisekunden
Memo1.Lines.Add('
ID:' + IntToStr (SessionID) + '
-' + Datumzeit + '
:' + Msg );
end;
// ----------------
function ReadByte: Byte;
begin
AThread.Connection.ReadBuffer(Result, SizeOf(Result));
end;
// ----------------
function ReadLong: Cardinal;
function SwapLong(Value: Cardinal): Cardinal;
// konvertiert Big Endian zu Little Endian
// im INet ist es üblich ALLE Daten in Big Endian zu übertragen
asm
BSWAP EAX
end;
begin
AThread.Connection.ReadBuffer(Result, SizeOf(Result));
Result := SwapLong(Result);
end;
// ----------------
function ReadString:
String;
begin
SetLength (Result, ReadByte);
AThread.Connection.ReadBuffer (Result[1], Length(Result));
end;
// ----------------
procedure WriteByte (Value: Byte);
begin
AThread.Connection.WriteBuffer (Value, SizeOf(Value));
end;
// ----------------
procedure WriteString(
const Value:
String);
begin
WriteByte(Length(Value));
// DoLog ('SERVER: Procedure WriteString, Länge des Strings:' + IntToStr (Length(Value)) + ', StringInhalt:' + Value);
AThread.Connection.WriteBuffer (Value[1], Length(Value));
end;
// ----------------
var
KdNr :
String;
KundenDaten :
String;
DateiGroesse:
String;
begin // ------------ Hauptprocedur von ServerExecute --------------------
SessionID := LastSessionID;
Inc (LastSessionID);
DoLog ('
TFrmHaupt.IdTCPServer1Execute(AThread: TIdPeerThread); gestartet ===============');
try
if (ReadByte = 1)
and (ReadString = '
Preisliste@Web')
then begin // LOGIN
DoLog ('
ReadByte=1 und ReadString=Preisliste@Web vom Client erhalten.');
// Client hat sich korrekt identifiziert sende Bestätigung und warte auf Kommandos
WriteByte (1);
DoLog ('
WriteByte=1 an Client gesendet.');
while True
do begin
case ReadByte
of
0:
begin // NULL
DoLog ('
SERVER: ReadByte=0 vom Client erhalten');
WriteByte (0);
end;
2:
begin // LOGOUT
DoLog ('
SERVER: ReadByte=2, Abmeldeanforderung vom Client erhalten.');
Break;
DoLog ('
SERVER: Break, Client meldet ab');
end;
3:
begin // ERROR
DoLog (Format ('
SERVER: ReadByte=3, Client Fehler %d, %s', [ReadByte, ReadString]));
Break;
// Fehler führt IMMER zum Abbau der Verbindung
end;
4:
begin // Text senden
// Zu aufbereitende Kundennummer vom Client lesen Anfang
DoLog ('
Readbyte=4, Aufzubereitende KdNr wird nun vom Client gesendet.');
KdNr := ReadString;
DoLog ('
ReadString Aufzubereitende KdNr vom Client erhalten:' + KdNr + '
<');
// Zu aufbereitende Kundennummer vom Client lesen Ende
// Kundendaten aufbereiten Anfang
KundenDaten := KundenDatenAufbereiten (KdNr, DateiGroesse, SessionID);
// Kundendaten aufbereiten Ende
// Dateigrösse an Client senden Anfang
WriteByte (6);
DoLog ('
WriteByte=6, Dateigrösse der zu sendenden KdDaten wird nun an den Client gesendet.' );
WriteString (DateiGroesse);
DoLog ('
WriteString='+DateiGroesse+'
, (Dateigrösse) an Client gesendet.');
// Dateigrösse an Client senden Ende
// Kundendatei an Client senden Anfang
WriteByte (5);
DoLog ('
WriteByte=5, Gesamte Kundendatei wird nun an den Client gesendet.');
WriteString (KundenDaten);
DoLog ('
WriteString="Gesamter Inhalt der Kundendatei" an Client gesendet');
// Kundendatei an Client senden Ende
end;
else
begin // Invalid Code;
DoLog ('
Hier wäre ein Break, Readbyte ist <> 0,2,3,4');
WriteByte (3);
DoLog ('
WriteByte=3 gesendet');
WriteByte (0);
// Errorcode
DoLog ('
WriteByte=0 gesendet');
WriteString ('
ungültiger Befehl');
DoLog ('
WriteString="ungültiger Befehl" an Client gesendet');
Sleep (1);
// Break;
DoLog ('
Hier wäre ein Break');
end;
end;
end;
// falsches/fehlendens Client-Login, trenne einfach die Verbindung, Server ist im Stealth mode
end
Else begin
DoLog ('
ReadByte=? und ReadString=? vom Server erhalten -> Login geht nicht');
End;
finally
DoLog ('
Finally, Client Verbindung geschlossen.');
try
Sleep (1);
AThread.Connection.Disconnect;
DoLog ('
Finally -> Try, AThread.Connection.Disconnect;');
except
DoLog ('
Finally -> Try -> Except, Irgendein Fehler ist passiert.');
end;
end;
DoLog ('
TFrmHaupt.IdTCPServer1Execute(AThread: TIdPeerThread); ende der Schlaufe ============');
end;
Quellcode des Clients für IP- Kommunikation:
Delphi-Quellcode:
procedure TFrmHaupt.DatenAnServerSenden (VonWoKommtAufruf :
String);
Var DateiGroesse :
String;
// ----------------
procedure DoLog(
const Msg:
String);
// Log schreiben
var
DatumZeit :
String;
begin
DateTimeToString (DatumZeit,'
hh:nn:ss:zzz', Now);
// Aktuelles Datum eintragen zzz ist millisekunden
Memo1.Lines.Add (Msg + '
-' + Datumzeit);
end;
// ----------------
procedure WriteByte (Value: Byte);
begin
TCP.WriteBuffer (Value, SizeOf(Value));
end;
// ---------------
procedure WriteString (
const Value:
String);
begin
WriteByte (Length (Value));
TCP.WriteBuffer (Value[1], Length (Value));
end;
// ---------------
function ReadByte: Byte;
begin
TCP.ReadBuffer (Result, SizeOf (Result));
// showmessage (inttostr (ord(65))); // ergibt -> A
DoLog ('
CLIENT:In Funct ReadByte. ASCII-Wert vom gelesenen Byte:' + IntToStr (Ord(Result)));
// IntToStr(Ord('c')) chr(65) ergibt A
end;
// ---------------
function ReadString:
String;
begin
SetLength (Result, ReadByte);
TCP.ReadBuffer (Result[1], Length (Result));
end;
// ---------------
function ReadKundenDatei:
String;
begin
SetLength (Result, StrToInt(DateiGroesse)-1) ;
TCP.ReadBuffer (Result[1], Length (Result));
end;
// ---------------
begin // // --------------- Client Hauptprocedur -------------------------
Randomize;
// Zufallszahlen initialisieren
TCP.Host := EdtServerIP.Text;
DoLog (VonWoKommtAufruf);
try
try
TCP.Connect;
DoLog ('
CLIENT: TCP.Connect -> Kommunikation mit Server aufgenommen.');
WriteByte (1);
DoLog ('
CLIENT: WriteByte=1 an Server gesendet.');
WriteString ('
Preisliste@Web');
DoLog ('
CLIENT: WriteString, "Preisliste@Web" an Server gesendet.');
Repeat
case ReadByte
of
1:
begin
DoLog ('
CLIENT: ReadByte=1 vom Server erhalten.');
WriteByte(4);
DoLog ('
CLIENT: WriteByte=4 an Server gesendet.');
KdNrTmp := KundenListe.Strings[Random (5)];
//Zufallswert aus der Kundenliste nehmen
WriteString (KdNrTmp);
DoLog ('
CLIENT: WriteString, KdNr: ' + KdNrTmp + '
, an Server gesendet. Anforderung der Kundendaten');
end;
3:
begin
DoLog ('
CLIENT: ReadByte=3 vom Server erhalten.');
ShowMessage(Format('
Fehler %d, %s', [ReadByte, ReadString]));
end;
5:
begin // Kundendatei vom Server lesen
DoLog ('
CLIENT: ReadByte=5 vom Server erhalten.');
KundenDaten := ReadKundenDatei ;
KundenDatenLokalSpeichern (KundenDaten, KdNrTmp);
DoLog ('
CLIENT: ReadString, vom Server erhalten: GesamteKundenDatei');
DoLog ('
CLIENT: Länge ReadString:' + IntToStr (Length(KundenDaten)) + '
<');
WriteByte(2);
// Abmeldeanforderung an Server senden
DoLog ('
CLIENT: WriteByte=2 an Server gesendet (Disconnectanforderung)');
DoLog ('
CLIENT: "Repeat .... Until False" wird nun mit Exit beendet');
Exit;
end;
6:
begin // Dateigrösse der zu lesenden Datei vom Server lesen
DoLog ('
CLIENT: ReadByte=6 (DateiGroesse) vom Server erhalten.');
DateiGroesse := ReadString ;
DoLog ('
CLIENT: ReadString, vom Server erhalten (DateiGroesse):' + DateiGroesse + '
<');
end;
else
DoLog ('
CLIENT: ReadByte=? vom Server erhalten. Ungültiger Antwortcode.');
ShowMessage('
Ungültiger Antwortcode.');
end;
Until False;
// unendliche Schlaufe
finally
TCP.Disconnect;
DoLog ('
CLIENT: Finally, tcp.disconnect =============================================================');
end;
except
on E:
Exception do
DoLog ('
CLIENT: on Exception:' + E.
Message);
end;
DoLog ('
CLIENT: Ende Schlaufe Daten an Server senden =============================================================');
End;