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)的更多相关文章

  1. DELPHI中完成端口(IOCP)的简单分析(4)

    DELPHI中完成端口(IOCP)的简单分析(4)   在我以前写的文章中,一直说的是如何接收数据.但是对于如何发送数据却一点也没有提到.因为从代码量上来说接收的代码要比发送多很多.今天我就来写一下如 ...

  2. DELPHI中完成端口(IOCP)的简单分析(2)

    DELPHI中完成端口(IOCP)的简单分析(2)   今天我写一下关于DELPHI编写完成端口(IOCP)的工作者线程中的东西.希望各位能提出批评意见.上次我写了关于常见IOCP的代码,对于IOCP ...

  3. DELPHI中完成端口(IOCP)的简单分析(1)

    DELPHI中完成端口(IOCP)的简单分析(1)   用DELPHI开发网络代码已经有一段时间了! 我发现在网上用VC来实现完成端口(IOCP)的代码很多,但是使用DELPHI来实现的就比较少了.对 ...

  4. java 中 “文件” 和 “流” 的简单分析

    java 中 FIle 和 流的简单分析 File类 简单File 常用方法 创建一个File 对象,检验文件是否存在,若不存在就创建,然后对File的类的这部分操作进行演示,如文件的名称.大小等 / ...

  5. rocketmq中的NettyRemotingClient类的简单分析

    rocketmq中的NettyRemotingClient类的简单分析 Bootstrap handler = this.bootstrap.group(this.eventLoopGroupWork ...

  6. Delphi中Android运行和JNI交互分析

    Androidapi.JNIBridge负责和JNI交互.,既然要交互,那么首先就是需要获得JNI的运行环境,Android本身内置的就有一个Java(Dalvik)虚拟机.所以这个第一步就肯定是要这 ...

  7. 关于Delphi中多线程传递参数的简单问题

    http://bbs.csdn.net/topics/390513469/ unit uThread; interface uses Classes; type Th = class(TThread) ...

  8. 不用注册热键方式在Delphi中实现定义快捷键(又简单又巧妙,但要当前窗体处在激活状态)

    第一步:在要实现快捷键的窗体中更改属性“KeyPreview”为True:第二步:在要实现快捷键的窗体中的OnKeyPress事件中填入一个过程名称(在Object Inspector中),填写好后回 ...

  9. jQuery中样式和属性模块简单分析

    1.行内样式操作 目标:扩展框架实现行内样式的增删改查 1.1 创建 css 方法 目标:实现单个样式或者多个样式的操作 1.1.1 css方法 -获取样式 注意:使用 style 属性只能获取行内样 ...

随机推荐

  1. Swift实时画箭头的实现

    iOS上实现画箭头,如果是指定了坐标点,那是很简单的,但如果需要做到实时绘制,就需要计算一下了 需求: 在白板上,根据手势落下点和移动点,实时绘制一条箭头直线(如下图) 实现代码: /// 获取箭头的 ...

  2. 关于docker 意外停止,重新快速启动措施

    1. 我们要重启这个镜像,需要知道这个镜像ID,类似这个: 7079ff99e10ac326726a364348853c0e508cad8ce00ae970f3c800f172a40252 那么你可以 ...

  3. shell脚本:Kill掉MySQL中所有sleep的client线程

    分享一个shell脚本,实现kill掉mysql中所有的sleep状态的client线程,有需要的朋友,可以参考研究下. 文件名称:killsleep.sh. #It is used to kill ...

  4. php中urlencode与rawurlencode的区别

    前段时间说自己遇到了个<URL加号引发错误>的BUG,引起这个bug的原因就是自己在URL中使用了 urlencode 函数,该函数会把空格转换成加号,这样就导致URL解析出错,而空格只有 ...

  5. Asp.Net WebApi接口返回值IHttpActionResult

    WebApi是微软在VS2012 MVC4版本中绑定发行的,webapi2.0同mvc5发行的 webapi一共有以下接口返回值 1.void无返回值2.IHttpActionResult Json( ...

  6. 类似于c语言读取文件进行解析

    $log_file_name = 'D:/static/develop/kuai_zhi/acagrid.com/public/Logs/'.date('Ym').'/'.date('d').'_er ...

  7. 一款可视化的在线制作H5

    一款可视化的在线制作H5 官方网站: http://www.iii66.cn 制作H5网址: http://www.iii66.cn/love/page/index 包括对图片,文字,图形,视频,声音 ...

  8. assets下的文件复制到SD卡

    由于assets和res下的文件都只可以读不可以写,那么在程序初始化后,将后期需要使用并且需要修改的文件复制到SD卡.下面代码提供一个工具类,将assets下的任意资源复制到SD卡下. assets下 ...

  9. Spark学习之常用算子介绍

    1. reduceByKey reduceByKey的作用对像是(key, value)形式的rdd,而reduce有减少.压缩之意,reduceByKey的作用就是对相同key的数据进行处理,最终每 ...

  10. 我的C语言编程风格

    前几天看别人的代码,真是的看的头昏脑涨,基本没有注释.乱起的变量名字,还要费尽心思去解读作者的意思.突然感觉高效的程序注释说明和良好的编程风格是多么的重要. 为了不让别人在看到我的代码时在背后骂我,也 ...