我用indy udp套接字制作了一个简单的文件传输程序,但是出现了问题。
由于UDP不保证数据的顺序,因此我尝试手动使udp可靠。
首先,我做了一个TPacket记录,其中包括序列号,下一个号码, 索引号和字节(数据)数组。
序列号是数据包的ID。 下一个号码是接收者接下来需要接收数据包的序列号。 索引号是数据包的索引,当发件人一次发送多个数据包时,用于对数据包进行排序。
发送方将数据分成固定长度的数据包,然后发送数据包总数。
在此之后,发送方尝试逐渐发送数据包:发送数据包计数,发送数据包,接收错误数据包的索引,然后重新发送错误数据包。 (接收方接收数据包并按索引对数据包进行排序,如果丢失了数据包,则接收方发送丢失数据包的索引。)
我尝试发送大文件。 它在本地(本地主机,热点)中运行良好,但是有时通过网络连接(通过AP,互联网)时会停止
我调试了我的应用程序,发现发送与接收不匹配(在源中为“ SendAck”和“ RecvAck”)(例如,发送方发送数据包计数,接收方发送错误数据包索引。 ..接收方应该已经收到数据包的计数。)
我的来源(省略了一些部分):
unit UUDPHelper;
interface
uses
System.Classes, System.Generics.Collections, IdUDPClient, IdUDPServer, System.SysUtils,
System.Math, IdGlobal, IdSocketHandle, UCode, IdExceptionCore, IdStack,
System.Generics.Defaults, System.DateUtils;
type
TPacket = record
Serial:Int64;
Next:Int64;
Index:Int64;
data:TBytes;
end;
TIdAck = (IA_OK = 1,IA_FAIL);
EReadTimeout = class(Exception) end;
TUDPClient = class(TIdUDPClient)
private
RNum:Int64;
SNum:Int64;
function ReceiveBuffer:TBytes; overload;
function StreamToPackets(Stream:TMemoryStream):TList<TPacket>;
procedure SendAck(Ack:Int64);
function RecvAck(Timeout:Int64 = -1):Int64;
public
constructor Create(AOwner:TComponent);
procedure AtConnected(Sender:TObject);
procedure AtDisconnected(Sender:TObject);
procedure Signal; overload;
procedure Signal(Code:TCode); overload;
procedure DisconnectSignal;
procedure ConnectSignal;
procedure WriteInt64(value:Int64);
procedure WriteString(str:string);
procedure WriteStream(Stream:TMemoryStream);
function ReadInt64():Int64;
function ReadString():string;
procedure ReadStream(var Stream:TMemoryStream);
end;
TIPNum = record
IP:string;
RNum:Int64;
SNum:Int64;
end;
TUDPRead = reference to procedure(const AData:TBytes; ABinding:TIdSocketHandle);
TUDPServer = class(TIdUDPServer)
private
IPNumList:TList<TIPNum>;
function ReceiveBuffer(ABinding:TIdSocketHandle):TBytes;
procedure SetRNum(RNum:Int64; Idx:Integer);
procedure SetSNum(SNum:Int64; Idx:Integer);
procedure SendAck(Ack:Int64; ABinding:TIdSocketHandle);
function RecvAck(ABinding:TIdSocketHandle; Timeout:Int64 = -1):Int64;
public
AtUDPRead:TUDPRead;
constructor Create(AOwner:TComponent);
destructor Destroy;
procedure InUDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle);
function FindIPNum(IP:string):Integer;
function GetIPNum(IP:string):TIPNum;
procedure ClearIPNum;
function StreamToPackets(Stream:TMemoryStream; IP:string):TList<TPacket>;
procedure WriteInt64(value:Int64; ABinding:TIdSocketHandle);
procedure WriteString(str:string; ABinding:TIdSocketHandle);
procedure WriteStream(Stream:TMemoryStream; ABinding:TIdSocketHandle);
function ReadInt64(ABinding:TIdSocketHandle):Int64;
function ReadString(ABinding:TIdSocketHandle):string;
procedure ReadStream(var Stream:TMemoryStream; ABinding:TIdSocketHandle);
end;
procedure TUDPClient.ReadStream(var Stream: TMemoryStream);
var
count:Int64;
ack,ack2:TIdAck;
pList:TList<TPacket>;
buffer:TBytes;
packet:TPacket;
I,J,K:Int64;
T:Int64;
Amount:Integer;
Idxs,Temp:array of Int64;
nIdx,TempLen,pn:Integer;
IsFound:Boolean;
begin
count:=RecvAck(); // receive the total count of packets.
pList:=TList<TPacket>.Create();
I:=0;
T:=0;
nIdx:=0;
SetLength(Idxs,MAX_AMOUNT);
SetLength(Temp,MAX_AMOUNT);
while I<count do
begin
Amount:=RecvAck(); // receive the count of packets.
J:=0;
pn:=nIdx;
while nIdx<Amount do
begin
if T+J>=count then break;
Idxs[nIdx]:=T+J;
inc(nIdx);
inc(J);
end;
{Idxs is the array of indexes of packets which will be received soon.}
for J:=1 to Amount do
begin
try
buffer:=ReceiveBuffer();
except
buffer:=nil;
end;
if (buffer=nil) or (Length(buffer)=0) then break;
packet:=BytesToPacket(buffer);
if Length(packet.data)=0 then break;
IsFound:=False;
K:=0;
while K<pList.Count do
begin
if pList[K].Index=packet.Index then
begin
IsFound:=True;
break;
end;
inc(K);
end;
if IsFound then
pList.Delete(K);
pList.Add(packet);
Finalize(buffer);
end;
pList.Sort(TComparer<TPacket>.Construct(function(const Left,Right:TPacket):Integer
begin
Result:=Left.Index-Right.Index;
end));
//Finds the missed packets with Idxs array.
TempLen:=0;
for J:=0 to nIdx-1 do
begin
IsFound:=False;
for K:=I to pList.Count-1 do
if pList[K].Index=Idxs[J] then
begin
IsFound:=True;
break;
end;
if not IsFound then
begin
Temp[TempLen]:=Idxs[J];
inc(TempLen);
end;
end;
// counts the packets which is sent normally.
J:=I;
while J < pList.Count do
begin
if pList[J].Index <> J then
break;
inc(J);
end;
I:=J;
T := T + Amount - pn;
Move(Temp[0],Idxs[0],sizeof(Int64)*TempLen);
nIdx:=TempLen;
// if there is no missed packet, sends OK sign.
// else, sends FAIL and error indexes.
if nIdx=0 then
SendAck(Int64(IA_OK))
else
begin
SendAck(Int64(IA_FAIL));
SendAck(Int64(nIdx));
for J:=0 to nIdx-1 do
SendAck(Idxs[J]);
end;
end;
Finalize(Idxs);
Finalize(Temp);
Stream:=PacketsToStream(pList);
Stream.Position:=0;
for I:=0 to pList.Count-1 do
if Length(pList[I].data)>0 then
begin
buffer:=pList[I].data;
Finalize(buffer);
end;
pList.Free;
end;
procedure TUDPClient.WriteStream(Stream: TMemoryStream);
var
pList:TList<TPacket>;
buffer:TBytes;
ack,ack2:TIdAck;
Amount:Integer;
I,T:Int64;
ldt,dt,ot:TDateTime;
J:Integer;
Idxs:array of Int64;
nIdx,pn:Integer;
ncount:Integer;
Index:Int64;
IsFound:Boolean;
IsExcept:Boolean;
m:Int64;
begin
Stream.Position:=0;
pList:=StreamToPackets(Stream);
SendAck(Int64(pList.Count)); // sends the total count of packets
Amount:=30;
I:=0;
T:=0;
SetLength(Idxs,MAX_AMOUNT);
nIdx:=0;
ncount:=0;
ldt:=-1;
while I<pList.Count do
begin
// Fill Idxs array.
// Idxs array contains the indexes of packets to be sent soon.
J:=0;
while nIdx<Amount do
begin
if T+J>=pList.Count then break;
Idxs[nIdx]:=T+J;
inc(nIdx);
inc(J);
end;
pn:=nIdx;
SendAck(Int64(nIdx)); // sends the count of packets.
ot:=Date()+Time();
for J:=0 to nIdx-1 do
begin
buffer:=PacketToBytes( pList[Idxs[J]] );
SendBuffer(TIdBytes(buffer));
Finalize(buffer);
end;
ack:=TIdAck(RecvAck(10000)); // receives the ack of the receiver.
dt:=Date()+Time();
T:=T+nIdx-ncount;
if ack=IA_FAIL then // if failed, fill the idxs array with error indexs.
begin
nIdx:=RecvAck();
for J:=0 to nIdx-1 do
Idxs[J]:=RecvAck();
ncount:=nIdx;
end
else // if succeeded, clear the idxs array.
begin
ncount:=0;
nIdx:=0;
end;
I:=I+pn-nIdx;
// send-amount controlling //
if ldt<>-1 then
begin
if SecondsBetween(ot,ldt)>SecondsBetween(ot,dt) then
Amount:=Min(Amount+10,MAX_AMOUNT);
if SecondsBetween(ot,ldt)<SecondsBetween(ot,dt) then
Amount:=Max(Amount-10,Max(nIdx,30));
end;
ldt:=dt;
end;
Finalize(Idxs);
for I:=0 to pList.Count-1 do
if Length(pList[I].data)>0 then
begin
buffer:=pList[I].data;
Finalize(buffer);
end;
pList.Free;
end;
procedure TUDPClient.SendAck(Ack: Int64);
var
p:TPacket;
p2:TPacket;
o,t:TDateTime;
buffer:TBytes;
begin
p.Serial:=SNum;
p.Next:=Rand();
SNum:=p.Next;
p.Index:=Ack;
p2.Index:=Int64(IA_FAIL);
p2.Serial:=RNum;
o:=Date()+Time();
while not ((p2.Serial=RNum) and (p2.Index=Int64(IA_OK))) do
begin
if p2.Serial=RNum then
SendBuffer(TIdBytes(PacketToBytes(p)));
try
try
buffer:=ReceiveBuffer();
if Length(buffer)>24 then continue;
p2:=BytesToPacket(buffer);
except
end;
finally
Finalize(buffer);
t:=Date()+Time();
if SecondsBetween(o,t)>=5 then
begin
SNum:=p.Serial;
raise EReadTimeout.Create('Read Timeout');
end;
end;
end;
SendBuffer(TIdBytes(PacketToBytes(p)));
RNum:=p2.Next;
end;
function TUDPClient.RecvAck(Timeout:Int64): Int64;
var
p,p2,ap:TPacket;
buffer:TBytes;
o,t:TDateTime;
begin
if Timeout=-1 then
Timeout:=5000;
p.Serial:=RNum+1;
o := Date() + Time();
while p.Serial<>RNum do
begin
try
try
buffer:=ReceiveBuffer();
if Length(buffer)>24 then continue;
p := BytesToPacket(buffer);
except
end;
finally
T := Date() + Time();
if SecondsBetween(o,T) >= Timeout / 1000 then
raise EReadTimeout.Create('Read Timeout');
end;
end;
p2.Serial:=SNum;
p2.Next:=Rand();
SNum:=p2.Next;
p2.Index:=Int64(IA_OK);
o:=Date()+Time();
t:=o;
ap.Serial:=RNum+1;
while True do
begin
if SecondsBetween(o,t)>=2 then break;
SendBuffer(TIdBytes(PacketToBytes(p2)));
try
try
buffer:=ReceiveBuffer();
ap:=BytesToPacket(buffer);
except
continue;
end;
finally
t:=Date()+Time();
end;
if ap.Serial=RNum then
break;
end;
RNum:=p.Next;
Result:=p.Index;
end;
procedure TUDPServer.ReadStream(var Stream: TMemoryStream; ABinding:TIdSocketHandle);
var
count:Int64;
ack:TIdAck;
pList:TList<TPacket>;
buffer:TBytes;
packet:TPacket;
I,J,K:Int64;
T:Int64;
Amount:Integer;
Idxs,Temp:array of Int64;
nIdx,TempLen,pn:Integer;
IsFound:Boolean;
IPIdx:Integer;
IPNum:TIPNum;
begin
IPIdx:=FindIPNum(ABinding.PeerIP);
if IPIdx=-1 then Exit;
count:=RecvAck(ABinding);
pList:=TList<TPacket>.Create();
I:=0;
T:=0;
nIdx:=0;
SetLength(Idxs,MAX_AMOUNT);
SetLength(Temp,MAX_AMOUNT);
while I<count do
begin
Amount:=RecvAck(ABinding);
J:=0;
pn:=nIdx;
while nIdx<Amount do
begin
if T+J>=count then break;
Idxs[nIdx]:=T+J;
inc(nIdx);
inc(J);
end;
for J:=1 to Amount do
begin
try
buffer:=ReceiveBuffer(ABinding);
except
buffer:=nil;
end;
if (buffer=nil) or (Length(buffer)=0) then break;
packet:=BytesToPacket(buffer);
if Length(packet.data)=0 then break;
IsFound:=False;
K:=0;
while K<pList.Count do
begin
if pList[K].Index=packet.Index then
begin
IsFound:=True;
break;
end;
inc(K);
end;
if IsFound then
pList.Delete(K);
pList.Add(packet);
Finalize(buffer);
end;
pList.Sort(TComparer<TPacket>.Construct(function(const Left,Right:TPacket):Integer
begin
Result:=Left.Index-Right.Index;
end));
TempLen:=0;
for J:=0 to nIdx-1 do
begin
IsFound:=False;
for K:=I to pList.Count-1 do
if pList[K].Index=Idxs[J] then
begin
IsFound:=True;
break;
end;
if not IsFound then
begin
Temp[TempLen]:=Idxs[J];
inc(TempLen);
end;
end;
J:=I;
while J < pList.Count do
begin
if pList[J].Index <> J then
break;
inc(J);
end;
I:=J;
T:=T+Amount-pn;
Move(Temp[0],Idxs[0],sizeof(Int64)*TempLen);
nIdx:=TempLen;
if nIdx=0 then
SendAck(Int64(IA_OK),ABinding)
else
begin
SendAck(Int64(IA_FAIL),ABinding);
SendAck(Int64(nIdx),ABinding);
for J:=0 to nIdx-1 do
SendAck(Int64(Idxs[J]),ABinding);
end;
end;
Finalize(Idxs);
Finalize(Temp);
Stream:=PacketsToStream(pList);
Stream.Position:=0;
for I:=0 to pList.Count-1 do
if Length(pList[I].data)>0 then
begin
buffer:=pList[I].data;
Finalize(buffer);
end;
pList.Free;
end;
procedure TUDPServer.WriteStream(Stream: TMemoryStream; ABinding:TIdSocketHandle);
var
pList:TList<TPacket>;
packet:TPacket;
buffer:TBytes;
ack,ack2:TIdAck;
Amount:Integer;
I,T:Int64;
ldt,dt,ot:TDateTime;
J:Integer;
Idxs:array of Int64;
nIdx,pn:Integer;
ncount:Integer;
Index:Int64;
IsFound:Boolean;
IsExcept:Boolean;
m:Int64;
IPIdx:Integer;
begin
IPIdx:=FindIPNum(ABinding.PeerIP);
if IPIdx=-1 then Exit;
Stream.Position:=0;
pList:=StreamToPackets(Stream,ABinding.PeerIP);
SendAck(Int64(pList.Count),ABinding);
Amount:=Min(pList.Count,30);
I:=0;
T:=0;
SetLength(Idxs,MAX_AMOUNT);
nIdx:=0;
ncount:=0;
ldt:=-1;
while I<pList.Count do
begin
J:=0;
while nIdx<Amount do
begin
if T+J>=pList.Count then break;
Idxs[nIdx]:=T+J;
inc(nIdx);
inc(J);
end;
pn:=nIdx;
SendAck(Int64(nIdx),ABinding);
ot:=Date()+Time();
for J:=0 to nIdx-1 do
begin
buffer:=PacketToBytes( pList[Idxs[J]] );
SendBuffer(ABinding.PeerIP,ABinding.PeerPort,TIdBytes(buffer));
Finalize(buffer);
end;
ack:=TIdAck(RecvAck(ABinding,10000));
dt:=Date()+Time()-ot;
T:=T+nIdx-ncount;
if ack=IA_FAIL then
begin
nIdx:=RecvAck(ABinding);
for J:=0 to nIdx-1 do
Idxs[J]:=RecvAck(ABinding);
ncount:=nIdx;
end
else
begin
ncount:=0;
nIdx:=0;
end;
I:=I+pn-nIdx;
if ldt<>-1 then
begin
if SecondsBetween(ot,ldt)>SecondsBetween(ot,dt) then
Amount:=Min(Amount+10,MAX_AMOUNT);
if SecondsBetween(ot,ldt)<SecondsBetween(ot,dt) then
Amount:=Max(Amount-10,Max(nIdx,30));
end;
ldt:=dt;
end;
Finalize(Idxs);
for I:=0 to pList.Count-1 do
if Length(pList[I].data)>0 then
begin
buffer:=pList[I].data;
Finalize(buffer);
end;
pList.Free;
end;
procedure TUDPServer.SendAck(Ack: Int64; ABinding: TIdSocketHandle);
var
p:TPacket;
p2:TPacket;
IPIdx:Integer;
o,t:TDateTime;
buffer:TBytes;
begin
IPIdx:=FindIPNum(ABinding.PeerIP);
if IPIdx=-1 then Exit;
p.Serial:=IPNumList[IPIdx].SNum;
p.Next:=Rand();
SetSNum(p.Next,IPIdx);
p.Index:=Ack;
p2.Index:=Int64(IA_FAIL);
p2.Serial:=IPNumList[IPIdx].RNum;
o:=Date()+Time();
while not ((p2.Serial=IPNumList[IPIdx].RNum) and (p2.Index=Int64(IA_OK))) do
begin
if p2.Serial=IPNumList[IPIdx].RNum then
SendBuffer(ABinding.PeerIP,ABinding.PeerPort,TIdBytes(PacketToBytes(p)));
try
try
buffer:=ReceiveBuffer(ABinding);
if Length(buffer)>24 then continue;
p2:=BytesToPacket(buffer);
except
end;
finally
Finalize(buffer);
t:=Date()+Time();
if SecondsBetween(o,t)>=5 then
begin
SetSNum(p.Serial,IPIdx);
raise EReadTimeout.Create('Read Timeout');
end;
end;
end;
SendBuffer(ABinding.PeerIP,ABinding.PeerPort,TIdBytes(PacketToBytes(p)));
SetRNum(p2.Next,IPIdx);
end;
function TUDPServer.RecvAck(ABinding: TIdSocketHandle; Timeout:Int64): Int64;
var
p,p2,ap:TPacket;
buffer:TBytes;
IPIdx:Integer;
o,t:TDateTime;
begin
IPIdx:=FindIPNum(ABinding.PeerIP);
if IPIdx=-1 then Exit;
if Timeout=-1 then
Timeout:=5000;
p.Serial:=IPNumList[IPIdx].RNum+1;
o := Date() + Time();
while p.Serial<>IPNumList[IPIdx].RNum do
begin
try
try
buffer:=ReceiveBuffer(ABinding);
if Length(buffer)>24 then continue;
p := BytesToPacket(buffer);
except
end;
finally
T := Date() + Time();
if SecondsBetween(o,T) >= Timeout / 1000 then
raise EReadTimeout.Create('Read Timeout');
end;
end;
p2.Serial:=IPNumList[IPIdx].SNum;
p2.Next:=Rand();
SetSNum(p2.Next,IPIdx);
p2.Index:=Int64(IA_OK);
o:=Date()+Time();
t:=o;
ap.Serial:=IPNumList[IPIdx].RNum+1;
while True do
begin
if SecondsBetween(o,t)>=2 then break;
SendBuffer(ABinding.PeerIP,ABinding.PeerPort,TIdBytes(PacketToBytes(p2)));
try
try
buffer:=ReceiveBuffer(ABinding);
ap:=BytesToPacket(buffer);
except
continue;
end;
finally
t:=Date()+Time();
end;
if ap.Serial=IPNumList[IPIdx].RNum then
break;
end;
SetRNum(p.Next,IPIdx);
Result:=p.Index;
end;
您认为问题出在哪里? 没有连接问题。但是下载文件通过网络连接时,有时会引发EReadTimeout异常。
环境
OS:Windows 10
编译器:Delphi 10东京
AP:TP-LINK TL-WR940N PLUS
通过我的笔记本电脑,PC,Android手机进行了测试
我的笔记本电脑<->我的笔记本电脑=可用
我的笔记本电脑<->我的电脑=无效
我的笔记本电脑<->我的手机(在AP中)=不起作用
我的笔记本电脑<->我的手机(在笔记本电脑的热点中)=可用