Indy uses blocking sockets, both client and server side. There is nothing asynchronous about it. In the case of TIdTCPServer, it runs each client socket in a separate worker thread, just like you are trying to do in your client. TIdTCPClient1 is not multi-threaded, so you have to run your own thread.
1: If you upgrade to Indy 10, it has a TIdCmdTCPClient client that is multi-threaded, running its own thread for you, triggering TIdCommandHandler.OnCommand events for packets received from the server.
ReadLn() runs a loop until the specified ATerminator is found in the InputBuffer, or until a timeout occurs. Until the ATerminator is found, ReadLn() reads more data from the socket into the InputBuffer and scans it again. The buffer size checking is just to make sure it doesn't re-scan data it has already scanned.
The only way to "wake up" a blocking ReadLn() call (or any blocking socket call, for that matter) is to close the socket from the another thread. Otherwise, you just have to wait for the call to timeout normally.
Also note that ReadLn() does not raise an EIdReadTimeout exception when it times out. It sets the ReadLnTimedout property to True and then returns a blank string, eg:
ConnectionToServer.ReadTimeout := MyTimeOut;
while not Terminated do
begin
try
Command := ConnectionToServer.ReadLn;
except
on E: Exception do
begin
if E is EIdConnClosedGracefully then
AnotarMensaje(odDepurar, 'Conexión cerrada')
else
AnotarMensaje(odDepurar, 'Error en lectura: ' + E.Message );
Exit;
end;
end;
if ConnectionToServer.ReadLnTimedout then begin
//AnotarMensaje(odDepurar, 'Timeout');
Continue;
end;
// treat the command
ExecuteRemoteCommand( Command );
end;
If you don't like this model, you don't have to use Indy. A more efficient and responsive model would be to use WinSock directly instead. You can use Overlapped I/O with WSARecv(), and create a waitable event via CreateEvent() or TEvent to signal thread termination, and then your thread can use WaitForMultipleObjects() to wait on both socket and termination at the same time while sleeping when there is nothing to do, eg:
hSocket = socket(...);
connect(hSocket, ...);
hTermEvent := CreateEvent(nil, True, False, nil);
...
var
buffer: array[0..1023] of AnsiChar;
wb: WSABUF;
nRecv, nFlags: DWORD;
ov: WSAOVERLAPPED;
h: array[0..1] of THandle;
Command: string;
Data, Chunk: AnsiString;
I, J: Integer;
begin
ZeroMemory(@ov, sizeof(ov));
ov.hEvent := CreateEvent(nil, True, False, nil);
try
h[0] := ov.hEvent;
h[1] := hTermEvent;
try
while not Terminated do
begin
wb.len := sizeof(buffer);
wb.buf := buffer;
nFlags := 0;
if WSARecv(hSocket, @wb, 1, @nRecv, @nFlags, @ov, nil) = SOCKET_ERROR then
begin
if WSAGetLastError() <> WSA_IO_PENDING then
RaiseLastOSError;
end;
case WaitForMultipleObjects(2, PWOHandleArray(@h), False, INFINITE) of
WAIT_OBJECT_0: begin
if not WSAGetOverlappedResult(hSocket, @ov, @nRecv, True, @nFlags) then
RaiseLastOSError;
if nRecv = 0 then
begin
AnotarMensaje(odDepurar, 'Conexión cerrada');
Exit;
end;
I := Length(Data);
SetLength(Data, I + nRecv);
Move(buffer, Data[I], nRecv);
I := Pos(Data, #10);
while I <> 0 do
begin
J := I;
if (J > 1) and (Data[J-1] = #13) then
Dec(J);
Command := Copy(Data, 1, J-1);
Delete(Data, 1, I);
ExecuteRemoteCommand( Command );
end;
end;
WAIT_OBJECT_0+1: begin
Exit;
end;
WAIT_FAILED: begin
RaiseLastOSError;
end;
end;
end;
except
on E: Exception do
begin
AnotarMensaje(odDepurar, 'Error en lectura ' + E.Message );
end;
end;
finally
CloseHandle(ov.hEvent);
end;
end;
If you are using Delphi XE2 or later, TThread has a virtual TerminatedSet() method you can override to signal hTermEvent when TThread.Terminate() is called. Otherwise, just call SetEvent() after calling Terminate().