使用 TIdTCPClient 异步读取

Héc*_* C. 4 delphi asynchronous indy

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

\n\n

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

\n\n
ConnectionToServer.ReadTimeout := MyTimeOut;\nwhile( Continue ) do\nbegin\n    //\n    try\n        Command := ConnectionToServer.ReadLn( );\n    except\n    on E: EIdReadTimeout do\n        begin\n                //AnotarMensaje(odDepurar, 'Timeout ' + E.Message );\n        end;\n    on E: EIdConnClosedGracefully do\n        begin\n                AnotarMensaje(odDepurar, 'Conexi\xc3\xb3n cerrada ' + E.Message );\n                Continue := false;\n        end;\n    on E: Exception do\n        begin\n                AnotarMensaje(odDepurar, 'Error en lectura ' + E.Message );\n                Continue := false;\n        end;\n    end;\n    // treat the command\n    ExecuteRemoteCommand( Command );    \n    if( self.Terminated ) then\n    begin\n        Continue := false;\n    end;\nend;    // while continue\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

\n

Rem*_*eau 5

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

\n\n

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

\n\n

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

\n\n

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

\n\n

另请注意,超时时ReadLn()不会引发异常。EIdReadTimeout它将ReadLnTimedout属性设置为 True,然后返回一个空字符串,例如:

\n\n
ConnectionToServer.ReadTimeout := MyTimeOut;\n\nwhile not Terminated do\nbegin\n  try\n    Command := ConnectionToServer.ReadLn;\n  except\n    on E: Exception do\n    begin\n      if E is EIdConnClosedGracefully then\n        AnotarMensaje(odDepurar, \'Conexi\xc3\xb3n cerrada\')\n      else\n        AnotarMensaje(odDepurar, \'Error en lectura: \' + E.Message );\n      Exit;\n    end;\n  end;\n\n  if ConnectionToServer.ReadLnTimedout then begin\n    //AnotarMensaje(odDepurar, \'Timeout\');\n    Continue;\n  end;\n\n  // treat the command\n  ExecuteRemoteCommand( Command );    \nend;\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
hSocket = socket(...);\nconnect(hSocket, ...);\nhTermEvent := CreateEvent(nil, True, False, nil);\n\n...\n\nvar\n  buffer: array[0..1023] of AnsiChar;\n  wb: WSABUF;\n  nRecv, nFlags: DWORD;\n  ov: WSAOVERLAPPED;\n  h: array[0..1] of THandle;\n  Command: string;\n  Data, Chunk: AnsiString;\n  I, J: Integer;\nbegin\n  ZeroMemory(@ov, sizeof(ov));\n  ov.hEvent := CreateEvent(nil, True, False, nil);\n  try\n    h[0] := ov.hEvent;\n    h[1] := hTermEvent;\n\n    try\n      while not Terminated do\n      begin\n        wb.len := sizeof(buffer);\n        wb.buf := buffer;\n\n        nFlags := 0;\n\n        if WSARecv(hSocket, @wb, 1, @nRecv, @nFlags, @ov, nil) = SOCKET_ERROR then\n        begin\n          if WSAGetLastError() <> WSA_IO_PENDING then\n            RaiseLastOSError;\n        end;\n\n        case WaitForMultipleObjects(2, PWOHandleArray(@h), False, INFINITE) of\n          WAIT_OBJECT_0: begin\n            if not WSAGetOverlappedResult(hSocket, @ov, @nRecv, True, @nFlags) then\n              RaiseLastOSError;\n\n            if nRecv = 0 then\n            begin\n              AnotarMensaje(odDepurar, \'Conexi\xc3\xb3n cerrada\');\n              Exit;\n            end;\n\n            I := Length(Data);\n            SetLength(Data, I + nRecv);\n            Move(buffer, Data[I], nRecv);\n\n            I := Pos(Data, #10);\n            while I <> 0 do\n            begin\n              J := I;\n              if (J > 1) and (Data[J-1] = #13) then\n                Dec(J);\n\n              Command := Copy(Data, 1, J-1);\n              Delete(Data, 1, I);\n\n              ExecuteRemoteCommand( Command );\n            end;\n          end;\n\n          WAIT_OBJECT_0+1: begin\n            Exit;\n          end;\n\n          WAIT_FAILED: begin\n            RaiseLastOSError;\n          end;\n        end;\n      end;\n    except\n      on E: Exception do\n      begin\n        AnotarMensaje(odDepurar, \'Error en lectura \' + E.Message );\n      end;\n    end;\n  finally\n    CloseHandle(ov.hEvent);\n  end;\nend;\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您使用的是 Delphi XE2 或更高版本,TThread则有一个虚拟TerminatedSet()方法,您可以覆盖该方法以在调用hTermEvent时发出信号。TThread.Terminate()否则,就SetEvent()调用后调用Terminate()

\n