Multithreading 如何可靠地从indy TCP';什么是输入缓冲区?
我有一段代码,当许多数据包几乎同时从同一个源到达时,大部分时间都会跳过数据包。数据包被构建成这样,一个数据包大小字段被附加在开始处,该字段后面有数据包的大小,以字节为单位 TCP客户端线程以10毫秒的间隔运行 线程库 TCP线程代码Multithreading 如何可靠地从indy TCP';什么是输入缓冲区?,multithreading,delphi,tcp,network-programming,indy,Multithreading,Delphi,Tcp,Network Programming,Indy,我有一段代码,当许多数据包几乎同时从同一个源到达时,大部分时间都会跳过数据包。数据包被构建成这样,一个数据包大小字段被附加在开始处,该字段后面有数据包的大小,以字节为单位 TCP客户端线程以10毫秒的间隔运行 线程库 TCP线程代码 如何确保零数据包丢失?您的TCP读取代码存在两个主要问题: 在第二次调用ExtractToBytes()之前,您没有确保InputBuffer实际具有MsgSize可用字节数。如果试图提取的字节数超过缓冲区中的实际字节数,ExtractToBytes()将引发异常
如何确保零数据包丢失?您的TCP读取代码存在两个主要问题:
ExtractToBytes()
之前,您没有确保InputBuffer
实际具有MsgSize
可用字节数。如果试图提取的字节数超过缓冲区中的实际字节数,ExtractToBytes()
将引发异常ExtractToBytes()
之前,您没有将缓冲区的大小调整回0。在第一次循环迭代中的第一次调用之后,缓冲区的长度为4字节。如果该消息的大小小于4个字节,则在缓冲区的末尾留下随机字节,这些字节将被传递到解析器,并可能破坏其逻辑。但更糟糕的是,如果缓冲区中存在另一个消息大小,则下一次循环迭代将第三次调用ExtractToBytes()
,并将这4个字节追加到现有缓冲区的末尾内容,not将按照您的假设替换该内容(默认情况下,ExtractToBytes()
的aaappend
参数为True)。因此,您最终会将上一条消息数据中的4个字节复制到MsgSize
变量中,而不是刚刚提取的新4个字节,因此在下一条ExtractToBytes()上使用损坏的MsgSize
值
打电话
CheckForDataOnSource()
或直接访问InputBuffer
。使用以下代码,让Indy为您完成工作:
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;
默认情况下,这将阻止调用者,直到有数据可读取。如果没有数据可读取时需要退出X_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
,因此,如果在短时间内发送大量数据,则循环可能会运行很长时间。如果您一次只能读取一个缓冲区,并且只能处理完整的消息,请使用以下方法:
procedure TForm1.THEX_TCP;
var
MsgSizeBuffer: array[0..3] of Byte;
MsgSize, I : Integer;
Buffer : TBytes;
begin
TCPClient.IOHandler.CheckForDataOnSource;
TCPClient.IOHandler.CheckForDisconnect;
while TCPClient.IOHandler.InputBuffer.Size >= 4 do
begin
// unfortunately, TIdBuffer does not have a way to peek
// multiple bytes at a time without removing them
for I := 0 to 3 do
MsgSizeBuffer[I] := TCPClient.IOHandler.InputBuffer.Peek(I);
Move(MsgSizeBuffer[0], MsgSize, 4);
MsgSize := LongInt(GStack.NetworkToHost(LongWord(MsgSize)));
if TCPClient.IOHandler.InputBuffer.Size < (4+MsgSize) then
Break;
TCPClient.IOHandler.InputBuffer.Remove(4);
TCPClient.IOHandler.InputBuffer.ExtractToBytes(Buffer, MsgSize);
Inc(fRXCount);
NAT.RecievedNATData(Buffer);
SetLength(Buffer, 0);
end;
end;
程序TForm1.THEX\U TCP;
变量
MsgSizeBuffer:字节数组[0..3];
MsgSize,I:整数;
缓冲区:t字节;
开始
TCPClient.IOHandler.checkforDataSource;
TCPClient.IOHandler.CheckForDisconnect;
而TCPClient.IOHandler.InputBuffer.Size>=4 do
开始
//不幸的是,TIdBuffer没有一种窥视的方式
//一次多个字节而不删除它们
对于I:=0到3 do
MsgSizeBuffer[I]:=TCPClient.IOHandler.InputBuffer.Peek(I);
移动(MsgSizeBuffer[0],MsgSize,4);
MsgSize:=LongInt(GStack.NetworkToHost(LongWord(MsgSize));
如果TCPClient.IOHandler.InputBuffer.Size<(4+MsgSize),则
打破
TCPClient.IOHandler.InputBuffer.Remove(4);
TCPClient.IOHandler.InputBuffer.ExtractToBytes(缓冲区,MsgSize);
公司(fRXCount);
NAT.接收数据(缓冲器);
SetLength(缓冲区,0);
结束;
结束;
您的代码有错误;TCPClient.IOHandler.ReadLongInt()的参数应为False。否则,它将启用NetworkToHost转换。大多数协议使用网络字节顺序发送整数。在设计自定义协议时,您应遵循相同的规则。因此
写入(Longint)
和ReadLongInt()
默认情况下执行转换。您当然可以在需要时手动设置参数,这就是它存在的原因。
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;
procedure TForm1.THEX_TCP;
var
MsgSizeBuffer: array[0..3] of Byte;
MsgSize, I : Integer;
Buffer : TBytes;
begin
TCPClient.IOHandler.CheckForDataOnSource;
TCPClient.IOHandler.CheckForDisconnect;
while TCPClient.IOHandler.InputBuffer.Size >= 4 do
begin
// unfortunately, TIdBuffer does not have a way to peek
// multiple bytes at a time without removing them
for I := 0 to 3 do
MsgSizeBuffer[I] := TCPClient.IOHandler.InputBuffer.Peek(I);
Move(MsgSizeBuffer[0], MsgSize, 4);
MsgSize := LongInt(GStack.NetworkToHost(LongWord(MsgSize)));
if TCPClient.IOHandler.InputBuffer.Size < (4+MsgSize) then
Break;
TCPClient.IOHandler.InputBuffer.Remove(4);
TCPClient.IOHandler.InputBuffer.ExtractToBytes(Buffer, MsgSize);
Inc(fRXCount);
NAT.RecievedNATData(Buffer);
SetLength(Buffer, 0);
end;
end;