现象:
  发送文件,发送端循环读取一片组装成我自己定义的数据包(有包头,长度,压缩数据 等信息)发送出去。
  接收端在OnRead事件中读取。
  1.读取到大于一个我自己的数据包就去拆分数据包(可能接收到多个数据包粘连在一起的情况),可能是>=1个
  2.拆分出来m个数据包后循环解压数据。
  3.将解压出的真实数据传入一个外部回调函数,
  4.外部回调函数将数据写到文件里。-----
问题出现:
  发送端,我在循环中每个数据包里加了个序号:
  接收时,将序号显示出来时,是乱序的。比如:
------------------------
序号      本次数据包个数
1         4
2        4
5        1
6        1
3        4
4        4
------------------
数据包顺序不正确了。感觉是在接收到数据后,拆分、解压、响应回调相对比较慢,在此时又响应了一次OnRead事件,而在第一次OnRead事件中,收到了多个,还没来的急全部写到文件中,第二次只收到1个数据包,就写到文件中了。导致数据顺序不对。
我读了一下delphi的源码,使用的是WSAAsyncSelect(FSocket, Wnd, Msg, Longint(Byte(FAsyncStyles)))模式,也就是从Wnd窗口消息中获得有数据到来,而消息是异步的,相当于postmessage发出来的一样。
我之前的数据没处理完成,第二次消息又来了,第二次消息中收到的数据比第一次的少,先完成,导致数据顺序不正确。
不知道我的分析对不对?目前不知道如何解决这个问题,请大家多多帮助,指点,在此非常感谢!

解决方案 »

  1.   

    既然有粘包,那应该会有拆成半个的包吧,这个你有没有先缓存再重新组包?TCP是顺序发送的,要不你不要用
    TClientSocket和TServerSocket组件来写,自己直接用api来写,如果数据包快的话用iocp试试.
    数据收到先缓存一些数据,再一次性写.
      

  2.   

    半个包的情况我也做了处理的,我会把半包保存到一个地方,下次onread的时候,先将上次的半包数据放到接收缓冲区中,再继续接收。
    我现在很多程序中都用到了这个类, 并且不是服务器才有这个问题,只要发送方,循环发送,接收端有就有可能粘包,一粘包解析就就慢了,慢了就会再次接收到onread事件,顺序就乱了。
      

  3.   

    我把代码贴出来,大家帮我看看TADLTcpClient是继承TClientSocket的
    ---------------------------------------
    procedure TADLTcpClient.Read(Sender: TObject; Socket: TCustomWinSocket); //响应OnRead事件
    begin
      ReceviceCommand(Socket);
    end;
    ----------------------------------------
    procedure TADLTcpClient.ReceviceCommand(Socket: TCustomWinSocket);
    var
      NetCommand: TADLNetCommand;
      RecLen: Integer;
      i, PackageCount: Integer;
      pData: Pointer;
      NetCommandList:TList;
    begin  NetCommand := nil; //FNetInOutPackage类是用来专门接收、发送、加压、解压数据的,传了socket进去,服务器端和客户端都用这个类。
      RecLen := FNetInOutPackage.ReciveNetPackage(Socket);
     
      if RecLen <= 0 then
        Exit;
      //定义了一个指针,用来保存半截包
      pData := nil;
      //折分数据包,得到包的个数,如果有半包,返回pData不为nil
      PackageCount := FNetInOutPackage.GetNetPackageCount(pData);
      //将半截包,保存到Socket.Data 指针中,
      Socket.Data := pData;  //NetCommandList 用来保存解压还原后的数据数据,每一个item是一个类(我发送过来的数据会还原成一个类)
      NetCommandList := TList.Create;
      //循环将PackageCount个包,解压,还原
      for i := 0 to PackageCount - 1 do
      begin
        NetCommand := FNetInOutPackage.OutOfPackage(i);  // 这里解压、还原成一个类对象
        //还原出错,或解压出错为nil,
        if NetCommand = nil then
        begin
          Socket.Close;
          NetCommandList.Free;
          Exit;
        end;
        NetCommandList.Add(NetCommand);//将返回的对象指针放到list, 还原完了再同时响应外部回调函数
      end;  //这里清空FNetInOutPackage内部的拆分数据包链表。上面的循环就是循环的这个链表中的数据   
      FNetInOutPackage.ClearPackageList;
      
      //下面响应回调函数
      if Assigned(FOnReceviceCommand) then
      begin
        for i := 0 to NetCommandList.Count - 1 do
        begin
          NetCommand := TADLNetCommand(NetCommandList.Items[i]);
          NetCommand.Tag := NetCommandList.Count;
          //if NetCommandList.Count > 1 then
          //  MessageBox(Application.Handle, pchar(inttostr(Integer(@Socket))),'',MB_OK);
          FOnReceviceCommand(Socket, FNetInOutPackage, NetCommand);
        end;
      end;
      NetCommandList.Free;
    end;
      

  4.   

    我的问题搞定了! 在调试过程中,我发现只要一进入回调函数就跳到FD_READ的消息处,百思不得其解。一但把回调函数出了就对了。在无意的跟踪过程中,我跟进了FOnReceviceCommand回调函数中, 里面有一句为了使窗口不卡住,写了一句Application.pocessmessage,一执行这句又跳到FD_READ消息。
    这下问题找到了....
    Application.pocessmessage中是执行的peekmessage,TranslateMessage,DispatchMessage 将消息队列中的消息又分派出来,导致响应网络接收消息。。使用数据顺序不正确!!