使用TIdTCPClient进行异步读取

时间:2017-05-26 08:20:41

标签: delphi asynchronous indy

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

2 个答案:

答案 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()