https://blog.51cto.com/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 发送数据的方法.
同时祝大家新年快乐
来源: http://www.bubuko.com/infodetail-2982365.html