Delphi-udp在本地工作,但不能在网络上工作

时间:2018-10-02 16:46:41

标签: delphi

我用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中)=不起作用

我的笔记本电脑<->我的手机(在笔记本电脑的热点中)=可用

0 个答案:

没有答案