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. TIdTCPClient
1 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()
.