AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Indy Server und Client

Ein Thema von Briand · begonnen am 19. Apr 2014 · letzter Beitrag vom 28. Apr 2014
Antwort Antwort
Briand
(Gast)

n/a Beiträge
 
#1

Indy Server und Client

  Alt 19. Apr 2014, 00:53
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:

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 = 'FILEthen
    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 = 'QUITthen
    begin
      AContext.Connection.IOHandler.WriteLn('Hallo');

    end
    else
    begin
      AContext.Connection.Disconnect;
    end;
  end;
end;
end.
Das ganze funktioniert sogar mit Progressbar der Client schickt dem Server ohne Probleme die Datei.
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
  Mit Zitat antworten Zitat
Benutzerbild von stahli
stahli

Registriert seit: 26. Nov 2003
Ort: Halle/Saale
4.336 Beiträge
 
Delphi 11 Alexandria
 
#2

AW: Indy Server und Client

  Alt 19. Apr 2014, 01:39
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: http://www.delphipraxis.net/171226-i...o-clients.html


Deinen IndyClient solltest Du m.E. im Thread erzeugen, damit die Trennung vom Mainthread gegeben ist.
Ich habe das so gelöst:

Delphi-Quellcode:
{ 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;
So funktioniert das bisher ganz gut aber es kann schon sein, dass das noch nicht ganz "state of the art" ist.
Stahli
http://www.StahliSoft.de
---
"Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004)
  Mit Zitat antworten Zitat
mjustin

Registriert seit: 14. Apr 2008
3.005 Beiträge
 
Delphi 2009 Professional
 
#3

AW: Indy Server und Client

  Alt 19. Apr 2014, 08:24
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.
Der Server verwendet das OnExecute um in einer Schleife mit dem Client zu kommunizieren. Solange der Client verbunden ist und in OnExecute keine unbehandelte Exception auftritt, wird OnExecute immer wieder ausgeführt.
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:
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.
Das Grundprinzip kann man natürlich auch mit Streams verwenden (IOHandler hat Methoden um Streams und auch Files zu senden).

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.
Michael Justin
habarisoft.com

Geändert von mjustin (19. Apr 2014 um 13:14 Uhr) Grund: ohne IsConsole := True;
  Mit Zitat antworten Zitat
Benutzerbild von stahli
stahli

Registriert seit: 26. Nov 2003
Ort: Halle/Saale
4.336 Beiträge
 
Delphi 11 Alexandria
 
#4

AW: Indy Server und Client

  Alt 19. Apr 2014, 10:05
@mjustin:

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.
Stahli
http://www.StahliSoft.de
---
"Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004)
  Mit Zitat antworten Zitat
mjustin

Registriert seit: 14. Apr 2008
3.005 Beiträge
 
Delphi 2009 Professional
 
#5

AW: Indy Server und Client

  Alt 19. Apr 2014, 12:41
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 Server benötigt nicht generell "besondere" Zeit zum Aufräumen: er benötigt Zeit, falls umfangreiche Objekte /und Resourcen während OnExecute erzeugt und freigegeben werden. Falls viele Anfragen gleichzeitig eintreffen, werden diese auf vorhandene Prozessoren verteilt und konkurrieren um die Rechenleistung, so dass der Server bis 100% ausgelastet ist.
Das Sleep(1000); 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.
Michael Justin
habarisoft.com

Geändert von mjustin (19. Apr 2014 um 12:46 Uhr)
  Mit Zitat antworten Zitat
Briand
(Gast)

n/a Beiträge
 
#6

AW: Indy Server und Client

  Alt 28. Apr 2014, 22:41
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
  Mit Zitat antworten Zitat
Benutzerbild von stahli
stahli

Registriert seit: 26. Nov 2003
Ort: Halle/Saale
4.336 Beiträge
 
Delphi 11 Alexandria
 
#7

AW: Indy Server und Client

  Alt 28. Apr 2014, 23:00
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: http://www.delphipraxis.net/180134-i...ptimieren.html
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.
Stahli
http://www.StahliSoft.de
---
"Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004)

Geändert von stahli (28. Apr 2014 um 23:28 Uhr)
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 14:20 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