![]() |
Indy-TCP-Server aufsetzen und Dateien versenden
Hagen hat
![]() Zuerst bauen wir den Server und erklären dabei das benutzte Protokoll. Als erstes definieren wir was als wichtigstes und erstes in das projekt integeriert werden soll. Wir beschränken uns erstmal auf das Notwendigste, also keine Progressbar und nur Upload von Dateien vom Clienten aus den Server. Erzeuge neues Project und nenne es Server. Auf's Hauptformular bringste einen INDY TCP/IP Server drauf. Bei diesem stellste folgendes ein:
Delphi-Quellcode:
Zusätzlich ein TMemo mit Namen Memo1 und folgenden Eigenschaften:
.Bindings := 0.0.0.0:7000;
.DefaultPort := 7000; .ReuseSockects := rsTrue; .Active := True;
Delphi-Quellcode:
Nun im OI in das Ereigniss .OnExecute ein Doppelklick. Wir landen im Sourceeditor und coden NUR diese eine Execute Methode, denn mehr benötigen wir erstmal garnicht.
.Font := 'Courier New';
.Align := alClient; .Lines := ''; .WordWrap := False; .Scrollbars := ssBoth; Damit klarer wird was wir möchten definieren wir aber erstmal unser Protokoll. Jede Kommunikation beginnt mit einem Kommando-Byte, somit stehen uns maximal 256 verschiedene Kommandos zur Verfügung. Per Definition legen wir folgende Kommandos fest: 0 = NULL, heist keine Aktion und dient als Dummy Message um eventuell connections am leben zu erhalten 1 = LOGIN, der Client sendet dieses Kommando IMMER als erstes, über dieses Kommando identifiziert sich ein Client und die Clientsoftware. Ein Server antwortet NUR auf eine solches korrektes Client Login. D.h. dieses Kommando MUSS das erste sein was der Server empfängt. Falls dies nicht der Fall ist trennt der Server sofort die Conection. 3 = ERROR, Fehlerpacket 4 = LOGOUT, der Client trennt die verbindung 5 = UPLOAD, der Client sendet eine Datei, direkt nach dem Kommando-Byte folgen der dateiheader und die eigentliche Datei Der Server hat auf jedes Kommando mit einem Echo-Kommando zu bestätigen. D.h. wenn der Client das Kommando=1=LOGIN sendet antwortet der Server ebenfalls mit Kommando=1=LOGIN bei Erfolg oder er antwortet mit Kommand=3=ERROR und trennt die Verbindung. Genauer werden wir erstmal das Protokoll nicht beschreiben, die Sourcen sollten es erklären. Nun der Code in OnExecute() sollte so aussehen:
Delphi-Quellcode:
Fertig ist der Server. Er sollte KEINERLEI Progressbars besitzen, da Server normalerweise unsichtbar im Hintergund laufen sollten.
var
LastSessionID: Integer = 1; procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread); var SessionID: Integer; procedure DoLog(const Msg: String); var S: String; I: Integer; begin S := Format('%s-%0.6d: %s', [FormatDateTime('YYMMDD HHNNSS ZZZ', Now), SessionID, Msg]); for I := 1 to Length(S) do if S[I] in [#10,#13] then S[I] := ' '; // da Memo1.Lines.Add() intern über SendMessage() arbeitet sollte dies Threadsafe sein. Memo1.Lines.Add(S); 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: ShortString); begin WriteByte(Length(Value)); AThread.Connection.WriteBuffer(Value[1], Length(Value)); end; var FileName: String; FileSize: Cardinal; FileStream: TStream; begin SessionID := LastSessionID; Inc(LastSessionID); try DoLog(Format('Client connect at IP ', [AThread.Connection.Socket.Binding.PeerIP])); if (ReadByte = 1) and (ReadString = 'MyUpload 1.0') then // LOGIN begin // Client hat sich korrekt identifiziert sende Bestätigung und warte auf Kommandos WriteByte(1); while True do case ReadByte of 0: begin // NULL WriteByte(0); end; 2: begin // LOGOUT DoLog('Client logout'); Break; end; 3: begin // ERROR DoLog(Format('Client error %d, %s', [ReadByte, ReadString])); Break; // Fehler fürht IMMER zum Abbau der Verbindung end; 4: begin // UPLOAD FileName := ExtractFilePath(ParamStr(0)) + 'Upload\' + ReadString; FileSize := ReadLong; DoLog(Format('Client upload %5d, %s', [FileSize, FileName])); try FileStream := TFileStream.Create(FileName, fmCreate); try AThread.Connection.ReadStream(FileStream, FileSize); finally FileStream.Free; end; WriteByte(4); except on E: Exception do begin WriteByte(3); // ERROR WriteByte(1); WriteString(E.Message); Break; end; end; end; else begin // Invalid Code; DoLog('Client sends unknown command, terminate'); WriteByte(3); WriteByte(0); // Errorcode WriteString('unknown kommand'); Sleep(1); Break; end; end; end; // falsches/fehlendens Client-Login, trenne einfach die Verbindung, Server ist im Stealth mode finally DoLog('Client terminated'); try Sleep(1); AThread.Connection.Disconnect; except end; end; end; Nun ein zweites neues Projekt für den Clienten erzeugen. Auf das Formular ein TidTCPClient mit Namen 'TCP' daruf und Eigenschaften:
Delphi-Quellcode:
Zusätzlich noch einen TButton, TLabel, TProgressbar und TOpenDialog drauf.
.Host := 'localhost';
.Port := 7000; .ReadTimeout := 5000; Im OnClick von Button1 steht dann folgendes:
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
procedure WriteByte(Value: Byte); begin TCP.WriteBuffer(Value, SizeOf(Value)); end; procedure WriteLong(Value: Cardinal); function SwapLong(Value: Cardinal): Cardinal; asm BSWAP EAX end; begin Value := SwapLong(Value); 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)); end; function ReadString: String; begin SetLength(Result, ReadByte); TCP.ReadBuffer(Result[1], Length(Result)); end; var I: Integer; SendSize,FileSize: Integer; FileName: String; FileStream: TStream; begin if OpenDialog1.InitialDir = '' then OpenDialog1.InitialDir := ExtractFilePath(ParamStr(0)); if OpenDialog1.Execute then try try TCP.Connect; WriteByte(1); WriteString('MyUpload 1.0'); case ReadByte of 1: begin for I := 0 to OpenDialog1.Files.Count -1 do try FileName := OpenDialog1.Files[I]; FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone); try FileSize := FileStream.Size; WriteByte(4); WriteString(ExtractFileName(FileName)); WriteLong(FileSize); Label1.Caption := Format('sende %s', [Filename]); Label1.Update; ProgressBar1.Min := 0; ProgressBar1.Max := FileSize; ProgressBar1.Position := 0; while FileSize > 0 do begin SendSize := FileSize; if SendSize > 1024 then SendSize := 1024; Dec(FileSize, SendSize); TCP.WriteStream(FileStream, False, False, SendSize); ProgressBar1.Position := ProgressBar1.Position + SendSize; end; finally FileStream.Free; end; case ReadByte of 3: begin // fehler ShowMessage(Format('Fehler %5d, %s', [ReadByte, ReadString])); Break; end; 4: ; // alles Ok end; except on E: Exception do ShowMessage(E.Message); end; WriteByte(2); end; 3: begin ShowMessage(Format('Error %d, %s', [ReadByte, ReadString])); end; else ShowMessage('Invalid Responsecode'); end; finally TCP.Disconnect; end; except on E: Exception do ShowMessage(E.Message); end; end; |
Re: Indy-TCP-Server aufsetzen und Dateien versenden
Moin,
schönes Tutorial. Noch schöner wäre allerdings gewesen, wenn die Statusinformationen nicht einfach als Zahl + Kommentar im Code auftauchen würden. Genau dafür sind Konstanten da, die den Quellcode sprechender machen. Also zum Beispiel...
Delphi-Quellcode:
...
const
stNULL = 0; stLOGIN = 1; stERROR = 3; stLOGOUT = 4; stUPLOAD = 5;
Delphi-Quellcode:
Wenn ich jetzt irgendwelche Zahlen nicht richtig übersetzt habe, dann liegt das genau daran, dass die vorhergende Darstellung unübersichtlich war und den Unterschied zwischen wirklicher Zahl und Status nicht mehr ersichtlich waren. Das sollte man gleich beim Erstellen bedenken und übersichtlich gestallten, im Nachhinein ist das schwierig.
case ReadByte of
stNULL: WriteByte(stNULL); stLOGOUT: begin DoLog('Client logout'); Break; end; stERROR: begin DoLog(Format('Client error %d, %s', [ReadByte, ReadString])); Break; // Fehler führt IMMER zum Abbau der Verbindung end; stUPLOAD: begin FileName := ExtractFilePath(ParamStr(0)) + 'Upload\' + ReadString; FileSize := ReadLong; DoLog(Format('Client upload %5d, %s', [FileSize, FileName])); try FileStream := TFileStream.Create(FileName, fmCreate); try AThread.Connection.ReadStream(FileStream, FileSize); finally FileStream.Free; end; WriteByte(stLOGOUT); except on E: Exception do begin WriteByte(stERROR); WriteByte(1); // Errorcode oder stLogin? (war nicht dokumentiert!) WriteString(E.Message); Break; end; end; end; else begin // Invalid Code; DoLog('Client sends unknown command, terminate'); WriteByte(stERROR); WriteByte(0); // Errorcode WriteString('unknown kommand'); Sleep(1); Break; end; end; Hoffe aber, ich habe es richtig umgesetzt. MfG Thorsten |
Re: Indy-TCP-Server aufsetzen und Dateien versenden
Hi Thorsten,
man muß wissen das dieser Source quasi live im Forum entstanden ist. Ich habe ihn also direkt im Forum entwickelt um auf einfache und nachvollziehbare Weise eine Frage zu beantworten. D.h. dieser Source war garnicht für die Codelib bestimmt und ist demzufolge so kurz wie möglich gehalten. Ich gebe dir aber Recht damit das man ihn wesentlich "schöner" machen sollte. Gruß Hagen |
Re: Indy-TCP-Server aufsetzen und Dateien versenden
Hi,
hat den schon jemand diesen Tutorial-Code in Indy10 umgesetzt ? - als Indy Anfänger bekomme ich die Portierung leider nicht hin.. da sich scheinbar selbst Befehlsnamen geändert haben cu Ralf |
Re: Indy-TCP-Server aufsetzen und Dateien versenden
Das Tutorial wurde sehr schön umgesetzt,doch leider habe ich ein paar Fehler,obwohl ich es so übernommen habe,wie es da steht:
Zuerst beim Client Code: procedure TForm1.Button1Click(Sender: TObject); procedure WriteByte(Value: Byte); begin TCP.WriteBuffer(Value, SizeOf(Value)); end; Undeklarierter Bezeichner TCP! Was soll statt TCP dahinkommen? Wenn ich den Namen der Indy Komponente (IdTCPClient1) nehme,dann sagt er undeklarierter Bezeichner Writebuffer! BTW: Ich habe Delphi 2009! MFG Gooner16 |
Re: Indy-TCP-Server aufsetzen und Dateien versenden
Zitat:
|
Re: Indy-TCP-Server aufsetzen und Dateien versenden
Wie drastisch sind diese Unterschiede zwischen Indy 9 und INDY 10?
Gibt es auch dafür einen Tutorial? |
AW: Indy-TCP-Server aufsetzen und Dateien versenden
Hallo zusammen,
sorry, wenn ich diesen alten Beitrag auskrame. Aber ich versuche gerade dieses Tutorial mit Indy 10 unter Delphi 2010 umzusetzen. Nur leider scheitert es an den Befehlen AThread.Connection.ReadBuffer und AThread.Connection.WriteBuffer. Da gibt es einen Unterschied von Indy 9 zu Indy 10. Kann mir jemand sagen, wie der neue Befhel heißt oder wo man es nachlesen kann. |
AW: Indy-TCP-Server aufsetzen und Dateien versenden
Zitat:
|
AW: Indy-TCP-Server aufsetzen und Dateien versenden
Hallo,
mit der Antwort komme ich leider nicht weiter. Ich finde nicht das passende ReadStream oder WriteStream von Indy 9 für Indy 10. Ich stochere da irgendwie im Nebel rum. Kann mir jemand es bitte erklären, was ich zu tun habe ? |
Alle Zeitangaben in WEZ +1. Es ist jetzt 20:26 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz