![]() |
Indy Server und Client
Hallo Delphianer
Ich hab heute bisschen mit den Indy’s rum gespielt und zwar mit TidTCPServer und TidTCPClient. Ich dachte mir ich versuche mal damit einen Stream zu versenden. Dies hat dann auch funktioniert und zwar sende ich den Stream vom Client zum Server. Nun wollte ich auch vom Server einen Stream zum Client schicken nur der Client hat ja kein Execute Ereignis. Habe dann bisschen gegooglet und gelesen man muss das mit einem Thread lösen. Hab dann ein bisschen rumgebastelt und kann nun mit dem Thread so weit mal Strings empfangen. Was allerdings nur geht wenn der Client dem Server einen String sendet und der Server das im Execute Ereignis dann zurücksendet. Was mich auch verwirrt ist z.b im Server kann ich da nur was im Execute Ereignis senden und nicht wenn ich einen Button Klicke? Da ich das AContext ja nur im Ereignis Execute habe. Beispiel: AContext.Connection.IOHandler.WriteStream(AStream, FileSize, False); Client
Delphi-Quellcode:
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ImgList, System.Win.ScktComp, Vcl.Buttons, JvExButtons, JvButtons, Vcl.ComCtrls, JvBackgrounds, Vcl.Imaging.pngimage, Vcl.Imaging.jpeg, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdExplicitTLSClientServerBase, IdFTP; type TForm1 = class(TForm) BitBtn1: TBitBtn; Edit1: TEdit; client: TIdTCPClient; BitBtn2: TBitBtn; BitBtn3: TBitBtn; BitBtn4: TBitBtn; BitBtn5: TBitBtn; BitBtn6: TBitBtn; procedure BitBtn1Click(Sender: TObject); procedure BitBtn2Click(Sender: TObject); procedure BitBtn3Click(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormDestroy(Sender: TObject); procedure BitBtn4Click(Sender: TObject); procedure BitBtn5Click(Sender: TObject); procedure BitBtn6Click(Sender: TObject); private { Private-Deklarationen } public { Public-Deklarationen } end; type ULThread = class(TThread) procedure Execute; override; end; type DLThread = class(TThread) procedure Execute; override; end; type TCPREAD = class(TThread) procedure Execute; override; end; var Form1: TForm1; UL : ULThread; ULFilePath: String; ULFileName: String; DL : DLThread; DLFilePath: String; DLFileName: String; SR : TCPREAD; implementation Uses FileStreamUnit; {$R *.dfm} procedure TCPREAD.execute; begin while not Terminated do begin; form1.Caption := form1.Client.IOHandler.ReadLn; if terminated then begin break; end; end; end; procedure ULThread.execute; var AStream: TFileStream; begin AStream := TFileStream.Create(form1.edit1.text, fmOpenRead + fmShareDenyNone); // form1.edit1.Text := inttostr(astream.Size); try form1.Client.IOHandler.LargeStream := True; form1.Client.IOHandler.SendBufferSize := 32768; form1.Client.IOHandler.WriteLn('FILE'); // send command "FILE" form1.Client.IOHandler.WriteLn(ExtractFilename(form1.edit1.text)); // send file name form1.client.IOHandler.WriteLn(IntToStr(AStream.Size)); // send file size form1.Client.IOHandler.Write(AStream); finally FreeAndNil(AStream); end; end; procedure DLThread.execute; var ftpDownStream: TFileStreamEx; begin end; procedure TForm1.BitBtn1Click(Sender: TObject); begin client.Connect; end; procedure TForm1.BitBtn2Click(Sender: TObject); begin client.disconnect; end; procedure TForm1.BitBtn3Click(Sender: TObject); begin UL := ULThread.Create(True); UL.FreeOnTerminate := false; UL.resume end; procedure TForm1.BitBtn4Click(Sender: TObject); begin form1.Client.IOHandler.WriteLn('QUIT'); // sende irgend was damit ich was zurück erhalte end; procedure TForm1.FormCreate(Sender: TObject); begin SR := TCPREAD.Create(true); SR.FreeOnTerminate := true; SR.start; end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin if Client.Connected then Client.Disconnect; end; procedure TForm1.FormDestroy(Sender: TObject); begin if Client.Connected then Client.Disconnect; end; end. Server
Delphi-Quellcode:
Das ganze funktioniert sogar mit Progressbar der Client schickt dem Server ohne Probleme die Datei.unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Win.ScktComp, Vcl.StdCtrls, MMSystem, Shellapi, IdContext, IdBaseComponent, IdComponent, IdCustomTCPServer, IdTCPServer, IdCmdTCPServer, IdExplicitTLSClientServerBase, IdFTPListOutput,IdFTPList, IdFTPServer, Vcl.ComCtrls, JvExComCtrls, JvProgressBar, Vcl.Buttons; type TForm1 = class(TForm) server: TIdTCPServer; ProgressBar1: TProgressBar; Label1: TLabel; BitBtn1: TBitBtn; Edit1: TEdit; procedure serverConnect(AContext: TIdContext); procedure serverExecute(AContext: TIdContext); procedure serverDisconnect(AContext: TIdContext); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormDestroy(Sender: TObject); procedure FormCreate(Sender: TObject); procedure BitBtn1Click(Sender: TObject); private { Private-Deklarationen } public { Public-Deklarationen } end; var Form1: TForm1; implementation uses FileStreamUnit; {$R *.dfm} procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin if Server.Active then Server.Active := False; end; procedure TForm1.FormDestroy(Sender: TObject); begin if Server.Active then Server.Active := False; end; procedure TForm1.serverConnect(AContext: TIdContext); begin form1.Caption := 'Connected' end; procedure TForm1.serverDisconnect(AContext: TIdContext); begin form1.Caption := 'Disconnect'; end; procedure TForm1.serverExecute(AContext: TIdContext); var s: string; AStream: TFileStreamEx; Filename: string; FileSize: Int64; begin while AContext.Connection.Connected do begin s := AContext.Connection.IOHandler.ReadLn; if s = 'FILE' then begin a := 0; b := 0; Filename := AContext.Connection.IOHandler.ReadLn; // get filename FileSize := StrToInt64(AContext.Connection.IOHandler.ReadLn); a := filesize; // get filesize ForceDirectories(ExtractFilePath(Paramstr(0))); AStream := TFileStreamEx.Create('c:\0\0\test.mp4', fmCreate); try AContext.Connection.IOHandler.RecvBufferSize:= 32768; AContext.Connection.IOHandler.ReadStream(AStream, FileSize, False); finally FreeAndNil(AStream); progressbar1.Position := 100; label1.Caption := '100%'; end; end else if s = 'QUIT' then begin AContext.Connection.IOHandler.WriteLn('Hallo'); end else begin AContext.Connection.Disconnect; end; end; end; end. Wie hab ich vorzugehen wenn ich das umgekehrt machen will? Ich hoffe das mich jemand auf den Richtigen Weg führen kann ;-) Grüsse Brian |
AW: Indy Server und Client
Die Indys funktionieren nur so, dass der Client beim Server anfragt und eine Antwort erhält. (Ich bin da aber auch nicht ganz sattelfest.)
Hier gab es mal etwas zum Thema: ![]() Deinen IndyClient solltest Du m.E. im Thread erzeugen, damit die Trennung vom Mainthread gegeben ist. Ich habe das so gelöst:
Delphi-Quellcode:
So funktioniert das bisher ganz gut aber es kann schon sein, dass das noch nicht ganz "state of the art" ist.
{ TThreadTcp }
constructor TThreadTcp.Create(aGuiManager: TsoGuiManager); begin fGuiManager := aGuiManager; FreeOnTerminate := True; IdTCPClient1 := TIdTCPClient.Create; IdTCPClient1.Host := gIdTCPClient.Host; IdTCPClient1.Port := gIdTCPClient.Port; inherited Create(False); end; procedure TThreadTcp.Execute; var aPackage: TsoGuiDataPackage; S: String; begin while (not Terminated) and Assigned(fGuiManager) do begin ... Kommandos senden und Ergebnisse verarbeiten ... Zugriffe auf Bereiche des Mainthreads mit CriticalSections absichern end; FreeAndNil(IdTCPClient1); end; |
AW: Indy Server und Client
Zitat:
Der Client hat dagegen lediglich Methoden zum Senden und Empfangen einzelner Datenobjekte (Bytes, Strings, Streams ...) an den Server. Ein Thread ist aber nicht erforderlich, damit ein Client passiv Nachrichten empfangen kann. Der Thread ist nur dazu da, den Hauptthread nicht aufzuhalten, während der Client auf Daten wartet. In einer GUI Anwendung ist das natürlich notwendig. In einer Konsolenanwendung dagegen kann der Client dagegen im Prinzip in einer normalen while Schleife auf Nachrichten vom Server warten. Ob mit oder ohne Thread: der Client muss nicht zuerst eine Nachricht an den Server senden, damit dieser Daten an den Client überträgt. Wenn man zum Beispiel aktiv allen Clients regelmäßig die aktuelle Uhrzeit senden will, geht das mit diesem Code:
Delphi-Quellcode:
Das Grundprinzip kann man natürlich auch mit Streams verwenden (IOHandler hat Methoden um Streams und auch Files zu senden).
unit Unit1;
interface uses IdTCPServer, IdTCPClient, IdContext, SysUtils, Classes, Forms, StdCtrls, Controls; type TMyPushClientThread = class(TThread) private TCPClient: TIdTCPClient; FLog: TStrings; public constructor Create(AHost: string; APort: Word; ALog: TStrings); destructor Destroy; override; procedure Execute; override; end; TMyPushServer = class (TIdTCPServer) public procedure InitComponent; override; procedure MyOnExecute(AContext: TIdContext); end; TServerPushExampleForm = class(TForm) MemoLog: TMemo; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private ExampleClient: TMyPushClientThread; ExampleServer: TMyPushServer; end; var ServerPushExampleForm: TServerPushExampleForm; implementation uses IdGlobal; {$R *.dfm} procedure TServerPushExampleForm.FormCreate(Sender: TObject); begin ExampleServer := TMyPushServer.Create; ExampleServer.DefaultPort := 8088; ExampleServer.Active := True; ExampleClient := TMyPushClientThread.Create('localhost', 8088, MemoLog.Lines); end; procedure TServerPushExampleForm.FormDestroy(Sender: TObject); begin ExampleServer.Free; ExampleClient.Terminate; ExampleClient.WaitFor; ExampleClient.Free; end; { TMyPushServer } procedure TMyPushServer.InitComponent; begin inherited; OnExecute := Self.MyOnExecute; end; procedure TMyPushServer.MyOnExecute(AContext: TIdContext); begin Sleep(1000); AContext.Connection.IOHandler.WriteLn('Server-Zeit ist: ' + TimeToStr(Now), IndyTextEncoding_UTF8); end; { TMyPushClientThread } constructor TMyPushClientThread.Create(AHost: string; APort: Word; ALog: TStrings); begin inherited Create(False); FLog := ALog; TCPClient := TIdTCPClient.Create; TCPClient.Host := AHost; TCPClient.Port := APort; TCPClient.ReadTimeout := 500; end; destructor TMyPushClientThread.Destroy; begin TCPClient.Free; inherited; end; procedure TMyPushClientThread.Execute; var S: string; begin TCPClient.Connect; while not Terminated do begin S := TCPClient.IOHandler.ReadLn(IndyTextEncoding_UTF8); if not TCPClient.IOHandler.ReadLnTimedout then begin TThread.Queue(nil, procedure begin FLog.Append(S); end); end; end; TCPClient.Disconnect; end; end. Die Kernfrage bei "Server Push" ist natürlich: welcher Client soll denn die Daten erhalten (wenn es mehr als ein Client ist)? Dazu muss man sich natürlich eine eindeutige Client-Kennung bauen. "Chat"-Programme sind da ein guter Ausgangspunkt, ich habe aktuell leider keinen Link zu einem guten Tutorial parat. |
AW: Indy Server und Client
@mjustin: :kiss:
Ist folgende (vereinfachte) Beschreibung richtig? Der Server arbeitet die Anfragen automatisch in Threads ab. Das heißt, der Mainthread wird durch die Abfragen nicht gebremst. Der Server muss immer mal etwas Zeit zum aufräumen haben. Zu viele ständige Anfragen in kurzer Zeit verkraftet er nicht. Der Client muss dagegen in einem extra Thread ausgeführt werden, wenn er den Mainthread nicht bremsen soll. |
AW: Indy Server und Client
Zitat:
Das
Delphi-Quellcode:
im OnExecute sorgt dafür, dass der Server einmal pro Sekunde Daten an den Client sendet. Wenn der Client innerhalb von 500 Millisekunden keine Antwort erhält, erhält er ein Timeout, und versucht dann sofort wieder Daten zu empfangen. Der Server sendet nur, wenn er gerade "will" - im Beispielcode also erst nach Ablauf einer Sekunde. Client und Server müssen nicht zeitsynchronisiert sein: wenn der Server das Intervall ändert (zum Beispiel immer zwanzig Sekunden zu pausieren), muss der Client Code nicht geändert werden.
Sleep(1000);
|
AW: Indy Server und Client
Hab das ganze mal versucht nach Stahlis Anleitung zu machen und es funktioniert.
Allerdings wie kann ich vom Server nen String an den Client schicken peer Botton klick? Funktioniert das nur im Server.OnExecute Ereigniss? Weil ich möchte was schicken wenn ich nen Botton klicke. L.g Briand |
AW: Indy Server und Client
Ich versuche, das mal korrekt zusammen zu fassen - so dass ich es auch verstehe. ;-)
Über die Indys fragt normalerweise der Client1 oder Client2 beim Server an. Der Server startet einen Thread, berechnet darin die Antwort (man sollte also im OnExecute nicht auf die GUI o.ä. zugreifen ohne vorher Konflikte abzusichern) und schickt die Antwort an den anfragenden Client zurück. Nach einer gewissen Zeit räumt der Server dann zyklisch den verbrauchten Speicher bzw. die Verbindungen wieder auf. Deshalb ist es grundsätzlich besser, selten größere Datenpakete zu übertragen als extrem oft kleine. Wenn der Server von sich aus eine Nachricht an einen Client senden will wird das schwieriger. Zum einen muss der Server alle Clients kennen (Details kann ich nicht beschreiben) und die Server müssen dauernd lauern, ob eine Nachricht anliegt. Alternativ können alle Clients natürlich auch zyklisch beim Server anfragen "Hey, hat jemand bei Dir Button1 gedrückt", worauf ja oder nein zurück kommt. Welches der beiden Varianten der bessere Weg wäre kann ich nicht sagen. Grundsätzlich ist Indy aber wohl eher auf Frage(hin)-Antwort(zurück) ausgerichtet. Man könnte sonst noch beide Seiten mit jeweils einem Server und einem Client ausstatten. Für so etwas wie Chats sollten aber Sockets (peer to peer) effektiver einsetzbar sein. (Ich lasse mich gern korrigieren, wenn das noch nicht ganz passt.) [EDIT] Schau mal noch hier: ![]() Das könnte nützlich sein. [EDIT2] Oups, jetzt habe ich irgendwie den Überblick über die Indy-Beiträge verloren. mjustin hat Deine Frage unter #3 ja fast beantwortet. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 15:45 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