DELPHI中完成端口(IOCP)的簡單分析(3)

 

最近太忙,所以沒有機會來寫IOCP的後續文章。今天好不容易有了時間來寫IOCP的粘包處理問題。
TCP數據粘包的產生原因在於TCP是一種流協議。在以太網中一個TCP的數據包長度是1500位。其中20位的IP包頭,20位的TCP包頭,其餘的1460都是我們可以發送的數據。在數據發送的時候,我們發送的數據長度有可能比1460短,這樣在TCP來說它還是以一個數據包來發送。從而降低了網絡的利用率。所以TCP在發送數據包的時候,會將下一個數據包和這個數據包合在一起發送以增加網絡利用率(雖然SOCKET 中可以強制關閉這種合併發送,但是我不建議使用)。這樣以來,在我們接受到一個數據包以後,就會發現在這個數據包中含有其它的數據包,從而很難處理。
處理粘包現象有多種方法。我的方法是在每發送一個數據的前面加入這次發送的數據長度(4位)。以char的方式加入。這樣以來我們的數據包結構就變成了:
數據包長度(4位)+實際數據。
在接收到數據包以後,我們首先得到數據包的長度,然後根據這個數據包長度來得到實際的數據。
以下是我的粘包處理函數實現(這個函數是對於多個套接字來處理的所以在這裏我使用了TList鏈表):
 
 
//用於處理粘包的數據結構
  tagPacket = record
    Socket:TSocket;                                 //處理粘包的套接字
    hThread:THANDLE;                          //線程句柄
    ThreadID:DWORD;                           //線程ID
    DataBuf:array[0..DATA_BUFSIZE-1] of char;           //處理粘包的包
    DataLen:Integer;                                           //處理粘包的包長度
  end;
  TDealPacket = tagPacket;
  PDealPacket = ^tagPacket;
 
{粘包處理函數}
function TClientNet.ComminutePacket(SorucePacket:array of char;SPLen:Integer;var Destpacket:array of char;
                                    var DPLen:Integer;var SparePacket:array of char;
                                    var SpareLen:Integer;var IsEnd:Boolean;socket:Tsocket):Boolean;
const
  MaxPacket = 1024;
 PacketLength = 4;
var
  Temp:pchar;
  TempLen,PacketHeader:Integer;
  I,J:Integer;
  TempArray:array[0..MaxPacket-1] of char;
  TempCurr:Integer;
  CurrListI:Integer;
  SocketData:PDealPacket;
  t_Ord:Integer;
begin
  Result:=true;
  try
//首先根據套接字來得到上次遺留的數據
Fillchar(TempArray,sizeof(TempArray),#0);
    for I:=0 to DealDataList.Count-1 do
    begin
      SocketData:=DealDataList.Items[I];
      if SocketData.Socket = socket then
      begin
        strmove(TempArray,SocketData.DataBuf,sizeof(SocketData.DataBuf));
        TempCurr:=SocketData.DataLen;
        CurrListI:=I;
        break;
      end;
end;
 
//我們將每次處理粘包以後剩餘的數據保存在一個TDealPacket的鏈表中DealDataList。每次根據套接字先得到上次是否有剩餘的數據。如果有則將這個數據拷貝到一個臨時處理的緩存中。
 
    FillChar(Destpacket,sizeof(Destpacket),#0);
    FillChar(SparePacket,sizeof(SparePacket),#0);
IsEnd:=false;
 
{以下就是對數據包的整合,其算法很簡單,讀者可以參考我的註釋來理解}
 
    //對臨時緩存進行檢測
    if TempCurr<>0 then  //緩存中存在數據
    begin
      if TempCurr<PacketLength then //緩存中包含的數據包長度不足一個4位的數據包長度。
      begin
        TempLen:=PacketLength-TempCurr;
        if TempLen>SPLen then //數據包中含有的數量不足包頭數量
        begin
          strmove(TempArray+TempCurr,SorucePacket,SPLen);
          TempCurr:=TempCurr+SPLen;
          //分解完畢,
          IsEnd:=true;
        end
        else
        begin
          strmove(TempArray+TempCurr,SorucePacket,TempLen);
          TempCurr:=TempCurr+TempLen;
          GetMem(Temp,PacketLength+1);
          Fillchar(Temp^,PacketLength+1,#0);
          strmove(Temp,TempArray,PacketLength);
          //最近在檢查代碼的時候發現這裏轉換包頭長度的時候,只是使用異常來判斷是不合適的。所以這裏進行了修改 (2008年3月24日)
          {try
            PacketHeader:=StrToInt(StrPas(Temp));
          except
            Result:=false;
            exit;
          end;
          }
          for J := 1 to 4 do
          begin
            t_Ord:=Ord(StrPas(Temp)[J]);
            if (t_Ord<48) or (t_Ord>57) then
            begin
              Result := false;
              IsEnd := true;
              Exit;
            end;
          end;
          if PacketHeader>SPLen-TempLen then //此包是不全包
          begin
            strmove(TempArray+TempCurr,SorucePacket+TempLen,SPLen-TempLen);
            TempCurr:=TempCurr+SPLen-TempLen;
            //已經將數據拷貝完成
            IsEnd:=true;
          end
          else                         //此包是過包
          begin
            strmove(TempArray+TempCurr,SorucePacket+TempLen,PacketHeader);
            strmove(Destpacket,TempArray,PacketHeader+PacketLength);
            DPLen:=PacketHeader+PacketLength;
            Strmove(SparePacket,SorucePacket+TempLen+PacketHeader,SPLen-(TempLen+PacketHeader));
            SpareLen:=SPLen-(TempLen+PacketHeader);
            FillChar(TempArray,sizeof(TempArray),#0);
            TempCurr:=0;
            IsEnd:=false;
          end;
          FreeMem(Temp);
        end;
      end
      else                    //緩存中已經含有數據頭
      begin
        GetMem(Temp,PacketLength+1);
        Fillchar(Temp^,PacketLength+1,#0);
        strmove(Temp,TempArray,PacketLength);
        //最近在檢查代碼的時候發現這裏轉換包頭長度的時候,只是使用異常來判斷是不合適的。所以這裏進行了修改 (2008年3月24日)
        {try
          PacketHeader:=StrToInt(StrPas(Temp));
        except
          Result:=false;
          exit;
        end;
        }
        for J := 1 to 4 do
          begin
            t_Ord:=Ord(StrPas(Temp)[J]);
            if (t_Ord<48) or (t_Ord>57) then
            begin
              Result := false;
              IsEnd := true;
              Exit;
            end;
          end;
        if PacketHeader>TempCurr-PacketLength then //數據包包頭
        begin
          TempLen:=(PacketHeader+PacketLength)-TempCurr;
          if TempLen>SPLen then
          begin
            strmove(TempArray+TempCurr,SorucePacket,SPLen);
            TempCurr:=TempCurr+SPLen;
            IsEnd:=true;
          end
          else
          begin
            strmove(TempArray+TempCurr,SorucePacket,TempLen);
            strmove(Destpacket,TempArray,PacketHeader+PacketLength);
            DPLen:=PacketHeader+PacketLength;
            Strmove(SparePacket,SorucePacket+TempLen,SPLen-TempLen);
            SpareLen:=SPLen-TempLen;
            TempCurr:=0;
            FillChar(TempArray,sizeof(TempArray),#0);
            IsEnd:=false;
          end;
        end
        else
        begin
          strmove(TempArray+TempCurr,SorucePacket,TempLen+PacketLength);
          strmove(Destpacket,TempArray,TempCurr+TempLen+PacketLength);
          DPLen:=TempCurr+TempLen+PacketLength;
          Strmove(SparePacket,SorucePacket+TempLen+PacketLength,SPLen-TempLen);
          SpareLen:=SPLen-TempLen-PacketLength;
          TempCurr:=0;
          FillChar(TempArray,sizeof(TempArray),#0);
          IsEnd:=false;
        end;
        FreeMem(Temp);
      end;
    end
    else                      //緩存中不存在數據
    begin
      Fillchar(TempArray,sizeof(TempArray),#0);
      if SPLen>=PacketLength then
      begin
        strmove(TempArray,SorucePacket,PacketLength);
        GetMem(Temp,PacketLength+1);
        Fillchar(Temp^,PacketLength+1,#0);
        strmove(Temp,TempArray,PacketLength);
        //最近在檢查代碼的時候發現這裏轉換包頭長度的時候,只是使用異常來判斷是不合適的。所以這裏進行了修改 (2008年3月24日)
        {try
          PacketHeader:=StrToInt(StrPas(Temp));
        except
          Result:=false;
          exit;
        end;}
        for J := 1 to 4 do
          begin
            t_Ord:=Ord(StrPas(Temp)[J]);
            if (t_Ord<48) or (t_Ord>57) then
            begin
              Result := false;
              IsEnd := true;
              Exit;
            end;
          end;
 
        if PacketHeader>SPLen-PacketLength then
        begin
          strmove(TempArray+PacketLength,SorucePacket+PacketLength,SPLen-PacketLength);
          TempCurr:=SPLen;
          IsEnd:=true;
        end
        else
        begin
          strmove(TempArray+PacketLength,SorucePacket+PacketLength,PacketHeader);
          strmove(Destpacket,TempArray,PacketHeader+PacketLength);
          DPLen:=PacketHeader+PacketLength;
          Strmove(SparePacket,SorucePacket+PacketHeader+PacketLength,SPLen-(PacketHeader+PacketLength));
          SpareLen:=SPLen-(PacketHeader+PacketLength);
          TempCurr:=0;
          FillChar(TempArray,sizeof(TempArray),#0);
          IsEnd:=false;
        end;
        FreeMem(Temp);
      end
      else
      begin
        strmove(TempArray,SorucePacket,SPLen);
        TempCurr:=SPLen;
        IsEnd:=true;
      end;
    end;
    //恢復數據
    SocketData.DataLen:=TempCurr;
    Fillchar(SocketData.DataBuf,sizeof(SocketData.DataBuf),#0);
    strmove(SocketData.DataBuf,TempArray,TempCurr);
  except
    Result:=false;
  end;
end;
 
上面的函數就是對TCP協議中粘包的處理DLEPHI代碼,對於UDP數據來說是不存在粘包現象的。
我寫的IOCP的代碼已經在我編寫的網絡遊戲中使用,運行穩定。
下次我會講使用IOCP發送數據的方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章