关于IOCP的投递顺序
假设一种情况
我发出两次WSASend,一次投递10字节数据,里面全是a,第二次投递10字节数据,里面全是b
我们期待的顺序应该是一共发出去20字节的数据,前10字节是a,后10字节是b
但是由于WSASend有可能投递不完整,可能第一次只投递出去了5字节,我们在IOCP线程中捕获到了投递不完整,然后投递剩余的数据~~
那么最终的顺序是不是就变成了,一共20字节数据,前5字节是a,然后10字节的b,最后5字节为a
由于这种情况不好模拟,所以来问问,我分析的对吗?
问题点数:20、回复次数:52Top
1 楼striking(庸人自扰)回复于 2006-05-29 09:01:57 得分 1
完全有这样的可能!Top
2 楼hxzb7215191(天行健,君子以自强不息)回复于 2006-05-29 09:07:34 得分 1
这个还没有考虑过.gzTop
3 楼fengge8ylf(秀视工作室,承接P2P项目)回复于 2006-05-29 09:29:54 得分 1
我感觉有这种可能 尽管我还没遇到过 这来看来 IOCP也是有缺陷的Top
4 楼asker100()回复于 2006-05-29 10:32:24 得分 1
这方面, IOCP 只提供一个尽量高效的I/O机制,只管数据的收发,包的处理还是自己的Top
5 楼nuaawenlin(飘人)回复于 2006-05-29 10:49:11 得分 1
tcp是这样得
udp就不会了Top
6 楼weiziyuner(烂人)回复于 2006-05-29 11:29:49 得分 1
这方面, IOCP 只提供一个尽量高效的I/O机制,只管数据的收发,包的处理还是自己的
---
同意,包要自己在应用层设计和解析.Top
7 楼vincentcsdn(DemonPumpkin)回复于 2006-05-29 12:39:42 得分 1
会这样么?
我一直以为iocp是以队列方式处理所有的io的!Top
8 楼lemony8734(lemony)回复于 2006-05-29 14:22:21 得分 0
呵呵,看来我分析的没有问题~~~
这样的话,IOCP就麻烦了好多~~
解决方法看来只有3种,第一种就是确定第一个包投递完整之后,再投递第二个。那就不得不阻塞
第二种就是在包上面加标记,接收的时候组包(好麻烦的。。。。)
第三种,也是我目前用的方法,就是自己建立有个缓冲区,把要投递的数据放入缓冲区,然后交给WSASend去投递,后来的数据依次放入缓冲区中,这样就可以避免投递顺序的问题。。。
唉,IOCP封装了这么多东西,为什么不把TCP的这个缺陷也封装进去。。。。。
TransmitFile这个函数就解决了TCP的投递顺序问题,可惜用它来投递数据,总是感觉不爽。。Top
9 楼lifengice0706(无)回复于 2006-05-29 16:36:42 得分 1
IOCP是通用性的,不会单独为tcp通信作这样的设计。
没看懂你的第三种方法,我看到的都类似第一种方法。Top
10 楼fengge8ylf(秀视工作室,承接P2P项目)回复于 2006-05-29 17:24:25 得分 1
第一种和第三种是有区别的 第一种是以包为单位 第三种是以缓冲区为单位Top
11 楼Delphityro(下岗工人)回复于 2006-05-30 14:29:24 得分 1
理论上是有这个可能,一次发送未全部发送完成,但我压力测试时,曾发过几百T的数据,从未碰到过dwIoSize<sendlen的情况,服务器吞吐率100Mbps/s CPU占用60%,内存每连接20K左右。
后天,我干脆把dwIosize<len的情况作坏包丢弃了。因为这种机率实在太小。Top
12 楼fengge8ylf(秀视工作室,承接P2P项目)回复于 2006-05-30 14:42:47 得分 1
Delphityro 你在广域网或者网络繁忙或状况不好的情况下测试一下Top
13 楼lemony8734(lemony)回复于 2006-05-30 15:01:07 得分 0
To Delphityro:
这种情况还是很多的,估计你在局域网用吧?
Top
14 楼robin_yao()回复于 2006-05-30 17:18:25 得分 1
只能投完一个再投第二个了.GZTop
15 楼Delphityro(下岗工人)回复于 2006-05-31 20:31:09 得分 1
楼上的两位兄弟,我在广域网上试过。使用8K的数据包时,容易出现接收不完整的情况。我把它当坏包丢掉了。后来我发现每次只投递小于路由器MTU的包,都可以完整的收发包。100%的可以。MTU通常是1096,所以发<1024字节的包比较合适,要是大于1024,在WSASend时就作多包发送,比事后处理要容易的多。若时事先发大包,事后重组,排序,引起的复杂性就很划不来了。Top
16 楼Delphityro(下岗工人)回复于 2006-05-31 20:37:29 得分 1
你们也可以去试一下,看看结果跟我的是不是一样。Top
17 楼fengge8ylf(秀视工作室,承接P2P项目)回复于 2006-06-01 09:21:28 得分 1
容易出现接收不完整的情况
----------------------------
接收不完整是正常的 现在说的是发送一定数据量的数据时能不能一次发送完 也就是GetQueuedCompletionStatus返回时dwIoSize是不是发送的数据的长度Top
18 楼closeall2008(codeproduction)回复于 2006-06-04 11:03:53 得分 1
关注, 顶一下Top
19 楼xiaoheng_wh(XH)回复于 2006-06-04 13:54:58 得分 1
IOCP只是完成通知的手段,使用的内核队列。
而执行IO的先后取决于系统的IO排队。一般来讲早的IO请求先执行,一个IRP处理完再处理下一个。也就是说,无论如何也不可能出现数据交错。
Top
20 楼caitian6()回复于 2006-06-04 14:08:56 得分 1
MARKTop
21 楼windey(行云一派)回复于 2006-06-04 14:45:11 得分 1
顶!Top
22 楼lemony8734(lemony)回复于 2006-06-04 23:01:10 得分 0
To:xiaoheng_wh(XH)
什么意思?
也就是说不会出现我描述的那种情况?Top
23 楼ringphone(临风)回复于 2006-06-05 09:36:17 得分 1
不会出现你描述的那种情况,WSASend有可能投递不完整,可能第一次只投递出去了5字节,那么后面的15字节就不会投递出去,不会把10个b投递的。
Top
24 楼Roamer2889(静流深远)回复于 2006-06-05 12:14:26 得分 0
呵呵,这个问题N年前我就提出过。楼主你提出的解决方法也没错,其实在使用IOCP的时候,为每个SOCKET连接建立一个发送队列,发完一个包后才发第2个,这样从单个SOCKET来看,是成了阻塞的,但是从整个系统来看,比如你有1万个连接,那么对于单个连接来说,是不是组塞的问题都不大。因为当连接数多了后,你考虑的问题不是单个SOCKET要有多块,而是系统资源和带宽如何平均地分配给这么多个连接。所以你提出的第一种解决方法是没错的,而且我几年前在实际应用中也实现过,效果很好。
Top
25 楼qrlvls( 空 气 )回复于 2006-06-05 21:23:06 得分 0
不会出现这种可能,所谓的乱序只是针对多个线程而言,但即使是多线程,先投递的WSASend也会在完成后才会执行后投递的WSASend,所以这种担心是没有必要的,即:数据不会被截断
只不过你需要注意在多个线程时如何来保证这种先后顺序而已Top
26 楼qrlvls( 空 气 )回复于 2006-06-05 21:26:28 得分 0
所谓的 multiple pending write or read 并不是指在一个WSASend或WSARecv的过程中被调度
只是告诉你两个WSARecv虽然能够依据顺序被完成,但你在后续过程中需要考虑在多个数据处理线程中如何保持WSAWaitForMultipleEvents中所等待到的顺序Top
27 楼Roamer2889(静流深远)回复于 2006-06-06 10:27:59 得分 0
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;
Top
28 楼Delphityro(下岗工人)回复于 2006-06-06 12:10:20 得分 0
SDK里的确实是处理了发送不完整的情况,写的还算稳定,但用我的一个做压力测试的客户端测试出,它关闭时有地址访问错。Top
29 楼Roamer2889(静流深远)回复于 2006-06-06 12:25:07 得分 0
SDK的例子只是用来演示原理。做为商也应用根本不行,还要做N多工作。Top
30 楼fengge8ylf(秀视工作室,承接P2P项目)回复于 2006-06-06 13:01:18 得分 0
谁能把SDK里的例子发给我 谢谢了 fengge_ylf@163.comTop
31 楼lemony8734(lemony)回复于 2006-06-08 18:24:07 得分 0
我顶。。。。。。。
各位继续讨论啊。。。。。。。。。Top
32 楼Delphityro(下岗工人)回复于 2006-06-09 09:50:26 得分 0
sdk里的拿来商用,确实还要做N多工作。Top
33 楼sdf123321()回复于 2006-06-09 16:13:26 得分 0
gzTop
34 楼ahao(天·狼·星星)回复于 2006-06-11 09:34:37 得分 0
不会的,发送顺序肯定和你投递顺序一致的,这个是系统保证的,但完成通知的次序是不保证和投递次序一致的。Top
35 楼yinzhaohui(努力)回复于 2006-06-11 12:25:12 得分 0
这个问题,确实存在
解决方法:使用队列发送,
其实这个问题还要解决,不好解决的是在多工作线程时的接收乱续问题Top
36 楼unsigned(僵哥(发站内消息,请附上链接或问题说明,否则不予回复))回复于 2006-07-03 15:52:55 得分 0
楼上说不会的,和说会的好象并没有完全统一情况。楼主所描述的是,可能由于两次(两个线程)向同一个客户端发送数据,由于两个线程没有进行同步,可能前一个线程只发了一半数据,后一个线程继而发送完它自己的数据,前一个线程把后一半的数据发送出去
|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了,而直接使用单连接去维护。即使要挤那么不如一次就单一向一个客户开放。Top
37 楼godfly000()回复于 2006-07-03 16:54:51 得分 0
同意unsigned(僵哥(为什么我会到这里来……))
单一线程如果出现插队,就是你程序设计的问题,为什么第一次的数据没发完就去发第二次呢?
如果安顺序投递不会出现插队Top
38 楼godfly000()回复于 2006-07-03 16:57:57 得分 0
而且如果第二次的数据发出去了,那么第一次没发完的就没了,不可能出现在第二次的后面
也就是最多出现aaaaa bbbbb的情况Top
39 楼whwjn(哈哈)回复于 2006-07-03 20:13:06 得分 0
markTop
40 楼whwjn(哈哈)回复于 2006-07-03 20:13:20 得分 0
markTop
41 楼unsigned(僵哥(发站内消息,请附上链接或问题说明,否则不予回复))回复于 2006-07-03 20:47:32 得分 0
同意unsigned(僵哥(为什么我会到这里来……))
单一线程如果出现插队,就是你程序设计的问题,为什么第一次的数据没发完就去发第二次呢?
如果安顺序投递不会出现插队
==================================
请注意是多个线程单一客户,并不是单一线程出现插队,单一线程是不可能插队的。Top
42 楼wwwllg(野蛮人)回复于 2006-07-04 08:45:36 得分 0
看来二楼是高手Top
43 楼xb_luotuo(luotuo)回复于 2006-07-04 10:00:50 得分 0
ahao(天·狼·星星):
不会的,发送顺序肯定和你投递顺序一致的,这个是系统保证的,但完成通知的次序是不保证和投递次序一致的。
同意上面的观点,这个是微软的"WINDOWS网络编程(第2版)"上面的原话.Top
44 楼hollysky(爱神)回复于 2006-07-04 10:35:21 得分 0
对于同一个SOKET是不会有这个问题的。看来误人子弟的人还真不少。
这个问题与并不需要与IOCP关联起来。Top
45 楼godfly000()回复于 2006-07-04 16:40:12 得分 0
多个线程,那就是我说的第二种情况
如果第二次的数据发出去了,那么第一次没发完的就没了,不可能出现在第二次的后面
也就是最多出现aaaaa bbbbb的情况
不会aaaaa bbbbb aaaaa
Top
46 楼wwwllg(野蛮人)回复于 2006-07-04 16:45:30 得分 0
你在512k的网络上,一次性投递二个10m的数据aa(10m),bb(10m),看看就知道了。Top
47 楼wwwllg(野蛮人)回复于 2006-07-04 16:48:45 得分 0
当aa没有发送完的时候,但有可能完成事件已经收到,这里你再继续wsasend(aa的剩余字节)的时候,上次发送的bb,可以全部发送完。后来假设20m都收全了,就成了aaabbbaa了Top
48 楼godfly000()回复于 2006-07-04 18:20:12 得分 0
send(a),send(b),send(a),结果当然是abaTop
49 楼louifox(兰陵笑笑生)回复于 2006-07-22 13:32:03 得分 0
mark
Top
50 楼Juchiyufei(三更半夜我送你回家.总统也许我做不到.今生难得的遇见你,我们就应该在一起.....)回复于 2006-07-22 16:05:00 得分 0
markTop
51 楼ironox(铁牛)回复于 2006-07-22 20:00:39 得分 0
关注一下,这正是我在学习的内容
Top
52 楼unsigned(僵哥(发站内消息,请附上链接或问题说明,否则不予回复))回复于 2006-07-23 03:17:48 得分 0
关于这个问题,由于考虑到多重发送的包乱序问题,并且必要性在服务器的设计上来说,针对单一用户的带宽占用意义不太大。首先,如果允许多重发送,那么须动态管理内存,这是其一,也就是如果同使用一个OVERLAPPEDEX,那肯定是不可行的。我的设想是这样子,给每个连接分配两个OVERLAPPEDEX,或者干脆只给接收一个固定的OVERLAPPEDEX,发送则使用一个链式的结构(实则该称为发送队列),并设置一标志。由于发送是主动的,那么完全可以设置状态,用于标示该连接是否正有数据在发送(pending),此时其有其它数据须发送则只须将数据接到发送队列即可。否则就可以直接发起一个WSASend,而不采用PostQueuedCompletionStatus影响现有接收监听(Pending WSARecv)。只是如此可能导致给每个连接分配大量的内存。Top




