Delphi 使用TIdTCPClient进行异步读取

Delphi 使用TIdTCPClient进行异步读取,delphi,asynchronous,indy,Delphi,Asynchronous,Indy,我是德尔福的新手,我正在尝试做一些网络操作。在本例中,我希望连接到一个(我们称之为)通知服务器,该服务器将在任何事件发生时发送字符串 我的第一个方法是: 我在自己的线程上运行TIdTCPClient并设置ReadTimeout,这样我就不会总是被阻塞。这样我可以检查线程的终止状态 ConnectionToServer.ReadTimeout := MyTimeOut; while( Continue ) do begin // try Command := Con

我是德尔福的新手,我正在尝试做一些网络操作。在本例中,我希望连接到一个(我们称之为)通知服务器,该服务器将在任何事件发生时发送字符串

我的第一个方法是: 我在自己的线程上运行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代码时,我看到它在repeat-until循环中执行一些活动等待,该循环始终检查一些缓冲区大小


有没有一种方法可以像TIdTCPServer使用OnExecute等方法那样异步执行此操作?或者,至少可以通过某种方式避免这种活动等待。

您可以在单独的线程中执行此操作

TIdTCPServer在后台使用线程来支持侦听多个客户端并与之通信

由于TIdTCPClient连接到一台服务器,我认为它没有内置此功能,但您可以自己在单独的线程中创建和使用TIdTCPClient,因此对我来说,您的解决方案是好的。我会用同样的方法解决它


如果您将超时设置得很小,那么在这段时间内套接字仍处于打开状态,这样您就不会丢失数据,这应该不是问题。您可以将超时设置为10ms之类的小值。这样,您的线程不会停留很长时间,但超时时间足够长,不会导致退出和重新进入readln的大量开销。

Indy使用阻塞套接字,包括客户端和服务器端。这没有什么不同步的。对于
TIdTCPServer
,它在一个单独的工作线程中运行每个客户机套接字,就像您在客户机中尝试做的那样
TIdTCPClient
1不是多线程的,因此您必须运行自己的线程

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

ReadLn()
运行循环,直到在
InputBuffer
中找到指定的
ater
,或者直到出现超时。在找到
a计数器之前,
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;
如果你不喜欢这个型号,你就不必使用印地。一个更高效、响应更快的模型是直接使用WinSock。您可以使用重叠I/O,并通过或创建可等待事件,以发出线程终止的信号,然后您的线程可以在无事可做的情况下在睡眠时同时等待套接字和终止,例如:

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;
hSocket=socket(…);
连接(hSocket,…);
HterEvent:=CreateEvent(无、真、假、无);
...
变量
缓冲区:AnsiChar的数组[0..1023];
wb:WSABUF;
nRecv,nFlags:DWORD;
ov:WSAOVERLAPPED;
h:THandle的数组[0..1];
命令:字符串;
数据,组块:AnsiString;
一、 J:整数;
开始
零内存(@ov,sizeof(ov));
ov.hEvent:=CreateEvent(无、真、假、无);
尝试
h[0]:=ov.hEvent;
h[1]:=hTERMENT;
尝试
虽然没有终止
开始
wb.len:=sizeof(缓冲区);
wb.buf:=缓冲区;
nFlags:=0;
如果WSARecv(hSocket,@wb,1,@nRecv,@nFlags,@ov,nil)=套接字错误,则
开始
如果WSAGetLastError()WSA_IO_挂起,则
赖斯·塞罗;
结束;
case WaitForMultipleObjects(2,PWOHandleArray(@h),False,无限)
等待\u对象\u 0:开始
如果不是WSAGetOverlappedResult(hSocket,@ov,@nRecv,True,@nFlags),那么
赖斯·塞罗;
如果nRecv=0,则
开始
AnotarMensaje(odDepurar,'Conexión cerrada');
出口
结束;
I:=长度(数据);
设置长度(数据,I+nRecv);
移动(缓冲区、数据[I]、nRecv);
I:=Pos(数据,#10);
而我呢
开始
J:=I;
如果(J>1)和(数据[J-1]=#13),则
12月(J);
命令:=复制(数据,1,J-1);
删除(数据,1,I);
ExecuteMoteCommand(命令);
结束;
结束;
等待\u对象\u 0+1:开始
出口
结束;
等待失败:开始
赖斯·塞罗;
结束;
结束;
结束;
除了
关于E:Exception-do
开始
AnotarMensaje(odDepurar,“讲课时出错”+E.信息);
结束;
结束;
最后
闭合手柄(ov.hEvent);
结束;
结束;

如果您使用的是Delphi XE2或更高版本,
TThread
有一个虚拟的
TerminatedSet()
方法,当调用
TThread.Terminate()
时,您可以重写该方法以发出
hterEvent
信号。否则,只需在调用
Terminate()

后调用即可,这就是我要做的。但我必须定期退出ReadLn操作,以检查终止线程属性。另外,当我在别处终止这个线程时,我必须等到ReadLn超时才能从线程退出。是的。如果您将超时设置得很小,这是一个问题吗?套接字在此期间仍处于打开状态,因此可以将超时设置为10ms之类的小值。这样,您的线程不会停留很长时间,但超时时间足够长,不会导致sig