na38
na38

Reputation: 119

Delphi 10.4: Indy TIdTCPClient Reading Data from websocket and webserver

I am trying to read real time data being sent every 4 ms from a server. I am using a TCPclinet in Delphi 10.4 and Indy 10 for a cross platform application. All the communication between the client and server is text based and html. The server is running a webserver(port 80) and a websocket(port 81). The server shows a page that waits for a 1 to be sent and then starts sending data through the socket. This works in the TwebBrowser1 and I see the data. I also tested my websocket using a chrome extension and it seems to be sending the data as expected (https://chrome.google.com/webstore/detail/websocket-test-client/fgponpodhbmadfljofbimhhlengambbn?hl=en). However, I do not get any data in my socket on the client using the code below based on links here Delphi: Indy TIdTCPClient Reading Data and edits by Remy as well as his updates here https://en.delphipraxis.net/topic/1010-using-indy-for-cross-platform-tcpip/. Also to get the thread structure I used Remy's answer here Indy TIdTCPClient receive text. Cleaned the code based on more reading to as to not call the main UI threat for debug reading response.

Any help in where I am going wrong would be much appreciated.

unit Mainunit;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Memo.Types,
  FMX.StdCtrls, FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo
  ,IdCustomTransparentProxy, IdSocks, IdBaseComponent,
  IdComponent, IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack,
  IdTCPConnection, IdTCPClient, IdSync, FMX.WebBrowser
  ,Web.HTTPApp, IdHTTP;

type

  TDataEvent = procedure(const Data: string) of object;

  TReadingThread = class(TThread)
    private
      FClient : TIdTCPClient;
      Fdata: string;
      FOnData: TDataEvent;
      procedure DataReceived;
    protected
      procedure Execute; override;
      procedure DoTerminate; override;
    public
      constructor Create(AClient: TIdTCPClient); reintroduce;
      property OnData: TDataEvent read FOnData write FOnData;
    end;

  TLog = class(TIdSync)
  protected
    FMsg: String;
    procedure DoSynchronize; override;
  public
    constructor Create(const AMsg: String);
    class procedure AddMsg(const AMsg: String);
  end;

  TMainForm = class(TForm)
    Memo1: TMemo;
    Connect_btn: TButton;
    IdTCPClient1: TIdTCPClient;
    Disconnect_btn: TButton;
    WebBrowser1: TWebBrowser;
    refresh_btn: TButton;

    procedure DataReceived(const Data: string);
    procedure CreateTCPIPConnection;
    procedure Connect_btnClick(Sender: TObject);
    procedure Disconnect_btnClick(Sender: TObject);
    procedure refresh_btnClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);

  private
    { Private declarations }
  public
    { Public declarations }
    request_count : integer;

  end;

var
  MainForm: TMainForm;
  ZPReadThread: TReadingThread = nil;

implementation

{$R *.fmx}


procedure TMainForm.CreateTCPIPConnection;
begin
  if not Assigned(ZPReadThread) then
  begin
    IdTCPClient1.Host := '192.168.11.5';
    IdTCPClient1.Port := 81;
    try
      ZPReadThread := TReadingThread.Create(IdTCPClient1);
      try
        ZPReadThread.OnData := DataReceived;
        ZPReadThread.Start;
      except
        FreeAndNil(ZPReadThread);
        raise;
      end;
    except
      on E: Exception do
      begin
        TLog.AddMsg('DEBUG: TCP/IP exception creating read thread : '+E.Message);
       end;
    end;
  end;
end;



constructor TReadingThread.Create(AClient: TIdTCPClient);
begin
  TLog.AddMsg('DEBUG: TReadingThread.Create');
  inherited Create(True);
  FClient := AClient;
end;


procedure TReadingThread.Execute;
begin
  try
    FClient.ConnectTimeout := 10000; // <-- use whatever you want...   was 10000
    FClient.Connect;
    if FClient.Connected then
      TLog.AddMsg('DEBUG: Threat execute connected to the client: ' + FClient.Host
                  + '   on port: '        + inttostr(FClient.Port)
                  + '   RecvBufferSize: ' + inttostr(FClient.IOHandler.RecvBufferSize)
                  );
  except
    on E: Exception do
    begin
      TLog.AddMsg('DEBUG: TCP/IP connect exception : '+E.Message);
      raise;
    end;
  end;

  try
    FClient.IOHandler.ReadTimeout := 1000; // <-- use whatever you want...  was 5000
    while not Terminated do
    begin
      try
        FData := FClient.IOHandler.ReadLn();
      except
        on E: Exception do
        begin
            TLog.AddMsg('DEBUG: TCP/IP IOHandler.ReadLn exception : '+E.Message);
          raise;
        end;
      end;
       //  if (FData <> '') and Assigned(FOnData) then Synchronize(DataReceived);
        Synchronize(DataReceived);
        SetLength (FData,0);
    end;
  finally
    FClient.Disconnect;
  end;
end;


procedure TReadingThread.DataReceived;
begin
  if Assigned(FOnData) then FOnData(FData);
   Mainform.request_count :=  Mainform.request_count + 1;
   TLog.AddMsg('Debug: ' +  inttostr(MainForm.request_count)+ ' ) Inside proc DataRecieved of thread fdata is: ' + FData);
end;


procedure TReadingThread.DoTerminate;
begin
  TLog.AddMsg('Debug: Read thread terminating');
  inherited;
end;

constructor TLog.Create(const AMsg: String);
begin
  inherited Create;
  FMsg := AMsg;
end;

procedure TLog.DoSynchronize;
begin
  Mainform.Memo1.Lines.Add(FMsg);
end;

class procedure TLog.AddMsg(const AMsg: String);
begin
  with Create(AMsg) do
  try
    Synchronize;
  finally
    Free;
  end;
end;



procedure TMainForm.DataReceived(const Data: string);
begin
  Memo1.Lines.Add('Recevied data DataRecieved of form : ' + Data);
end;

procedure TMainForm.Connect_btnClick(Sender: TObject);

begin
  try
    WebBrowser1.Navigate('http://192.168.11.5/');
    CreateTCPIPConnection();
    request_count := 0;

  except
    on E: Exception do
      MainForm.Memo1.Lines.Add('Threat connection Error: ' + E.Message);
    end;
  end;


procedure TMainForm.Disconnect_btnClick(Sender: TObject);
begin
  if Assigned(ZPReadThread) then
  begin
    TLog.AddMsg('Debug: Terminating Read thread');
    ZPReadThread.Terminate;
    TLog.AddMsg('Debug:Waiting for read thread termination');
    ZPReadThread.WaitFor;
    TLog.AddMsg('Debug:Finished waiting for read thread termination');
    FreeAndNil(ZPReadThread);
  end
  else TLog.AddMsg('Debug:Thread not active');
end;



procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
   Disconnect_btnClick(Sender);
end;

procedure TMainForm.refresh_btnClick(Sender: TObject);
begin
    WebBrowser1.Reload;
    memo1.Lines.clear;
    request_count := 0;
end;


end.

Upvotes: 2

Views: 3292

Answers (2)

Lutz
Lutz

Reputation: 21

The Unit works great (not yet testet on android), but not give the params to the webservice - so mine didn't authenticate me.

Solution : add paramstr to


    procedure TIdSimpleWebSocketClient.Connect(pURL: String);
    var  URI      : TIdURI;
         lSecure  : Boolean;
         paramstr:string;

and later on :
self.Port := StrToInt(URI.Port);

if uri.params <> ''
then paramstr := '?' + uri.params
else paramstr := '';

if lSecure and (self.IOHandler=nil) then

then add paramstr in this line :

    self.Socket.WriteLn(format('GET %s HTTP/1.1', [uri.path+uri.Document+paramstr]));

Example :

  wss1.Connect('wss://ws.eodhistoricaldata.com/ws/us-quote?api_token=demo);  
  if wss1.Connected
  then wss1.writeText('{"action": "subscribe", "symbols": "AMZN, TSLA"}');


without sending the params ?api_token=demo this webservice sends an error.

Upvotes: 1

na38
na38

Reputation: 119

Answering my own question above in case it helps others. Someone correct me if I am wrong but there is no way to use a base TidTCPClient to read data from a websocket. However, there is WebSocket client inherited from TIdTCPClient (https://github.com/arvanus/Indy/blob/WebSocketImpl/Lib/Core/IdWebSocketSimpleClient.pas) that works great. I was able to connect to the my socket and all I needed to add was a procedure to read the data.

Upvotes: 2

Related Questions