本帖最后由 groce 于 2010-01-11 17:01:45 编辑

解决方案 »

  1.   

    TCP不应该丢失数据包的,可能的原因就是服务器一个包一个包的分包发送数据,可能产生一个结果,就是数据包在传输过程中到达的次序出了问题,比如次序1,2,3,4可能是 1,3,2,4.还是一问一答比较保险点。
      

  2.   

    你把socket设置成了非阻塞,你怎么判断socket数据发送成功还是不成功的?如果你在发送完数据后,得到发送成功的消息,然后再发送,应该就没问题。这个问题应该是你频繁给client或者server发送数据造成的。最好server用阻塞方式写,发送给client后,client回复个状态值,应该就没问题了
      

  3.   

    客户端是阻塞模式,WSAAsyncSelect 模式。主线程收到消息之后才会调用 recv 函数。我刚才更新了一楼主贴。另外,服务器用的是开源代码 Darwin 流媒体服务器,用别人的客户端播放没有问题,用QuickTime 播放也没有问题。服务器是不会有错误的。我的客户端在 RTSP 会话建立之后,只管接收数据,不会向服务器发数据。客户端的接收缓冲也是足够大的。
      

  4.   

    说错了,我用的是属于非阻塞模式。但是服务器发送数据一定是成功的。因为用别人写的播放器和 QuickTime 都没有问题。客户端是按:长度 + 数据 + 长度 + 数据……的结构体来发送数据的,因此即使服务器频繁给客户端发数据也没有关系,数据也不应该丢,当然也不存在粘包的问题。
      

  5.   

    晚上回来又测了一下,用同一套代码得到如下结果请求指点迷津,谢谢!采用 Debug 模式编译:
    服务器与客户端在同一台电脑运行,截获的数据有上述问题;服务器与客户端分别在两台电脑运行,截获的数据没有问题。采用 Release 模式编译:
    无论服务器与客户端在同一台电脑运行还是分别在两台电脑运行,截获的数据都有上述问题
      

  6.   

    建议不要用winsock那4种模式,就直接按linux传统socket标准方法:阻塞socket, select + recv, 非常稳定且通用
      

  7.   

    昨天跑两台电脑的时候是用虚拟机测的,服务器在虚拟机上,Debug 没问题,Release 有问题。今天在局域网内真实的两台电脑上测,Debug 和 Release 都没有问题。真是郁闷了。请问大家这到底是怎么回事啊?
      

  8.   

    TO 8 楼:
    我虽然是非阻塞模式,但是在程序中,如果 recv 取不到数据我会有 while 循环重新去调用 recv,直到超时或取够数据,因此也相当于是阻塞模式了。
      

  9.   

    首先TCP是流式传送的,绝对不会出现乱序问题。
    再有服务端发送数据时要先判断发送缓冲区是否满,如果满就等待有空余空间再发送。
        客户端接收数据时也要判断接收缓冲区是否有数据,如果有数据才接收。经过这样的逻辑可以保证数据可以正确接收。
      

  10.   

    其实我已经点到了,就是你的代码缺少缓冲区判断和针对缓冲区中是否存在数据所进行的处理的地方出现了问题。你可以贴出服务端发送的代码和客户端接收的代码。另外Debug和Release模式区别就在于Debug对变量自动初始化,而Release不初始化。
      

  11.   

    我主要是想利用 MFC 的消息机制,所以采用了 WSAAsyncSelect IO模型。服务器是用的开源代码,Darwin Streaming Sever。客户端接收代码如下,我有 while 循环,因此相当于是阻塞模式了。如果大家愿意不辞辛劳帮我分析代码,我可以把我整个客户端代码发上来(因为我知道帮别人看代码是很痛苦的),谢谢!int32 TCPSocket::recv_packet_by_length(int8 *pRecvBuff, uint32 u32TotalLen)
    {
    int32 s32Timer = 0;
    int32 s32Ret;
    uint32 u32LeftLen = u32TotalLen;
    int8 *p8Data = pRecvBuff;
    CString s; do 
    {
    s32Ret   = recv(sockServer, p8Data, u32LeftLen, 0);
    if (s32Ret > 0)
    {
    s32Timer   = 0;
    p8Data  += s32Ret;
    u32LeftLen  -= s32Ret;
    }
    else
    {
    s32Timer ++;
    Sleep(1000);
    } if (s32Timer == 10)
    {
    AfxMessageBox("no more data !"); return FAILED_StreamPlayer;
    }
    } while (u32LeftLen > 0); pRecvBuff[u32TotalLen]   = '\0';

    return u32TotalLen;
    }
      

  12.   

    补充说明一下:
    正如我前面提到的,服务器代码肯定没有问题的,不用考虑。缓冲区也是足够大的,否则也不可能出现正确接收的情况。Release 并不是在所有情况下都正确,在本地和用虚拟机的情况下是有错误的。
      

  13.   

    你这样写确实有问题。你的写法是同步模式,循环接收。
    异步模式是需要消息配合的,也就是说当消息通知有数据到达的时候,触发你的接收数据的函数OnReceive。
    假设当前你刚接收完自定义数据包的长度字段,那么接收的时候就是接收这个长度数据,而在实际情况中,不是一次就能接收完你想要的数据的,这时候会发现实际接收到的数据比你自定义数据包长度小,所以你应该先把接收到的数据放到自己的一个缓冲区中以备拼成完整的数据包。待下次socket接收缓冲区中再次到达数据时,再进行接收的处理,直到把完整的数据包拼成后,才真正进入你自定义的数据包处理程序。
      

  14.   

    TO 16 楼:
    1、异步模式是需要消息配合的,也就是说当消息通知有数据到达的时候,触发你的接收数据的函数OnReceive。
    ——我是通过 WSAAsyncSelect IO模型实现的。主程序收到网络消息后,才会调用我的接收函数。2、我的 recv_packet_by_length 实现的是根据包长度接收数据的功能。while 循环的目的就是为了保证调用一次 recv_packet_by_length 就收到我想要的包长度。输入参数 u32TotalLen 是我想要接收的包长度,而不是整个接收缓冲的长度。你所说的拼包的操作在 while 循环中完成。
      

  15.   

    我的 recv_packet_by_length 函数循环接收的当前需要接收的包而不是所有网络数据。如果接收完当前包就会返回。输入参数 pRecvBuff 是接收缓冲,u32TotalLen 是当前包的总长度。接收缓冲是足够大的。可能是我没有对参数进行说明,所以造成了你的误解。
      

  16.   

    while循环中,通过Sleep休眠当前线程,那怎么能保证下一次通知的到达呢?这和异步消息模式处理也是类似的。
      

  17.   

    Sleep休眠只是为了等待服务器数据到达。如果Sleep之后,数据到达了,再次调用 recv 就能收到数据了。下次数据再到达,主程序又会收到消息,然后再调用接收函数。
      

  18.   

    WSAAsyncSelect 模型是我学SOCKET的时候用到的第一个IO模型,经验谈不上,多少有点感叹
    我觉得哈,你是因为没有等待客户端收到后就发第二包数据了,虽然用的是TCP协议,是流传输,不承在顺序问题,但问题是客户端,由于是用的是WSAAsyncSelect模型,他只会向WINDOWS窗口发送一个数据到达的消息,不会去真正执行接收过程,也对该消息是否能得到执行不感兴趣,而就算要执行某个FD_READ消息,如果有多包数据到达,也不能确定收哪一包,所以...
      

  19.   

    那会有什么问题呢?多个包到达,只要不存在顺序问题,我的接收是按长度接收,不用区分我要的数据到底是不是在同一个包里。“WSAAsyncSelect模型,他只会向WINDOWS窗口发送一个数据到达的消息,不会去真正执行接收过程,也对该消息是否能得到执行不感兴趣”
    ——窗口收到消息后,会调用我的接收代码。如下:BOOL CStreamPlayerDlg::PreTranslateMessage(MSG* pMsg) 
    {
    // TODO: Add your specialized code here and/or call the base class
    if ((pMsg->message == WM_SOCKET) && (bSessionEndFlag == FALSE))
    {
    int32 s32Ret;
                    int32   s32Error    = WSAGETSELECTERROR(pMsg->lParam);
                    int32   s32Event    = WSAGETSELECTEVENT(pMsg->lParam); if (s32Event == FD_READ)
                    {
    s32Ret   = rtsp.recv_response();
    if (s32Ret == FAILED_StreamPlayer)
    {
    bSessionEndFlag   = TRUE;
    end_session();
    }
                     }
    }

    return CDialog::PreTranslateMessage(pMsg);
    }
      

  20.   

    楼主还是没有明白吗?你是个单线程,消息解析和消息处理都是顺序执行的。当解析到数据到达消息时,进入了你的处理函数,而你的处理函数里是个循环,即使你做Sleep,也只是当前线程Sleep,你并没有提供机会来解析来下一条消息。相当于被你给阻塞了。
    你应该在每次recv数据前检查socket缓冲区是否有数据,如果没有数据,直接退出你的接收函数,并记录住当前接收了多少数据,并把已接收到的数据放到你自己的临时缓冲区中以备拼接。
    等下次出发数据到达的消息时,你又可以继续接收未接收完的数据
      

  21.   

    windows的socket模型不通用,基本不值得学,学socket还是看linux下的,简单又方便
      

  22.   

    问题原因:就是SOCKET 缓冲区满了,而不是你的Recv时,用的Buffer满了,一定要分清。1. 首先说你这个接收函数内的问题:Sleep(1000)
       我不知道你没有设置SOCKET接收缓冲区的大小,如果没有,SOCKET的缓冲区默认大小好像是4K(不敢肯定需要确认),但可以肯定地是不会超过64K,那么你的服务器会不停的发送数据到客户端的SOCKET缓冲内,好假如,你Recv了一次数据之后,开始Sleep(1000),那么在这1秒钟内服务器能够向你接收缓冲区发送多少数据呢? 本地的话我可以肯定地告诉肯定会超过64K,那么也就说在这1秒内服务器肯定会有发送数据失败的时候,失败原因就是客户端的SOCKET缓冲满了,SOCKET底层无法在继续帮你接收数据进行缓冲。   这个函数的修改方案:就是将Sleep(1000)删除掉,然后使用一个消息循环放入Sleep的位置
         MSG msg;
    while (::GetMessage(&msg, hNotifyWnd,0,0))
    {
    if (msg.message == WM_SOCKET_NOTIFY && (SOCKET)msg.wParam == sockServer)
    {
    if (WSAGETSELECTEVENT(msg.lParam) == FD_CLOSE)
    {
    // 关闭了这里要做处理的
    break;
    }
    if (WSAGETSELECTEVENT(msg.lParam) == FD_READ)
    {
    break;
    }
    }else
    {
    ::TranslateMessage(&msg);
    }
    }
        放入这个消息循环不是怕丢(或是没有处理)Window的WM_SOCKET_NOTIFY消息,而是防止不停的Recv会造成CPU100%
        当然,你可以将Sleep先直接去掉测试一下程序,然后再加入消息循环,我上面给出的不是很完善
        完善的你可以参考CSocket 的Recv 函数调用的CSocket::PumpMessages(...)函数2. 处理不处理WM_SOCKET_NOTIFY 这个消息其实这个东西看程序流程而定,具体在那里处理掉,都可以因为这个东西肯定会发送到消息队列的
       至于你用不用是另一会事,反正你处理不处理SOCKET缓冲中的数据是不会丢的,只有你调用recv的时候数据才能被取出来。
      

  23.   

    对上面回复的修改:
    1。
    我不知道你没有设置SOCKET接收缓冲区的大小,如果没有,SOCKET的缓冲区默认大小好像是4K(不敢肯定需要确认),但可以肯定地是不会超过 64K,那么你的服务器会不停的发送数据到客户端的SOCKET缓冲内,好假如,你Recv了一次数据之后,开始Sleep(1000),在半秒后,又开始由数据到达,那么在剩下的半秒内服务器能够向你接收缓冲区发送多少数据呢?本地的话我可以肯定地告诉肯定会超过64K,那么也就说在这不半秒内服务器肯定会有发送数据失败的时候,失败原因就是客户端的SOCKET缓冲满了,SOCKET底层无法在继续帮你接收数据进行缓冲。
      

  24.   

    猜测 : 1.缓冲区buff过小。
            2.数组越界。Release有问题是因为Release会有优化那一步而不是直接编译,所以一般类似数据越界啊什么的问题Release下会崩溃
      

  25.   

    TO 28 楼:
    我没有设置 SOCKET 的缓冲大小。如果按照你的说法,因为客户端处理不及时而造成 SOCKET 缓冲溢出,那么要根本杜绝这个问题应该怎么办呢?将 SOCKET 缓冲设大肯定不能根本解决问题,应该是客户端和服务器端在上层应用要做一些处理吧?
      

  26.   

    1、按照 wqf2 在 25 楼的意思改写了程序,仍然存在丢包问题;2、今天晚上用 Wireshark 抓了一下服务器发过来的包,显示服务器发的包本来中间就不连续,我接收和解析到的包的情况与服务器发包情况是一致的,因此证明我的程序逻辑是没有问题的。改写前的程序(采用 sleep 方式)和改写后的程序都是没有问题的;3、在抓的服务器的包中,RTP 包号为 20588 的包在 Wireshark 上显示信息为“[TCP out-of-order]”,这个应该就是 hwsts2 在 28 楼提到的客户端 SOCKET 缓冲满了造成的。因为该包后面仍然有一段包是连续的;4、从 20588 包开始到 20617 之间的 30 个包虽然包号连续,但所有的包都多了一条信息“Reassembled TCP Segments”;5、20617 号包后的包号跳变到 20942,出现服务器发包不连续。
    现在想请教大家的问题是:
    1、上述第 3 点中的信息显示的确切意思是什么?百度了一下有人说是“多半是网络拥塞,导致顺序包抵达时间不同,延时太长,或者包丢失。”;2、上述第 4 点的信息显示是什么意思?3、我采用 Sleep 方式的代码,如果将写数据的文件指针定义为类的成员变量在客户端接收到的包是有丢包的,但如果将写数据的文件指针作为被调用函数的临时变量客户端接收到的包反而没有丢包,这是为什么?如果说丢包是因为我没有及时处理数据,那么后者所用的处理时间显然比前者更多,因为后者要频繁打开关闭文件,而前者只需要打开关闭一次;但是,我采用改写后的代码,两种文件指针定义方式都会丢包;4、也是最重要的一点:正如我在楼上提到的,如何杜绝因为客户端处理不及时而造成的丢包现象?谢谢大家!
      

  27.   

    补充一个问题:5、TCP 是可靠连接,为什么客户端不及时处理就会造成服务器丢包呢?这与可靠连接相违背啊。如果网络拥塞,服务器应该不断重发才对啊。
      

  28.   

    说一下我的看法:
    1 TCP是可靠的协议,只要程序没问题就不会丢包,如果真的像你现在这样发生了丢包,应该就是我们程序的问题。
    2 你上面的第一个问题应该是丢包造成的,tcp会通过重传来解决不会影响到应用层;第二个问题它只是wireshark自己生成的一个提示信息,不是错误。你可以参考http://www.wireshark.org/lists/wireshark-users/200806 /msg00047.html  ;对于第三个问题我感觉应该跟文件指针是否是局部变量没关系,只要你在接收完之后把这个文件正常的关闭了或fllush了就没问题。我现在想看一下你调用int32 TCPSocket::recv_packet_by_length(int8 *pRecvBuff, uint32 u32TotalLen)
    这个函数的上一层是怎么写的,我怀疑是别的地方的问题。
      

  29.   

    1、TCP 是可靠连接,如何解释 wireshark 抓到的服务器的包有丢包的现象?2、我用别人的程序,在每次接收数据前加个 sleep,也会出现接收数据不完整的情况;3、我在 32 楼叙述的两种文件操作方式,都是正常打开和关闭的。但就是有那样的问题,不知道为什么;我的相关程序代码如下,是按照 wqf2 的意思改过的程序:int32 TCPSocket::recv_packet_by_length(CRecvBuff &recvBuff, uint32 u32TotalLen)
    {
    int32 s32Ret;
    uint32 u32LeftLen = u32TotalLen;
    int8 *p8Data = recvBuff.a8Buff;
    do 
    {
    s32Ret   = recv(sockServer, p8Data, u32LeftLen, 0);
    if (s32Ret > 0)
    {
    p8Data += s32Ret;
    u32LeftLen -= s32Ret;
    recvBuff.u32Offset += u32TotalLen;
    }
    else
    {
    return CONTINUE_StreamPlayer;
    }
    } while (u32LeftLen > 0); return SUCCEEDED_StreamPlayer;
    }BOOL CStreamPlayerDlg::PreTranslateMessage(MSG* pMsg) 
    {
    // TODO: Add your specialized code here and/or call the base class
    if (pMsg->message == WM_SOCKET)
    {
    int32 s32Ret;
            int32   s32Error    = WSAGETSELECTERROR(pMsg->lParam);
            int32   s32Event    = WSAGETSELECTEVENT(pMsg->lParam); if (s32Event == FD_READ)
            {
    s32Ret   = rtsp.recv_data();
    if (s32Ret == FAILED_StreamPlayer)
    {
    end_session();
    }
            }
    }

    return CDialog::PreTranslateMessage(pMsg);
    }int32 CRTSP::recv_data()
    {
    int32 s32Ret;
    CStreamPlayerDlg *pMainDlg = (CStreamPlayerDlg *)AfxGetMainWnd();
    if (u8RecvType == RECV_DATA_HEAD)
    {
    s32Ret = pMainDlg->sockTCP.recv_packet_by_length(recvBuff, 4 - recvBuff.u32Offset);
    if (s32Ret != SUCCEEDED_StreamPlayer)
    {
    u8RecvType = RECV_DATA_HEAD; return s32Ret;
    }
    else
    {
    recvBuff.u32Offset = 0;
    u8RecvType = RECV_DATA_BODY;
    } recvBuff.u32Len   = (recvBuff.p8Buff[2] << 8) | recvBuff.p8Buff[3];
    } s32Ret = pMainDlg->sockTCP.recv_packet_by_length(recvBuff, recvBuff.u32Len - recvBuff.u32Offset);
    if (s32Ret != SUCCEEDED_StreamPlayer)
    {
    u8RecvType = RECV_DATA_BODY; return s32Ret;
    }
    else
    {
    recvBuff.u32Len = recvBuff.u32Offset;
    recvBuff.u32Offset = 0;
    u8RecvType = RECV_DATA_HEAD;
    } s32Ret = pMainDlg->codecVideo.get_NALU(recvBuff.p8Buff, recvBuff.u32Len);
    if (s32Ret == FAILED_StreamPlayer)
    {
    ERROR_REPORT("get_NALU() failed !");

    return FAILED_StreamPlayer;
    } return SUCCEEDED_StreamPlayer;
    }
    对代码做下说明:
    (1)、recvBuff.p8Buff 和 recvBuff.a8Buff 是指向同一个地址的,只是为了程序实现方便,前者定义为 unsigned char*,后者是 char 数组;
    (2)、程序运行后,首先读 4 个字节的 head,这 4 个字节中包含后面跟的数据的长度。如果读不够 4 个字节,则立即返回,下次进来继续读;如果读够了 4 个字节,则计算得到后面跟的 body 的长度,然后根据长度读 body 数据,如果没读够需要的长度,则立即返回,下次进来继续读 body 数据;
    (3)、recvBuff.u32Offset 用来实现数据拼接。变量 u8RecvType 是用来控制下次进来的读 head 还是读 body,其值交替变化,实现交替读取 data head 和 data body。如果任何一次没有从 SOCKET 读到需要的长度,则 u8RecvType 值保持不变,下次再进来就能实现数据拼接;如果每次能读到需要的长度,recvBuff.u32Offset 会置零,保证下一次读的数据放在接收缓冲(即 recvBuff.p8Buff)的起始处。谢谢大家!
      

  30.   

    还有一点忘记说明了:
    (4)、get_NALU 函数用来实现将得到的数据写入文件。我在 32 楼叙述的两种文件指针定义方式,其一就是将文件指针做为 codecVideo 类的成员,其二就是作为 get_NALU 函数中的临时变量。PS:
    要是这里能发附件就好了,我就可以把整个工程发上来。另外,我换了个名字,请大家不要奇怪。呵呵~~~
      

  31.   

    不好意思,recv_packet_by_length 函数有一行代码写错了,应该是下面的。但还是有丢包:int32 TCPSocket::recv_packet_by_length(CRecvBuff &recvBuff, uint32 u32TotalLen)
    {
        int32    s32Ret;
        uint32    u32LeftLen    = u32TotalLen;
        int8    *p8Data        = recvBuff.a8Buff;    
        do 
        {
            s32Ret      = recv(sockServer, p8Data, u32LeftLen, 0);
            if (s32Ret > 0)
            {
                p8Data                += s32Ret;
                u32LeftLen            -= s32Ret;
                recvBuff.u32Offset    += s32Ret;
            }
            else
            {
                return CONTINUE_StreamPlayer;
            }
        } while (u32LeftLen > 0);    return SUCCEEDED_StreamPlayer;
    }
      

  32.   

    int32 TCPSocket::recv_packet_by_length(CRecvBuff &recvBuff, uint32 u32TotalLen)里面的   int8    *p8Data        = recvBuff.a8Buff;  这一行也有问题, p8Data移动的时候recvBuff.a8Buff也要移动,要不然下次再进来就会把上次的部分数据覆盖掉,导致丢数据了。
    int32 CRTSP::recv_data()
    的逻辑好像没什么问题。
      

  33.   

    楼上说的socket缓冲区满了应该不是造成这个问题的原因。
    tcp协议的滑动窗口原理在发现接受端缓冲区满的情况下,会阻止发送端的socket的发送buffer继续发送数据,发送端调用send会失败,或返回值少于send函数里参数要求发送的数据长度,也就是没有发送完成。楼主在做发送端的程序时又检查send函数的返回值吗?你说的丢包可能是这个情况(举个例子):
    1,假如发送端不停发送地址从0到10的数据。
    2,接收端接收不过来,接收端socket接收缓存满
    3,tcp滑动窗口协议限制发送端发送数据,这样程序通过send函数放入到发送端发送buffer的数据越积越多
    4,发送端socket发送buffer满了,发送端send函数返回失败或返回值少于send函数里参数要求发送的数据长度,也就是没有发送完成。
    5,我靠,这时候你的发送端程序没check send函数的返回值,继续发送下一个缓存。
    6,这样中间的差值没有成功发送,你以为丢失了数据,其实更本没发出去!
      

  34.   

    TO 38 楼:
    非常感谢为我排查出一个安全隐患。TO 39 楼:
    这么说来问题还是在于服务器端了?服务器端代码不是我写的,是用的 QuickTime 的开源代码 Darwin Streaming Server。如果真是服务器端的问题,我想,应该是我使用的方法不正确。现在先排查一下客户端有没有问题。下来我去看看服务器代码。
      

  35.   

    http://blogs.myspace.com/index.cfm?fuseaction=blog.ListAll&friendId=501021546
      

  36.   

    http://www.bsse.ethz.ch/bel/people
    http://www.123people.ch/s/joerg+fetzlinked inhttp://www.xing.com/profile/Joerg_Fetzhttp://www.sensorsmag.com/sensors/humidity-moisture/smart-humidity-control-laundry-rooms-989http://www.shenzhenstuff.com/profile/JoergFetzhttp://blogs.myspace.com/index.cfm?fuseaction=blog.ListAll&friendId=501021546