Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Netzwerke (https://www.delphipraxis.net/14-netzwerke/)
-   -   Delphi Beispielcode für TCP Socket Client Bibliotheken (Delphi & Free Pascal) (https://www.delphipraxis.net/212060-beispielcode-fuer-tcp-socket-client-bibliotheken-delphi-free-pascal.html)

mjustin 11. Dez 2022 13:22


Beispielcode für TCP Socket Client Bibliotheken (Delphi & Free Pascal)
 
Für meine Delphi- und Free Pascal Projekte verwende ich diverse TCP Bibliotheken.

Diese unterscheiden sich in ihrer API, manche sind eher high-level orientiert, andere nur ein kleiner Wrapper für die Windows API.
Das macht einen Wechsel, oder bibliotheksunabhängige Programmierung (bei der mehrere Bibliotheken unterstützt werden) nicht leicht.
Dadurch motiviert, sammele ich auf GitHub nun Codebeispiele für typische, einfache, Aufgabenstellungen.

Die Repository-Adresse ist https://github.com/michaelJustin/tcpclient-test

Teil 1: Lesen einer festen Anzahl von Bytes
Im ersten Teil des Projekts geht es um das Lesen einer festen Anzahl von Bytes vom Server. Dazu gibt es einen TCP Server, der dem Client eine feste Anzahl bytes zurückgibt und dann die Verbindung trennt.

Client-Testanwendungen für diese Bibliotheken sind enthalten:
* Indy 10 - https://github.com/IndySockets/Indy
* Ararat Synapse - https://sourceforge.net/p/synalist/c...AD/tree/trunk/
* Synopse - https://synopse.info

Alle Client-Beispiele enthalten nur eine Methode:

Delphi-Quellcode:
function Read(AHost: string; APort: Integer; ALength: Integer): TBytes;
Diese wird von der Test-Methode aufgerufen, und die zurückggebene Länge ermittelt und ausgegeben.

Für Indy sieht das Beispiel folgendermassen aus:
Delphi-Quellcode:
program FixedLengthClient;

uses
  ClientMainIndy10,
  SysUtils;

const
  CONTENT_LENGTH = 8192;
  SERVER_HOST = '127.0.0.1';
  SERVER_PORT = 30000;

  procedure Test(AExpectedLength: Integer);
  var
    Response: TBytes;
  begin
    WriteLn(Format('try to read %d bytes from %s:%d',
      [AExpectedLength, SERVER_HOST, SERVER_PORT]));
    Response := Read(SERVER_HOST, SERVER_PORT, AExpectedLength);
    WriteLn(Format('received %d bytes', [Length(Response)]));
  end;

begin
  try
    Test(CONTENT_LENGTH);
    Test(CONTENT_LENGTH - 1);
    Test(CONTENT_LENGTH + 1); // (surprise me)
  except
    on E: Exception do
    begin
      WriteLn(E.Message);
    end;
  end;
  ReadLn;
end.
Für jede Bibliothek muss nur die entsprechende Unit verwendet werden, und die anderen auskommentiert:
Delphi-Quellcode:
uses
  //ClientMainIndy10,
  //ClientSynapse266,
  ClientSynopseCrtSock,
  SysUtils;
Die Projekte sind zwar mit Free Pascal / Lazarus erstellt, sollten aber mit Delphi 2009 und neuer kompatibel sein. (Delphi dpr Dateien können durch Umbenennen der lpr erzeugt werden)

jaenicke 11. Dez 2022 13:54

AW: Beispielcode für TCP Client Bibliotheken (Delphi & Free Pascal)
 
Tatsächlich habe ich beruflich gerade ein ähnliches Thema bearbeitet (HTTP und Rest statt TCP). Ich habe eine Wrapperklasse erstellt, der man einfach sagen kann, welches Framework sie zur Kommunikation nutzen soll und ob die Kommunikation asynchron erfolgen soll. Diese asynchrone Kommunikation finde ich, gerade bei solchen Beispielen, sehr wichtig, denn diese ist noch schwieriger unter einen Hut zu bekommen.

Statt mehrere einzelne Beispiele zu schreiben, wäre vielleicht eine Überlegung, auch hier einen Wrapper zu schreiben, den man dann frameworkunabhängig nutzen kann.

Ich würde in die Liste der Framworks noch ICS aufnehmen. Das gefällt mir sehr gut, unter anderem weil z.B. bei (ja, anderes Thema) SSL-Verbindungen sehr ausführliche und hilfreiche Logs geschrieben werden können und auch die (im Gegensatz zu Indy) non-blocking sockets finde ich sehr nützlich.

mytbo 11. Dez 2022 19:02

AW: Beispielcode für TCP Socket Client Bibliotheken (Delphi & Free Pascal)
 
Zitat:

Zitat von mjustin (Beitrag 1516043)
Das macht einen Wechsel, oder bibliotheksunabhängige Programmierung (bei der mehrere Bibliotheken unterstützt werden) nicht leicht.

Nur aus Neugierde, warum möchtest du mehrere Bibliotheken unterstützen? Synopse mORMot, ich würde dir mORMot2 empfehlen, unterstützt Delphi ab Version 7 und FPC. Die Klasse TCrtSocket ist beschrieben mit: "Fast low-level Socket implementation, direct access to the OS (Windows, Linux) network layer API, ...our classes are (much) faster than the Indy or Synapse implementation". Für die asynchrone Kommunikation gibt es die Klassen TAsyncServer/TAsyncClient.

Bis bald...
Thomas

Codehunter 13. Dez 2022 09:04

AW: Beispielcode für TCP Socket Client Bibliotheken (Delphi & Free Pascal)
 
Das ist irgendwie eine ähnliche Diskussion wie "Was ist besser: Windows oder [xyz]?" Letztlich muss man sich selbst fragen: Wie wichtig ist einem das Thema Netzwerkkommunikation im eigenen Projekt? Wie viel Arbeit will ich investieren? Soll das Projekt plattformunabhängig sein? Vertraue ich einem bestimmten Hersteller und/oder proprietären OS?

Ich persönlich nutze Indy, weil es plattformunabhängig ist und ziemlich Low-Level ansetzt um den gesamten Protokollstack selbst abzubilden. Leider ist die Entwicklung in den letzten Jahren ins Stocken geraten, vor allem weil Remy kaum noch Zeit dafür hat. So ist z.B. OpenSSL 1.1 immer noch nicht final im Hauptzweig integriert. Dabei war OpenSSL genau der Grund, weshalb ich mich damals nicht auf die Emba-Lösung eingelassen habe, denn die setzt unter Windows auf WinInet und da ist man bei SSL/TLS-Verbindungen auf Gedeih und Verderb deren Zertifikatspeicher ausgeliefert - und den hat Microsoft schon mehrfach per Windowsupdate sabotiert. Bisher bin ich mit Indy sehr gut gefahren, noch gibt es keine TLS-1.3-only-Server in meinem Ökosystem, aber irgendwann wirds passieren und dann wären wir mit Indy und OpenSSL 1.0.x angeschmiert.

jaenicke 13. Dez 2022 09:36

AW: Beispielcode für TCP Socket Client Bibliotheken (Delphi & Free Pascal)
 
Zitat:

Zitat von Codehunter (Beitrag 1516101)
denn die setzt unter Windows auf WinInet und da ist man bei SSL/TLS-Verbindungen auf Gedeih und Verderb deren Zertifikatspeicher ausgeliefert - und den hat Microsoft schon mehrfach per Windowsupdate sabotiert.

Das stimmt nicht. Ich verwende die delphieigenen Klassen für Rest und Http und nutze Zertifikate aus Dateien. In Delphi 11.2 funktioniert das nun auch direkt ohne Tricks.

Da neuere Verschlüsselungen oder Kurven nicht unbedingt von allen Frameworks unterstützt werden, ist es gut, wenn man das Framework bei Bedarf relativ einfach wechseln kann.

Codehunter 13. Dez 2022 14:44

AW: Beispielcode für TCP Socket Client Bibliotheken (Delphi & Free Pascal)
 
Zitat:

Zitat von jaenicke (Beitrag 1516103)
Zitat:

Zitat von Codehunter (Beitrag 1516101)
denn die setzt unter Windows auf WinInet und da ist man bei SSL/TLS-Verbindungen auf Gedeih und Verderb deren Zertifikatspeicher ausgeliefert - und den hat Microsoft schon mehrfach per Windowsupdate sabotiert.

Das stimmt nicht. Ich verwende die delphieigenen Klassen für Rest und Http und nutze Zertifikate aus Dateien. In Delphi 11.2 funktioniert das nun auch direkt ohne Tricks.

Da neuere Verschlüsselungen oder Kurven nicht unbedingt von allen Frameworks unterstützt werden, ist es gut, wenn man das Framework bei Bedarf relativ einfach wechseln kann.

Das meinte ich nicht. SSL und TLS sind so aufgebaut, dass es (mehr oder weniger) vertrauenswürdige Zertifizierungsstellen gibt,z.B. Letsencrypt oder Symantec. Ein Server im Web liefert seine öffentlichen Schlüssel und der Client prüft dann entlang der Keychain zur Zertifizierungsstelle. Das ist aus gutem Grund so gewollt, wie man an den verschiedenen Skandalen rund um die Zertifizierungsstellen in der Vergangenheit gesehen hat. Dieser Tage wollten Mozilla und Google z.B. die Trustcor-Rootzertifikate sperren.

Die Browser bringen diese Prüfinfrastruktur selbst mit, Microsoft liefert aber mit WinInet auch eine API zu einer solchen Infrastruktur. Die meinte ich, die hat Windows Update schon mehrfach kaputt gepatched. Aus Anwendungssicht ist der Effekt dann der selbe: Du bekommst keine verschlüsselte Verbindung aufgebaut. Also neben der gewollten Sollbruchstelle noch eine ungewollte.

mjustin 13. Dez 2022 18:16

AW: Beispielcode für TCP Socket Client Bibliotheken (Delphi & Free Pascal)
 
Danke für das Feedback und die Anregungen! Ich freue mich natürlich auch über Pull Requests auf das GitHub Repository mit Vorschlägen, alternativ gerne per PM etc.

Nun ist der zweite Teil erschienen:

Teil 2: Lesen eines Snowman ☃ - terminierten Strings von einem TCP Socket

Dazu gibt es auch wieder einen Artikel.

Der Server sendet in diesem Beispiel einen kurzen Text an den Client, gefolgt von der Byte-Sequenz $2603, dies ist der Unicode-Wert für das Snowman Symbol.
Die clientseitige Implementierung für Indy und Synapse ist relativ kompakt und leicht lesbar. Hier die für Synapse:
Delphi-Quellcode:
function ReadDelimited(AHost: string; APort: Integer; ATerminator: RawByteString): string;
var
  FSock: TTCPBlockSocket;
begin
  FSock := TTCPBlockSocket.Create;
  try
    FSock.RaiseExcept := True;
    FSock.Connect(AHost, IntToStr(APort));
    Result := FSock.RecvTerminated(1000, ATerminator);
   finally
     FSock.Free;
   end;
end;
Der Terminator wird dazu im Beispiel als Konstante definiert:
Delphi-Quellcode:
  FIXED_DELIMITER = '' + #$2603; // '☃';
Das Testprogramm sieht aktuell so aus:

Delphi-Quellcode:
program FixedDelimiterClient;

uses
  ClientIndySockets10,
  //ClientSynapse266,
  //ClientSynopseCrtSock,
  SysUtils;

const
  FIXED_DELIMITER = '' + #$2603; // '☃';
  SERVER_HOST = '127.0.0.1';
  SERVER_PORT = 30000;

  procedure Test(ADelimiter: string);
  var
    Response: string;
  begin
    WriteLn(Format('try to read from %s:%d delimited with %s',
      [SERVER_HOST, SERVER_PORT, ADelimiter]));
    Response := ReadDelimited(SERVER_HOST, SERVER_PORT, Utf8Encode(ADelimiter));
    WriteLn(Format('received response "%s" - %d bytes',
      [Response, Length(Response)]));
  end;

begin
  try
    Test(FIXED_DELIMITER);
    Test(FIXED_DELIMITER);
    Test(FIXED_DELIMITER);
  except
    on E: Exception do
    begin
      WriteLn(E.Message);
    end;
  end;
  ReadLn;
end.
Da es eine Konsolenanwendung ist, sieht man den Snowman leider nicht.
Es erscheint nur "try to read from 127.0.0.1:30000 delimited with ?'


Alle Zeitangaben in WEZ +1. Es ist jetzt 22:05 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