gpt4 book ai didi

delphi - 使用 TIdTcpClient 超时接收部分数据

转载 作者:行者123 更新时间:2023-12-02 01:36:21 25 4
gpt4 key购买 nike

如何使用 TIdTcpClient 接收具有以下条件的 100 字节字符串?:

  • 如果没有任何进来,Read 调用将被阻塞,线程将永远等待
  • 如果收到 100 个字节,Read 调用应返回字节字符串
  • 如果接收到的字节数多于 0 个字节,但少于 100 个字节,Read 调用应在超时(例如 1 秒)后返回,以便在合理的时间内至少返回一些内容,而不会产生超时异常,因为 Delphi 中的异常处理IDE 的 Debug模式还不够方便。

我目前不是最佳的代码如下:

unit Unit2;

interface

uses
System.Classes, IdTCPClient;

type
TTcpReceiver = class(TThread)
private
_tcpc: TIdTCPClient;
_onReceive: TGetStrProc;
_buffer: AnsiString;
procedure _receiveLoop();
procedure _postBuffer;
protected
procedure Execute(); override;
public
constructor Create(); reintroduce;
destructor Destroy(); override;
property OnReceive: TGetStrProc read _onReceive write _onReceive;
end;

implementation

uses
System.SysUtils, Vcl.Dialogs, IdGlobal, IdExceptionCore;

constructor TTcpReceiver.Create();
begin
inherited Create(True);
_buffer := '';
_tcpc := TIdTCPClient.Create(nil);
//_tcpc.Host := '192.168.52.175';
_tcpc.Host := '127.0.0.1';
_tcpc.Port := 1;
_tcpc.ReadTimeout := 1000;
_tcpc.Connect();
Suspended := False;
end;

destructor TTcpReceiver.Destroy();
begin
_tcpc.Disconnect();
FreeAndNil(_tcpc);
inherited;
end;

procedure TTcpReceiver.Execute;
begin
_receiveLoop();
end;

procedure TTcpReceiver._postBuffer();
var buf: string;
begin
if _buffer = '' then Exit;
buf := _buffer;
_buffer := '';
if Assigned(_onReceive) then begin
Synchronize(
procedure()
begin
_onReceive(buf);
end
);
end;
end;

procedure TTcpReceiver._receiveLoop();
var
c: AnsiChar;
begin
while not Terminated do begin
try
c := AnsiChar(_tcpc.IOHandler.ReadByte());
_buffer := _buffer + c;
if Length(_buffer) > 100 then
_postBuffer();
except
//Here I have to ignore EIdReadTimeout in Delphi IDE everywhere, but I want just to ignore them here
on ex: EIdReadTimeout do _postBuffer();
end;
end;
end;

end.

最佳答案

TCP 是面向流的,而不是像 UDP 那样面向消息。读取没有任何结构的任意字节是糟糕的设计,如果您过早停止读取,然后您想要读取的字节在您停止读取后到达,则很容易破坏您的通信。这些字节在被读取之前不会从套接字中删除,因此下一次读取可能会具有比预期更多/不同的字节。

如果您期望 100 个字节,那么只需读取 100 个字节即可完成。如果发送方仅发送 50 个字节,则需要提前告诉您,以便您在收到 50 个字节后可以停止读取。如果发送者没有这样做,那么这是一个设计非常糟糕的协议(protocol)。一般来说,使用超时来检测传输结束是糟糕的设计。网络延迟很容易导致误检测。

TCP 消息应该被充分地构建,以便接收者准确地知道一个消息在哪里结束以及下一个消息在哪里开始。在 TCP 中可以通过三种方法来做到这一点:

  1. 使用固定长度的消息。接收方可以继续读取,直到达到预期的字节数。

  2. 在发送消息本身之前发送消息的长度。接收方可以先读取长度,然后继续读取,直到到达指定的字节数。

  3. 使用消息数据中未出现的唯一分隔符终止消息。接收方可以继续读取字节,直到分隔符到达。

<小时/>

话虽如此,您所要求的可以在TCP中完成(但是不应该在TCP中完成!)。而且根本不需要使用手动缓冲区就可以完成,而是使用 Indy 的内置缓冲区。例如:

unit Unit2;

interface

uses
System.Classes, IdTCPClient;

type
TTcpReceiver = class(TThread)
private
_tcpc: TIdTCPClient;
_onReceive: TGetStrProc;
procedure _receiveLoop;
procedure _postBuffer;
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
property OnReceive: TGetStrProc read _onReceive write _onReceive;
end;

implementation

uses
System.SysUtils, Vcl.Dialogs, IdGlobal;

constructor TTcpReceiver.Create;
begin
inherited Create(False);
_tcpc := TIdTCPClient.Create(nil);
//_tcpc.Host := '192.168.52.175';
_tcpc.Host := '127.0.0.1';
_tcpc.Port := 1;
end;

destructor TTcpReceiver.Destroy;
begin
_tcpc.Free;
inherited;
end;

procedure TTcpReceiver.Execute;
begin
_tcpc.Connect;
try
_receiveLoop;
finally
_tcpc.Disconnect;
end;
end;

procedure TTcpReceiver._postBuffer;
var
buf: string;
begin
with _tcpc.IOHandler do
buf := ReadString(IndyMin(InputBuffer.Size, 100));
{ alternatively:
with _tcpc.IOHandler.InputBuffer do
buf := ExtractToString(IndyMin(Size, 100));
}
if buf = '' then Exit;
if Assigned(_onReceive) then
begin
Synchronize(
procedure
begin
if Assigned(_onReceive) then
_onReceive(buf);
end
);
end;
end;

procedure TTcpReceiver._receiveLoop;
var
LBytesRecvd: Boolean;
begin
while not Terminated do
begin
while _tcpc.IOHandler.InputBufferIsEmpty do
begin
_tcpc.IOHandler.CheckForDataOnSource(IdTimeoutInfinite);
_tcpc.IOHandler.CheckForDisconnect;
end;

while _tcpc.IOHandler.InputBuffer.Size < 100 do
begin
// 1 sec is a very short timeout to use for TCP.
// Consider using a larger timeout...
LBytesRecvd := _tcpc.IOHandler.CheckForDataOnSource(1000);
_tcpc.IOHandler.CheckForDisconnect;
if not LBytesRecvd then Break;
end;

_postBuffer;
end;
end;

end.
<小时/>

顺便说一句,您所说的“Delphi IDE Debug模式中的异常处理尚未变得方便”简直是荒谬的。 Indy 的 IOHandler 具有用于控制异常行为的属性和方法参数,如果您不喜欢调试器处理异常的方式,则只需将其配置为忽略它们即可。您可以将调试器配置为忽略特定的异常类型,也可以使用断点告诉调试器跳过处理特定代码块中的异常。

关于delphi - 使用 TIdTcpClient 超时接收部分数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45933510/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com