【问题标题】:Asynchronous read using TIdTCPClient使用 TIdTCPClient 进行异步读取
【发布时间】:2017-10-27 01:38:58
【问题描述】:

我是 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 等方法一起工作的方式异步执行此操作?或者,至少,有某种方法可以避免这种主动等待。

【问题讨论】:

    标签: delphi asynchronous indy


    【解决方案1】:

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

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

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

    如果你把超时时间设置得非常小应该不是问题套接字在那段时间仍然是打开的,所以你不会错过数据。您可以将超时设置为一个较小的值,例如 10 毫秒。这样,您的线程不会逗留很长时间,但超时时间足够长,不会导致退出和重新进入 readln 的显着开销。

    【讨论】:

    • 这就是我正在做的。但是我必须定期退出 ReadLn 操作以检查 Terminated 线程属性。此外,当我在其他地方终止此线程时,我必须等到 ReadLn 超时才能退出线程。
    • 是的。如果您将超时设置得很小,这会不会有问题?在那段时间内,套接字仍处于打开状态,因此您可以将超时设置为一个较小的值,例如 10 毫秒。这样,您的线程不会逗留很长时间,但超时时间足够长,不会导致退出和重新进入 readln 的显着开销。
    • 顺便说一句,我很好奇并用谷歌搜索了一下。我发现了类似的问题Indy 10 IdTCPClient Reading Data using a separate threadIndy TIdTCPClient Reading Data,这两个问题都显示了有关如何实现此线程并克服将此事件与主线程同步的问题的更详细的示例。
    • 谢谢。我会看看那些线程。我想我不能使用 Indy 10(很遗憾,我正在开发一个旧版应用程序)。我正在考虑在通话之间让线程休眠,因为服务器不会经常发送消息。
    • 我认为这就是您在调用 ReadLn 时也超时时所做的事情。在线程中进行额外的等待不会造成太大的伤害,但是在退出时,您首先必须等待 readln 超时,然后是线程超时,这样可能会造成混淆。
    【解决方案2】:

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

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

    ReadLn() 运行一个循环,直到在InputBuffer 中找到指定的ATerminator,或者直到发生超时。在找到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。您可以将 Overlapped 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() 方法,您可以在调用 TThread.Terminate() 时覆盖以发出信号 hTermEvent。否则,只需在调用Terminate() 后调用SetEvent()

    【讨论】:

    • 感谢您的分解。我不担心 ReadLn 的阻塞性质,我担心检查缓冲区大小的繁忙等待循环。我被旧版本的 Indy 和 Delphi 7(遗留代码)困住了,所以没有太大的改变空间。
    • @HéctorC。然后考虑完全摆脱读取超时,让ReadLn在没有数据读取时阻塞线程,然后在准备终止线程时断开客户端。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-31
    • 1970-01-01
    • 2013-06-26
    • 1970-01-01
    • 1970-01-01
    • 2011-06-04
    • 1970-01-01
    相关资源
    最近更新 更多