gpt4 book ai didi

delphi - 使用 TIdTCPClient 异步读取

转载 作者:行者123 更新时间:2023-12-03 15:46:08 28 4
gpt4 key购买 nike

我是 Delphi 的新手,我正在尝试进行一些网络操作。在本例中,我想连接到(我们称之为)通知服务器,该服务器将在发生某些事件时发送字符串。

我的第一个方法是这样的:我在自己的线程上运行 TIdTCPClient 并设置 ReadTimeout,这样我就不会总是被阻塞。这样我就可以检查线程的终止状态。

ConnectionToServer.ReadTimeout := MyTimeOut;
while( Continue ) do
begin
//
try
Command := ConnectionToServer.ReadLn( );
except
on E: EIdReadTimeout do
begin
//AnotarMensaje(odDepurar, 'Timeout ' + E.Message );
end;
on E: EIdConnClosedGracefully do
begin
AnotarMensaje(odDepurar, 'Conexión cerrada ' + E.Message );
Continue := false;
end;
on E: Exception do
begin
AnotarMensaje(odDepurar, 'Error en lectura ' + E.Message );
Continue := false;
end;
end;
// treat the command
ExecuteRemoteCommand( Command );
if( self.Terminated ) then
begin
Continue := false;
end;
end; // while continue

阅读 ReadLn 代码,我发现它在重复直到循环中进行一些主动等待,始终检查某些缓冲区大小。

有没有办法以 TIdTCPServer 与 OnExecute 等方法一起工作的方式异步执行此操作?或者,至少,有某种方法可以避免这种主动等待。

最佳答案

Indy 在客户端和服务器端都使用阻塞套接字。它没有什么异步的地方。以TIdTCPServer为例,它在单独的工作线程中运行每个客户端套接字,就像您尝试在客户端中执行的操作一样。 TIdTCPClient 1 不是多线程,因此您必须运行自己的线程。

1:如果您升级到 Indy 10,它会有 TIdCmdTCPClient多线程客户端,为您运行自己的线程,触发 TIdCommandHandler.OnCommand从服务器接收到的数据包的事件。

ReadLn()运行循环直到指定的 ATerminator可以在 InputBuffer 中找到,或者直到发生超时。直到ATerminator发现,ReadLn()从套接字读取更多数据到 InputBuffer并再次扫描。缓冲区大小检查只是为了确保它不会重新扫描已经扫描过的数据。

“唤醒”阻塞的唯一方法ReadLn()调用(或任何阻塞套接字调用,就此而言)是从另一个线程关闭套接字。否则,你只需等待调用正常超时即可。

另请注意 ReadLn()不提出 EIdReadTimeout超时时异常。它设置 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;

如果你不喜欢这个模型,你不必使用Indy。更高效、响应更快的模型是直接使用 WinSock。您可以将重叠 I/O 与 WSARecv() 一起使用,并通过 CreateEvent() 创建一个等待事件或 TEvent 来发出线程终止信号,然后您的线程可以使用 WaitForMultipleObjects() 当无事可做时,在 sleep 时同时等待套接字和终止,例如:

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()叫做。否则,只需调用 SetEvent() 调用Terminate()后.

关于delphi - 使用 TIdTCPClient 异步读取,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44196849/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com