1. 程式人生 > >DELPHI中完成端口(IOCP)的簡單分析(3)

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

修改 false lean content cli 容易 header -c 端口

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

fxh7622關註4人評論7366人閱讀2007-01-17 11:18:24 最近太忙,所以沒有機會來寫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發送數據的方法。 同時祝大家新年快樂

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