Reputation: 119
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
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
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