
DELPHI中完成端口(IOCP)的简单分析(3)
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发送数据的方法。 同时祝大家新年快乐
转载于:.html