文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

DELPHI中完成端口(IOCP)的简单

2023-01-31 07: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发送数据的方法。
同时祝大家新年快乐!

 

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 资料下载
  • 历年真题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯