我是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等方法一起工作的方式异步执行此操作?或者,至少,某种方式来避免这种积极的等待。
答案 0 :(得分:3)
您可以在单独的线程中执行此操作。
TIdTCPServer在后台使用线程来支持监听多个客户端并与之通信。
由于TIdTCPClient连接到一台服务器,我认为它没有内置此功能,但您可以自己在单独的线程中创建和使用TIdTCPClient,所以对我来说你的解决方案很好。我会以同样的方式解决它。
如果使超时非常小,这应该不是问题套接字在该时间段内仍处于打开状态,因此您不会错过数据。您可以将超时设置为一个小值,如10毫秒。这样,您的线程将不会长时间停留,但超时足够长,不会导致退出和重新进入readln的大量开销。
答案 1 :(得分:3)
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()
等待当没有任何事情要做时,在睡觉时同时进行套接字和终止,例如:
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()
。