Indy使用阻塞套接字,客戶端和服務器端。關於它沒有任何異步。在TIdTCPServer
的情況下,它將在單獨的工作線程中運行每個客戶端套接字,就像您在客戶端嘗試執行的一樣。 TIdTCPClient
不是多線程的,所以你必須運行你自己的線程。
:如果您升級到印第安納波利斯10,它有一個TIdCmdTCPClient
客戶端是多線程的,運行其自己的線程你,觸發TIdCommandHandler.OnCommand
事件從服務器接收的數據包。
ReadLn()
運行一個循環,直到在InputBuffer
中找到指定的ATerminator
,或者直到發生超時。在找到ATerminator
之前,ReadLn()
會將更多數據從套接字讀取到InputBuffer
並再次掃描。緩衝區大小檢查只是爲了確保它不會重新掃描已經掃描的數據。
「喚醒」阻塞ReadLn()
調用(或任何阻塞的套接字調用,就此而言)的唯一方法是從另一個線程關閉套接字。否則,你只需等待通話超時。請注意0在超時時不會引發異常。它設置ReadLnTimedout
屬性爲True,然後返回一個空字符串,如:
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;
如果你不喜歡這種模式,你不必用印。一個更高效和響應的模型應該是直接使用WinSock。您可以使用WSARecv()
的Overlapped I/O,並通過CreateEvent()
或TEvent
創建一個等待事件以表示線程終止,然後您的線程可以使用WaitForMultipleObjects()
同時等待套接字和終端,同時休眠時無需任何操作例如:
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;
如果使用的Delphi XE2或更高版本,TThread
具有虛擬TerminatedSet()
方法可以重寫到信號hTermEvent
時TThread.Terminate()
被調用。否則,致電Terminate()
後致電SetEvent()
。
感謝您的分解。我不擔心ReadLn的阻塞性質,我擔心檢查緩衝區大小的繁忙等待循環。 我堅持使用老版本的Indy和Delphi 7(遺留代碼),所以沒有太大的改變空間。 –
@HéctorC。然後考慮徹底擺脫讀取超時,讓'ReadLn'在沒有數據要讀取時阻塞線程,然後在準備好終止線程時斷開客戶端連接。 –