我有一段代码,当许多数据包几乎同时从同一来源到达时,大部分时间都会跳过数据包。这些数据包的构建方式是,数据包大小字段在开头附加,后面有数据包大小,以字节为单位。
TCP客户端线程以10毫秒的间隔运行。
unit AgThread11;
interface
uses
SysUtils, Classes, Windows, Rtti;
type
TAgThreadMethod1 = procedure of object;
TAgThreadMethod2 = procedure;
TAgThread = class ( TThread )
private
fInterval : Cardinal;
fTerminateEvent : THandle;
fRun : Boolean;
fOnRun1 : TAgThreadMethod1;
fOnRun2 : TAgThreadMethod2;
protected
procedure Execute; override;
public
constructor Create
( const AOnRun: TAgThreadMethod1; const AInterval: Cardinal;
const ARun: Boolean = True ); overload;
constructor Create
( const AOnRun: TAgThreadMethod2; const AInterval: Cardinal;
const ARun: Boolean = True ); overload;
destructor Destroy; override;
procedure Signal;
property Run : Boolean read fRun write fRun;
property Interval : Cardinal read fInterval write fInterval;
property OnRun1 : TAgThreadMethod1 read fOnRun1 write fOnRun1;
property OnRun2 : TAgThreadMethod2 read fOnRun2 write fOnRun2;
end;
implementation
constructor TAgThread.Create
( const AOnRun: TAgThreadMethod1; const AInterval: Cardinal;
const ARun: Boolean = True );
begin
fTerminateEvent := CreateEvent ( nil, TRUE, FALSE, nil );
fInterval := AInterval;
fRun := ARun;
fOnRun1 := AOnRun;
inherited Create ( False );
end;
constructor TAgThread.Create
( const AOnRun: TAgThreadMethod2; const AInterval: Cardinal;
const ARun: Boolean = True );
begin
fTerminateEvent := CreateEvent ( nil, TRUE, FALSE, nil );
fInterval := AInterval;
fRun := ARun;
fOnRun2 := AOnRun;
inherited Create ( False );
end;
destructor TAgThread.Destroy;
begin
Terminate;
Signal;
WaitFor;
inherited;
end;
procedure TAgThread.Signal;
begin
SetEvent ( FTerminateEvent );
end;
procedure TAgThread.Execute;
begin
while not Terminated do
begin
if fRun then
if Assigned ( fOnRun1 ) then fOnRun1
else if Assigned ( fOnRun2 ) then fOnRun2;
WaitForSingleObject ( FTerminateEvent, fInterval );
end;
end;
end.
procedure TForm1.THEX_TCP;
var
Buffer : TBytes;
MsgSize : Integer;
begin
if TCPClient.IOHandler.CheckForDataOnSource then
begin
while TCPClient.IOHandler.InputBuffer.Size >= 4 do
begin
fRXCount := fRXCount + 1;
TCPClient.IOHandler.InputBuffer.ExtractToBytes ( Buffer, 4 );
Move ( Buffer [0], MsgSize, 4 );
TCPClient.IOHandler.InputBuffer.ExtractToBytes ( Buffer, MsgSize, False );
NAT.RecievedNATData ( Buffer ); // Packet Processor
end;
end;
end;
如何确保零丢包?
答案 0 :(得分:2)
TCP读取代码存在两个主要问题:
在第二次调用InputBuffer
之前,您无法确保MsgSize
实际上有ExtractToBytes()
个可用字节数。如果您尝试提取的字节数多于缓冲区中的实际字节数,ExtractToBytes()
会引发异常。
更重要的是,在每次调用Buffer
之前,您没有将ExtractToBytes()
变量的大小调整为0。在第一次循环迭代中第一次调用之后,Buffer
的长度为4个字节。如果该消息的大小小于4个字节,则会在Buffer
末尾留下随机字节,这些字节将被传递给您的解析器,并可能破坏其逻辑。但更糟糕的是,如果缓冲区中有另一个消息大小,则下一次循环迭代会对ExtractToBytes()
进行第3次调用,将这4个字节追加到现有Buffer
的末尾内容,不替换您所假设的内容(默认情况下AAppend
ExtractToBytes()
参数为True)。因此,您最终将前一个消息数据中的4个字节复制到MsgSize
变量中,而不是刚刚提取的新4个字节,因此您在下一个{{1}上使用了损坏的MsgSize
值} call。
由于您的数据包是以长度为前缀的,因此您无需使用ExtractToBytes()
或直接访问CheckForDataOnSource()
。使用以下代码让Indy为您完成工作:
InputBuffer
默认情况下,这将阻止调用者,直到数据可供读取。如果在没有准备好读取数据的情况下需要退出procedure TForm1.THEX_TCP;
var
Buffer : TBytes;
MsgSize : Integer;
begin
MsgSize := TCPClient.IOHandler.ReadLongInt;
TCPClient.IOHandler.ReadBytes(Buffer, MsgSize);
Inc(fRXCount);
NAT.RecievedNATData(Buffer);
end;
,请改用以下内容:
THEX_TCP
这种方法唯一的问题是procedure TForm1.THEX_TCP;
var
Buffer : TBytes;
MsgSize : Integer;
begin
if TCPClient.IOHandler.InputBufferIsEmpty then
begin
TCPClient.IOHandler.CheckForDataOnSource;
TCPClient.IOHandler.CheckForDisconnect;
if TCPClient.IOHandler.InputBufferIsEmpty then Exit;
end;
repeat
MsgSize := TCPClient.IOHandler.ReadLongInt;
TCPClient.IOHandler.ReadBytes(Buffer, MsgSize);
Inc(fRXCount);
NAT.RecievedNATData(Buffer);
SetLength(Buffer, 0);
until TCPClient.IOHandler.InputBufferIsEmpty;
end;
和ReadLongInt()
可能会在ReadBytes()
中读取更多字节,因此如果有大量数据,您的循环可能会运行很长时间在很短的时间内发送。如果您绝对必须一次只读取一个缓冲区并且只处理完整的消息,那么请使用以下内容:
InputBuffer