10-01-2020, 08:02 PM
Just to close this thread with final thoughts if someone stumbles across it in the future...
It all seems to work now to use several different approaches, so I decided to use this where I have a TIdBytes buffer and read all available data every pass in the Execute procedure.
But I am using an event handler where the transfer container is a string, partial code below.
Note that I have both set the event handler Data container as const as shown below and without const. I see no difference either way.
It all seems to work now to use several different approaches, so I decided to use this where I have a TIdBytes buffer and read all available data every pass in the Execute procedure.
But I am using an event handler where the transfer container is a string, partial code below.
Note that I have both set the event handler Data container as const as shown below and without const. I see no difference either way.
Code:
type
TCommRxEvent = procedure (Sender: TObject; const Data: string) of object;
TTcpClientComm = class;
{ TReadingThread }
TReadingThread = class(TThread)
protected
FRxData: TIdBytes;
FOwner: TTcpClientComm;
procedure Execute; override;
procedure DoOnRxData;
public
constructor Create(Owner: TTcpClientComm);
end;
{ TTcpClientComm }
TTcpClientComm = class(TObject)
private
FConn: TIdTCPClient;
FReadThread: TReadingThread;
FOnRxData: TCommRxEvent;
FOnLogMsgEvent: TLogMessageEvent;
FLastError: string;
FConnected: boolean;
FHost: string;
FPort: word;
procedure OnStatusChange(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string);
procedure LogEventData(Msg: Ansistring);
public
constructor Create;
destructor Destroy; override;
property OnRxData: TCommRxEvent read FOnRxData write FOnRxData;
property OnLogMsgEvent: TLogMessageEvent read FOnLogMsgEvent write FOnLogMsgEvent;
property LastError: string read FLastError;
property Connected: boolean read FConnected;
property Host: string read FHost;
property Port: word read FPort;
procedure Connect(AHost: string; APort: word);
procedure Disconnect;
function WriteData(const Data: string): boolean; overload;
function WriteData(const Data: TIdBytes): boolean; overload;
end;
implementation
const
ETX = #3;
constructor TTcpClientComm.Create;
begin
FConnected := false;
FHost := '';
FPort := 0;
FConn := TIdTCPClient.Create(NIL);
FConn.OnStatus := OnStatusChange;
FConn.ConnectTimeout := 5000; //5 second connect timeout
FConn.ReadTimeout := 100; //Used in the read thread to not block
end;
procedure TTcpClientComm.Connect(AHost: string; APort: word);
begin
try
FLastError := '';
FConn.Connect(AHost, APort); //Create read thread in OnStatus.hsConnected
FHost := Host;
FPort := APort;
except
on E: Exception do
begin
FLastError := E.Message;
end;
end;
end;
procedure TTcpClientComm.OnStatusChange(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string);
{This procedure parses the socket status change messages}
begin
case AStatus of
hsResolving: ;
hsConnecting: ;
hsConnected:
begin
FReadThread := TReadingThread.Create(Self);
FConnected := true;
LogEventData('Connected to server ' + FHost + ':' + IntToStr(FPort));
end;
hsDisconnecting: FConnected := false;
hsDisconnected:
begin
FConnected := false;
if FReadThread <> NIL then
FReadThread.Terminate;
if FReadThread <> NIL then
begin
FReadThread.WaitFor;
FreeAndNil(FReadThread);
end;
LogEventData('Disconnected from server ' + FHost);
end;
hsStatusText:
;
end;
end;
{ TReadingThread }
procedure TReadingThread.Execute;
begin
while not Terminated do
begin
if FOwner.Connected then //Do not use FConn.Connected here because that triggers a read operation on the socket...
begin
try
FOwner.FConn.IOHandler.ReadBytes(FRxData, -1, False); //Get all available data
except
//Do nothing, timeout happened
end;
if (Length(FRxData) > 0) and Assigned(FOwner.FOnRxData) then
Synchronize(DoOnRxData);
end;
end;
end;
procedure TReadingThread.DoOnRxData;
begin
if Assigned(FOwner.FOnRxData) then
FOwner.FOnRxData(Self, BytesToStringRaw(FRxData));
SetLength(FRxData, 0);
end;
constructor TReadingThread.Create(Owner: TTcpClientComm);
begin
FOwner := Owner;
inherited Create(False);
end;