假设一种情况我发出两次WSASend,一次投递10字节数据,里面全是a,第二次投递10字节数据,里面全是b我们期待的顺序应该是一共发出去20字节的数据,前10字节是a,后10字节是b但是由于WSASend有可能投递不完整,可能第一次只投递出去了5字节,我们在IOCP线程中捕获到了投递不完整,然后投递剩余的数据~~那么最终的顺序是不是就变成了,一共20字节数据,前5字节是a,然后10字节的b,最后5字节为a由于这种情况不好模拟,所以来问问,我分析的对吗?

解决方案 »

  1.   

    我感觉有这种可能 尽管我还没遇到过 这来看来 IOCP也是有缺陷的
      

  2.   

    这方面, IOCP 只提供一个尽量高效的I/O机制,只管数据的收发,包的处理还是自己的
      

  3.   

    这方面, IOCP 只提供一个尽量高效的I/O机制,只管数据的收发,包的处理还是自己的
    ---
    同意,包要自己在应用层设计和解析.
      

  4.   

    会这样么?
    我一直以为iocp是以队列方式处理所有的io的!
      

  5.   

    呵呵,看来我分析的没有问题~~~这样的话,IOCP就麻烦了好多~~解决方法看来只有3种,第一种就是确定第一个包投递完整之后,再投递第二个。那就不得不阻塞第二种就是在包上面加标记,接收的时候组包(好麻烦的。)第三种,也是我目前用的方法,就是自己建立有个缓冲区,把要投递的数据放入缓冲区,然后交给WSASend去投递,后来的数据依次放入缓冲区中,这样就可以避免投递顺序的问题唉,IOCP封装了这么多东西,为什么不把TCP的这个缺陷也封装进去。。TransmitFile这个函数就解决了TCP的投递顺序问题,可惜用它来投递数据,总是感觉不爽。。
      

  6.   

    IOCP是通用性的,不会单独为tcp通信作这样的设计。
    没看懂你的第三种方法,我看到的都类似第一种方法。
      

  7.   

    理论上是有这个可能,一次发送未全部发送完成,但我压力测试时,曾发过几百T的数据,从未碰到过dwIoSize<sendlen的情况,服务器吞吐率100Mbps/s CPU占用60%,内存每连接20K左右。
    后天,我干脆把dwIosize<len的情况作坏包丢弃了。因为这种机率实在太小。
      

  8.   

    Delphityro 你在广域网或者网络繁忙或状况不好的情况下测试一下
      

  9.   

    To Delphityro:
    这种情况还是很多的,估计你在局域网用吧?
      

  10.   

    楼上的两位兄弟,我在广域网上试过。使用8K的数据包时,容易出现接收不完整的情况。我把它当坏包丢掉了。后来我发现每次只投递小于路由器MTU的包,都可以完整的收发包。100%的可以。MTU通常是1096,所以发<1024字节的包比较合适,要是大于1024,在WSASend时就作多包发送,比事后处理要容易的多。若时事先发大包,事后重组,排序,引起的复杂性就很划不来了。
      

  11.   

    容易出现接收不完整的情况
    ----------------------------
    接收不完整是正常的 现在说的是发送一定数据量的数据时能不能一次发送完 也就是GetQueuedCompletionStatus返回时dwIoSize是不是发送的数据的长度
      

  12.   

    IOCP只是完成通知的手段,使用的内核队列。
    而执行IO的先后取决于系统的IO排队。一般来讲早的IO请求先执行,一个IRP处理完再处理下一个。也就是说,无论如何也不可能出现数据交错。
      

  13.   

    To:xiaoheng_wh(XH) 
    什么意思?也就是说不会出现我描述的那种情况?
      

  14.   

    不会出现你描述的那种情况,WSASend有可能投递不完整,可能第一次只投递出去了5字节,那么后面的15字节就不会投递出去,不会把10个b投递的。
      

  15.   

    呵呵,这个问题N年前我就提出过。楼主你提出的解决方法也没错,其实在使用IOCP的时候,为每个SOCKET连接建立一个发送队列,发完一个包后才发第2个,这样从单个SOCKET来看,是成了阻塞的,但是从整个系统来看,比如你有1万个连接,那么对于单个连接来说,是不是组塞的问题都不大。因为当连接数多了后,你考虑的问题不是单个SOCKET要有多块,而是系统资源和带宽如何平均地分配给这么多个连接。所以你提出的第一种解决方法是没错的,而且我几年前在实际应用中也实现过,效果很好。 
      

  16.   

    不会出现这种可能,所谓的乱序只是针对多个线程而言,但即使是多线程,先投递的WSASend也会在完成后才会执行后投递的WSASend,所以这种担心是没有必要的,即:数据不会被截断只不过你需要注意在多个线程时如何来保证这种先后顺序而已
      

  17.   

    所谓的 multiple pending write or read 并不是指在一个WSASend或WSARecv的过程中被调度只是告诉你两个WSARecv虽然能够依据顺序被完成,但你在后续过程中需要考虑在多个数据处理线程中如何保持WSAWaitForMultipleEvents中所等待到的顺序
      

  18.   

    qrlvls(空 气) ( ) 信誉:137  2006-6-5 21:23:06  得分: 0  
       
    不会出现这种可能,所谓的乱序只是针对多个线程而言,但即使是多线程,先投递的WSASend也会在完成后才会执行后投递的WSASend,所以这种担心是没有必要的,即:数据不会被截断只不过你需要注意在多个线程时如何来保证这种先后顺序而已 ---------------------------------------------------------------------------
    错了,在SOCKET里有一个系统缓冲区,假设这个缓冲区是4K,现在是空的,我现在A线程(发送线程)调用WSASend发送8K的数据,那么只会发送4K出去,在工作线程中,调用的GET函数的返回, 告诉你只发送出去4K,你在这个工作线程里,继续准备发送剩下的4K。这时候,A线程又在这个SOCKET上发送了一个4K的数据,并且顺利发出,A线程这个4K数据发送完成后,你的工作线程才发送第一次发送剩下的4K数据,这样,到了收数据的那边,本来正确的是8K+4K,现在成了4K+4K+4K数据了。你可以参考一下SDK里IOCP的那个例子里,他处理的上一次没发送完成的数据。
    这是那个SDK里的一段代码:
    case ClientIoWrite: //
    // a write operation has completed, determine if all the data intended to be
    // sent actually was sent.
    //
    lpIOContext->IOOperation = ClientIoWrite;
    lpIOContext->nSentBytes  += dwIoSize;
    dwFlags = 0;
    if( lpIOContext->nSentBytes < lpIOContext->nTotalBytes ) { //
    // the previous write operation didn't send all the data,
    // post another send to complete the operation
    //
    buffSend.buf = lpIOContext->Buffer + lpIOContext->nSentBytes;
    buffSend.len = lpIOContext->nTotalBytes - lpIOContext->nSentBytes;
    nRet = WSASend (lpPerSocketContext->Socket, &buffSend, 1, 
    &dwSendNumBytes, dwFlags, &(lpIOContext->Overlapped), NULL);
    if( nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError()) ) {
    myprintf("WSASend() failed: %d\n", WSAGetLastError());
    CloseClient(lpPerSocketContext, FALSE);
    } else if( g_bVerbose ) {
    myprintf("WorkerThread %d: Socket(%d) Send partially completed (%d bytes), Recv posted\n", 
       GetCurrentThreadId(), lpPerSocketContext->Socket, dwIoSize);
    }
    } else { //
    // previous write operation completed for this socket, post another recv
    //
    lpIOContext->IOOperation = ClientIoRead; 
    dwRecvNumBytes = 0;
    dwFlags = 0;
    buffRecv.buf = lpIOContext->Buffer,
    buffRecv.len = MAX_BUFF_SIZE;
    nRet = WSARecv(lpPerSocketContext->Socket, &buffRecv, 1, 
       &dwRecvNumBytes, &dwFlags, &lpIOContext->Overlapped, NULL);
    if( nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError()) ) {
    myprintf("WSARecv() failed: %d\n", WSAGetLastError());
    CloseClient(lpPerSocketContext, FALSE);
    } else if( g_bVerbose ) {
    myprintf("WorkerThread %d: Socket(%d) Send completed (%d bytes), Recv posted\n", 
       GetCurrentThreadId(), lpPerSocketContext->Socket, dwIoSize);
    }
    }
    break; 
      

  19.   

    SDK里的确实是处理了发送不完整的情况,写的还算稳定,但用我的一个做压力测试的客户端测试出,它关闭时有地址访问错。
      

  20.   

    SDK的例子只是用来演示原理。做为商也应用根本不行,还要做N多工作。
      

  21.   

    谁能把SDK里的例子发给我 谢谢了 [email protected]
      

  22.   

    sdk里的拿来商用,确实还要做N多工作。
      

  23.   

    楼上说不会的,和说会的好象并没有完全统一情况。楼主所描述的是,可能由于两次(两个线程)向同一个客户端发送数据,由于两个线程没有进行同步,可能前一个线程只发了一半数据,后一个线程继而发送完它自己的数据,前一个线程把后一半的数据发送出去|A1--->C|'bbbbb'|A1--->C|
    |'aaaaa'|A2--->C|'aaaaa'|A1本计划发送10个'a',侧是第一次send只发出了5个,继而A2赶到,发送了5个'b',并且全部发送成功,继而A1才发送后5个'a',那么本意发送的是'aaaaaaaaaabbbbb',而接收后就成了
    'aaaaa'+'bbbbb'+'aaaaa'.
    对于这种情况完全是有可能的。只不过是这种设计本身就存在问题。在这个问题上面,最好是为每个连接使用一个发送队列。对于整个发送队列确实也至少理论上是阻塞。而这样子设计对于整个服务来讲仍然是合理的,毕竟整个服务考虑的是众连接对于服务器CPU和IO资源的最大利用率,而并不关心个别连接在这当中的效率如何,至少次之,并且即便是使用了两个线程发送,那么对于这个插队的设计,也需要进步一讨论可行性。不仅会影响其它连接的利益,同时对本连接而言也是无利可图的,并且在服务器的设计上来说,也是不符合架构设计的。没有哪个服务设计的初衷是为了某一个连接(客户)而设计的,否则就不需要使用IOCP了,而直接使用单连接去维护。即使要挤那么不如一次就单一向一个客户开放。
      

  24.   

    同意unsigned(僵哥(为什么我会到这里来……)) 
    单一线程如果出现插队,就是你程序设计的问题,为什么第一次的数据没发完就去发第二次呢?
    如果安顺序投递不会出现插队
      

  25.   

    而且如果第二次的数据发出去了,那么第一次没发完的就没了,不可能出现在第二次的后面
    也就是最多出现aaaaa bbbbb的情况
      

  26.   

    同意unsigned(僵哥(为什么我会到这里来……)) 
    单一线程如果出现插队,就是你程序设计的问题,为什么第一次的数据没发完就去发第二次呢?
    如果安顺序投递不会出现插队==================================
    请注意是多个线程单一客户,并不是单一线程出现插队,单一线程是不可能插队的。
      

  27.   

    ahao(天·狼·星星):
    不会的,发送顺序肯定和你投递顺序一致的,这个是系统保证的,但完成通知的次序是不保证和投递次序一致的。
    同意上面的观点,这个是微软的"WINDOWS网络编程(第2版)"上面的原话.
      

  28.   

    对于同一个SOKET是不会有这个问题的。看来误人子弟的人还真不少。
    这个问题与并不需要与IOCP关联起来。
      

  29.   

    多个线程,那就是我说的第二种情况
    如果第二次的数据发出去了,那么第一次没发完的就没了,不可能出现在第二次的后面
    也就是最多出现aaaaa bbbbb的情况
    不会aaaaa bbbbb aaaaa
      

  30.   

    你在512k的网络上,一次性投递二个10m的数据aa(10m),bb(10m),看看就知道了。
      

  31.   

    当aa没有发送完的时候,但有可能完成事件已经收到,这里你再继续wsasend(aa的剩余字节)的时候,上次发送的bb,可以全部发送完。后来假设20m都收全了,就成了aaabbbaa了
      

  32.   

    send(a),send(b),send(a),结果当然是aba
      

  33.   

    关于这个问题,由于考虑到多重发送的包乱序问题,并且必要性在服务器的设计上来说,针对单一用户的带宽占用意义不太大。首先,如果允许多重发送,那么须动态管理内存,这是其一,也就是如果同使用一个OVERLAPPEDEX,那肯定是不可行的。我的设想是这样子,给每个连接分配两个OVERLAPPEDEX,或者干脆只给接收一个固定的OVERLAPPEDEX,发送则使用一个链式的结构(实则该称为发送队列),并设置一标志。由于发送是主动的,那么完全可以设置状态,用于标示该连接是否正有数据在发送(pending),此时其有其它数据须发送则只须将数据接到发送队列即可。否则就可以直接发起一个WSASend,而不采用PostQueuedCompletionStatus影响现有接收监听(Pending WSARecv)。只是如此可能导致给每个连接分配大量的内存。