请问各位:如何处理ClientSocket丢包问题啊? 
就是,当我发送比接收快的时候,ClientSocket就会丢包 
正常情况下,应该是我发送数等于接收数的。可是当发送过快,接收就会少啊,如何处理这种情况? 
谢谢! 

解决方案 »

  1.   

    我用的是NonBlocking模试啊,它应该是一直在执行OnRead事件吧?
      

  2.   

    只能 試下 Blocking 這種模式下的傳送了
      

  3.   

    参考
    http://expert.csdn.net/Expert/topic/2860/2860889.xml?temp=4.603213E-02
    我的回答
      

  4.   

    并参考
    http://expert.csdn.net/Expert/topic/2873/2873751.xml?temp=.2635919
    我的回答
      

  5.   

    postren(小虫) :我用了你的例子 ,可是怎么,它会把我的Socket断掉?
    我的代码是这样的:
    procedure TFrmShow.CSocketRead(Sender: TObject; Socket: TCustomWinSocket);
    var CMPP_HEAD_Tag: CMPP_HEAD;
        Len, L,ReadBufSize: integer;
        Buf:array [0..1024*8-1] of char;
    begin
      try
        Len := 0;
        repeat
          L := Socket.ReceiveBuf(Buf[Len], SizeOf(LongWord) - Len);
          if L > 0 then
          begin
            Inc(Len, L);
          end;
          if L < 0 then
            Exit; 
        until (Len = SizeOf(LongWord));    CopyMemory(@ReadBufSize, @Buf[0], SizeOf(LongWord));
        repeat               
          L := Socket.ReceiveBuf(Buf[Len], ReadBufSize - Len);
          if L > 0 then
          begin
            Inc(Len, L);
          end;
          if L < 0 then
            Exit; 
        until (Len = ReadBufSize);
        CopyMemory(@CMPP_HEAD_Tag, @Buf, SizeOf(CMPP_HEAD));      CMPP_HEAD_Tag.rCommand_ID:=Ntohl(CMPP_HEAD_Tag.rCommand_ID);      if CMPP_HEAD_Tag.rCommand_ID=CMPP_Connect_REP_Command then   
          begin
            API_CMPP_Connect_REP(Socket,CMPP_HEAD_Tag);
          end;      if CMPP_HEAD_Tag.rCommand_ID=CMPP_Active_Test_REP_Command then   
          begin
            API_Active_Test_REP(Socket);
          end;      if CMPP_HEAD_Tag.rCommand_ID=CMPP_Deliver_Command then   
          begin
            IsDeliver:=True;
            API_CMPP_Deliver(Socket,InSocket,RepSocket,CMPP_HEAD_Tag);
            IsDeliver:=False;
          end;      if CMPP_HEAD_Tag.rCommand_ID=CMPP_Terminate_REP_Command then   
          begin
            API_CMPP_Terminate_REP(Socket);
            CloseSocket;
          end;      if CMPP_HEAD_Tag.rCommand_ID=CMPP_Submit_REP_Command then  
          begin
            API_CMPP_Submit_REP(Socket,RepSocket,CMPP_HEAD_Tag);
          end;
     except
       Exit;
     end;
    end;
      

  6.   

    看楼主的代码应该是CMPP协议, CMPP协议中的包头中的长度为4个字节, 但并不是直接读出来就是LongWord类型的
    比如LongWord类型的$01020304, 在CMPP协议中是这样表示的$01020304, 但在内存中的LongWord为$04030201, 也就是说, 假如CMPP头中的包长度为16(比如链路测试包), 它是这样表示的:
    00 00 00 10   00 00 00 08   00 00 00 00   00 00 00 01而按上面我的代码读到的前4个字节是这样的 10 00 00 00
    也就是需要高低字节的翻转ReadBufSize
    试试看吧
      

  7.   

    啊,不会吧是这样子啊,好像没听过哦,可是我没有翻转也能收到包(不是用你的方式去收)?只是会丢包而已啊?请问 postren(小虫),你也有实现过CMPP协议吗?
      

  8.   

    直接连移动的ISMG网关, 肯定得翻转的, 除非你没有用我的方式直接读取一个LongWord的4个字节, 或许你的方法中本身就对起进行了翻转, 呵呵
      

  9.   

    啊?那你是不是也用ClientSocket;像Deliver包之类的接收是怎么处理的啊?
    也是在主线程里用OnRead事件?
    能否指教一下?
      

  10.   

    kmzym() :能说清楚点吗?
    这问题一直困扰着我,好郁闷啊?
      

  11.   

    soft_fly(soft_fly) ( )
    tcp/ip中流式套接字的机制能保证数据完全到达目标机器,如果有丢包的现象,它的底层协议会自动要求重发,socket不管是用阻塞还是非阻塞方式都不会出现丢包的。你数据不能接收完全,可能在发送或接收时处理有问题,没有看到你的代码,我也不能断定问题在那里。
      

  12.   

    kmzym() :我知道啊,现在就是我接收的有问题了,好像是慢了一样,一组完成的数据,老是最后发过的那个包会少?
    你有实现过CMPP的协议吗?
      

  13.   

    没有实现过CMPP,你上面的代码我看了,在非阻塞的模式下不要循环接收和发送数据。有数据到来的时候,windows会发消息通知ClientSocket,可以在OnRead事件中处理数据,用循环方法可能收不全,因为有可能数据还没有到达,而你已经跳出接收循环了。
      

  14.   

    上面那个是别人跟我说了,我改了,下面这才是我自己写的,你看一下:
    procedure TFrmShow.CSocketRead(Sender: TObject; Socket: TCustomWinSocket);
    var CMPP_HEAD_Tag: CMPP_HEAD;
        ReadBufSize,HeadSize: integer;
    begin
      try
        ReadBufSize:=SizeOf( CMPP_HEAD );
        FillChar( CMPP_HEAD_tag, ReadBufSize, 0 );
        HeadSize:=Socket.ReceiveBuf( CMPP_HEAD_tag,ReadBufSize );
        if HeadSize=-1 then
          exit;
       if  ReadBufSize=HeadSize then
       begin
          CMPP_HEAD_Tag.rCommand_ID:=Ntohl(CMPP_HEAD_Tag.rCommand_ID);      if CMPP_HEAD_Tag.rCommand_ID=CMPP_Connect_REP_Command then   
          begin
            API_CMPP_Connect_REP(Socket,CMPP_HEAD_Tag);
          end;      if CMPP_HEAD_Tag.rCommand_ID=CMPP_Active_Test_REP_Command then  
          begin
            API_Active_Test_REP(Socket);
          end;      if CMPP_HEAD_Tag.rCommand_ID=CMPP_Deliver_Command then   
          begin
            IsDeliver:=True;
            API_CMPP_Deliver(Socket,InSocket,RepSocket,CMPP_HEAD_Tag);
            IsDeliver:=False;
          end;      if CMPP_HEAD_Tag.rCommand_ID=CMPP_Terminate_REP_Command then   
          begin
            API_CMPP_Terminate_REP(Socket);
            CloseSocket;
          end;      if CMPP_HEAD_Tag.rCommand_ID=CMPP_Submit_REP_Command then   
          begin
            API_CMPP_Submit_REP(Socket,RepSocket,CMPP_HEAD_Tag);
          end;
        end;
     except
       Exit;
     end;
    end;
      

  15.   

    上面的代码,数据如果是一次到达那么没有问题,如果时多次到达就有问题了,可以把接收Buffer定义为一个全局变量,如果接收到的数据大小等于ReadBufSize,那么进行数据处理,如果数据小于ReadBufSize,那么等等待下一OnRead事件继续接收数据,把接收到的数据加到Buffer中去,直到数据接收完全再处理数据。
      

  16.   

    var
    Buffer: array[0..2047] of char;
    Received: Integer;
    ...
    Received := 0;
    ...
    procedure TFrmShow.CSocketRead(Sender: TObject; Socket: TCustomWinSocket);
    ...
    begin
    ...
      Received := Received + Socket.ReceiveBuf(Buffer[Received],ReadBufSize-Received);
      if Received = ReadBufSize then
      begin
        Move(Buffer[0],CMPP_HEAD_tag,SizeOf(CMPP_HEAD));
        Received := 0;
       // handle data
       ...
       ...
      end;
    end; 
      

  17.   

    kmzym() :很奇怪,第一 次可以收到,以后就收不到了,怎么回事啊?
      

  18.   

    上面简单的例子,是按照你原来程序的功能,只能接受SizeOf(CMPP_HEAD))大小的数据写的,如果还有其它数据类型,按其思路修改。
      

  19.   

    我就是只要接SizeOf(CMPP_HEAD))的数据啊,应该可以吧,我是分两部去收取数据的啊?
    我用这种方法,把服务端与客户端放在同一台机子上,是没问题,可是一分手就会丢啊?
      

  20.   

    如果每次接收数据都是CMPP_HEAD类型,按我提供的方法接收肯定没问题,不管是同一台机器还是不同机器。自己再仔细检查看看。
      

  21.   

    我的意思是我只单单先收CMPP_HEAD为这个包啊,我的程序也有贴出来啦
    这样子,不行吗?
    它需要一次性把发过来的包内容全部收完吗?
      

  22.   

    有一个很奇怪的事,我的CMPP_HEAD定义是这样子:
    type
      CMPP_Head=packed record
        rTotal_Length  :LongWord;
        rCommand_ID    :LongWord;
        rSequence_ID   :LongWord;
      end;
    我在第上次收取的时候是正确的,接下来,rCommand_ID  所收取到的数据会少了一个字节?
    为什么会这样啊?
      

  23.   

    不是很明白你的意思,如果发送方只是发送CMPP_HEAD类型的记录,那么用我提供的方法没有问题,而你原来的方法如果数据不是一次性到达目标机器,那么就会出现收不全的问题,而流式套接字数据分批到达目标机器的现象很常见的。如果发送的数据不只是CMPP_HEAD类型的记录或CMPP_HEAD本身是变体记录类型,那么用上述的接收方法都会有问题的。
      

  24.   

    我的发送程序是没有问题的,因为,我在另外一部分可以收到(发送程序比较多,没办法贴)
    我发送的是CMPP_HEAD(头)+CMPP_BODY(体)
    但,按常理来说,我先收头,再去收体应该也是没问题的吧?
      

  25.   

    CMPP_BODY是不是固定长度,如果是稍微改一下问题就能解决了。
      

  26.   

    rTotal_Length  是不是记录CMPP_BODY长度的?
      

  27.   

    对啊,rTotal_Length  是记录CMPP_BODY的长度,但CMPP_BODY不固定的长度?
    是不是这个问题呢?
    怎么改啊
    请指教
    谢谢!
      

  28.   

    var
    Buffer: array[0..2047] of char;
    Received,ReadBufSize: Integer;  //ReadBufSize 设为全局变量
    bHeadPack: Boolean;             //head 和body的区分标志 
    ...
    Received := 0;
    bHeadPack := True
    ReadBufSize := SizeOf(CMPP_HEAD); 
    ...
    procedure TFrmShow.CSocketRead(Sender: TObject; Socket: TCustomWinSocket);
    var
    CMPP_BODY_tag: CMPP_BODY; 
    ...
    begin
    ...
      Received := Received + Socket.ReceiveBuf(Buffer[Received],ReadBufSize-Received);
      if bHeadPack then
      begin
        if Received = ReadBufSize then
        begin
          Move(Buffer[0],CMPP_HEAD_tag,SizeOf(CMPP_HEAD));
          Received := 0;
          bHeadPack := False; 
          ReadBufSize := CMPP_HEAD_tag.rTotal_Length; 
          // handle head data
          ...
          ...
        end 
      end
      else
      begin
        if Received = ReadBufSize then
        begin
          Move(Buffer[0],CMPP_BODY_tag,ReadBufSize);
          Received := 0;
          bHeadPack := True
          ReadBufSize := SizeOf(CMPP_HEAD); 
          
          // handle Body data
          ...
          ...
        end 
      end;
    end; 
      

  29.   

    我是先收头,然后判断rCommand_ID 是否等于相应的体的标志,然后,再去调用一个过程去收包体(CMPP_BODY).
    这样应该不成问题吧?
    跟你那样收法,差不多啊?
      

  30.   

    先用简单的数据测试,把不相关的代码去掉,把问题范围缩小。我从没有发现TCP有丢包的.只能告诉你这些了。问题很简单的,再仔细调试调试。实在搞不定再说。
      

  31.   

    有一个很奇怪的问题就是,像这样一句接收:
    HeadSize:=Socket.ReceiveBuf( CMPP_HEAD_tag,SizeOf( CMPP_HEAD ) );
    HeadSize有等于SizeOf( CMPP_HEAD ),可是CMPP_HEAD_tag的值是空的?
    这是怎么回事啊?
      

  32.   

    不是接收来不及,是发送来不及。windsock发送缓冲写满了,后面的数据就不能再往里面写了。socket.sendbuf返回值因该是失败的。
      

  33.   

    建议你在OnRead事件中启用处理线程,这样可以减少OnRead的处理时间,加快监听速度!
    还有,你的sendbuf和readbuf写得不规范,没有检测的说,这样,你怎么知道发送和接收的都是成功的呢?
      

  34.   

    还有,最好不要把Header和Body分开发送,担心ISMG接收有误!
      

  35.   

    我在接收和发送都有做判断啊?
    发送是同时的,只是接收的时候分开收而已,因为,为了好判断是哪类数据 cdmar79(陈)你也有实现过CMPP协议?
      

  36.   

    1。是的,楼主!
    2。我说的判断是指长度判断,不是你那个>0/<0,那没用的;sendbuf和readbuf的函数返回不就是长度值吗?你应该拿这个和你从Header中解析出来的消息长度进行比较才对啊!
    3。分开接收不赞成,因为你要让buf在那里停着的,建议先都收到一个string或stream里面,然后起线程进行解析,不就快了吗?你用几个case函数去处理,那socket不是要等到你CSocketRead结束吗?太慢了,后面你从ISMG接收的消息不就把socket挤满了吗?这样,可能就是丢包的原因所在把!
      

  37.   

    还有那个什么翻转不翻转的问题,应该用HostToXXX/XXXToHost来处理的,不好意思,忘了!你自己找吧!是“网络字节序<-->本机字节序”的问题!
    不过,还是要调试的时候看的,如果不翻是对的,就不翻;否则,翻!
      

  38.   

    啊,不会吧,要是我不分开来收,先收包头,我怎么通过Command_Id来判断是那类上行啊?
      

  39.   

    呵~~,关于“网络字节序<-->本机字节序”的转换,我知道了,我有做了
      

  40.   

    cdmar79(陈):你现在还在从事SP行业的工作吗?曾经也做过网关?
      

  41.   

    我们实现的方式不同,我们这里是建了协议栈的,所以没有“通过Command_Id来判断是那类上行”的问题。如果象你这样,可以先把消息收到一个stream里先,然后,取stream的sizeof(CMPP_Header),取来的东西马上解析出Command_Id,再调起一个相应的线程来继续处理stream,不要写case函数去解析,会慢的,特别是这种流式的格式解析;线程起来之后,OnRead事件就释放了,这样快许多的;而且,如果是使用长模式的话,应该是这样做的,协议已经规定了16个(8个?)的缓冲了;要么就是短模式,也就相当于长模式1个缓冲的情况!你自己看着办吧!
    还有,就是没有办法的办法,把时间调宽了(你也是这样做的)!如果你对性能没什么太苛刻的要求的话,可以这么做的!!
      

  42.   

    kmzym() 说得对的,流式套接字不可能丢包。
    没有细看你的代码,
    建议你在代码里面添加发送日志之类的代码。实现一个记录日志的函数,比如叫WriteBinLog(const buf,buflen Integer);
    在每次ReceiveBuf或SendBuf之后,立即调用它,将实际收到的数据记入相应文件。
    这样可以准确的判断出是哪端程序出了问题。
    在程序发布后,这样的日志记录功能也是很有用。
      

  43.   

    cdmar79(陈):想问一下,你和这种做法是用阻塞模式还是非阻塞模式?
    ClientSocket的非阻塞模式不太适合用于线程处理吧?
      

  44.   

    还有就是我把计数器写在OnRead事件里,它所得到的数字也跟我发送的不一样,而跟我放在接收包体处理程序的地方一样的,这样,应该跟我后面的处理程序没关系吧?
    而是OnRead事件这边,本身就没有收到全部的数据?
      

  45.   

    //啊,不会吧,那我把发送的时间放长了,怎么没问题呢?
    //这样说,我应该是要把发送的时间设短了?
    //应该不是吧,据我所测,不是这样的哦?
    //能否,说明白点?
    //谢谢!windows处理通讯这一类I/O操作是要花时间的,由于是异步操作,所以你感觉不到。如果winsock发送缓冲区写满了,那么调用send就会返回错误WSAEWOULDBLOCK。
    简单的办法是把时间间隔方长,让windows有足够的时间去发送缓冲区中的数据。更准确的办法是当返回WSAEWOULDBLOCK错误的时候停止发送,windows将缓冲区中的数据发送完,空出缓存,会发消息通知Socket触发OnWrite事件,这时候再继续发送。
      

  46.   

    to soft_fly(soft_fly):
    给个不太适合用线程的理由?你说的计数器是指消息ID的记数器吗?那放在那里都一样的,只要你能在要用的时候能得到就可以了。按你的程序,OnRead事件当然有可能“没有收到全部的数据”。但是我想你在收到一条msg的时候应该是完整的;否则,就是流套接口有问题,但是我同意 kmzym() 的观点,流套接口不可能有错的。那么,只可能是你接收到的msg少了,而不是tcp包少了,少的原因,我认为是你OnRead处理太慢了。你OnRead里的“API_CMPP_Deliver(Socket,InSocket,RepSocket,CMPP_HEAD_Tag)”之类的函数是不是还要去做数据库操作的啊!如果是的话,那要跑多少ms才结束啊,100,200?那你自己想想好了,等这类函数结束了,再返回到OnRead中,OnRead才能结束啊,那你的socket的接收区不是要挤满了吗?后面到的msg当然是没办法挤进去的了。
    我还是一句话,你要想办法把OnRead的运行周期缩短,用线程是一个办法,其他的办法也有的,你要自己想了,否则,你的问题没办法解决。要么你干脆不要用长模式的,用短模式的好了,这样一来一去的,socket就不会挤满,绝对没问题了!
      

  47.   

    还有,我想 kmzym() 对你说的 “发送缓冲区写满了”应该是搞错了,我认为是你的接收缓冲区满了才对!