关于异步IO的一个疑问求教?

时间:2022-09-23 09:47:53
很久不弄异步IO了,这两天完善一个IOCP的实现。突然有一个概念模糊了。求证下:
对于异步IO投递的操作,比如同一个SOCKET对象投递的多次WSASend操作,是不是在
完成端口上也是按照投递的顺序完成的?

比如同一个SOCKET客户端投递的异步IO序列如下:
1 - WSASend-s1
2 - WSASend-s2
3 - WSASend-s3
那么在CompletionPort上面得到的通知也应该是(假定每一个异步操作都触发了多次完成端口通知):
1 - WSASend-s1完成通知-01
1 - WSASend-s1完成通知-xx
2 - WSASend-s2完成通知-01
2 - WSASend-s2完成通知-xx
3 - WSASend-s3完成通知-01
3 - WSASend-s3完成通知-xx

25 个解决方案

#1


你设想一种情况:

  你wsasend了两次,分别称为a和b,假设a完成了一半的字节,这时a从GetIo...Status函数里反馈出来,与此同时,socket又可写了,这时完成端口不等你相应,就开始执行b的操作。

请基于这种情形考虑一下。


#2


这个问题其实是很多人忽视的(简直不可思议),它直接影响整个设计的正确性。

#3


我也搞不清楚,但我觉得既然是异步,就不能假设它有序。
我的做法是对于同一Socket,无论是WSASend还是WSARecv,都保证同一时间只有一个。就是说只有上一个请求完成后才投递下一个请求。

#4


引用 3 楼 lzl_2008 的回复:
我也搞不清楚,但我觉得既然是异步,就不能假设它有序。
我的做法是对于同一Socket,无论是WSASend还是WSARecv,都保证同一时间只有一个。就是说只有上一个请求完成后才投递下一个请求。


不错,我认为这是唯一正确的方式。否则,就请考虑我1楼所说的那种情况。

#5


iocp, 单个socket同一时间多次i或多次o没有意义,多socket异步io适用。(相对多线程同步模型而言,效率高)
iocp原理上是前摄器模式。

#6


请明示。

我也纳闷这个问题。这不单单是IOCP的问题,所有的异步SOCKET工作模式都会遇到类似的问题。按照我开局的假设,对一次WSASend-s1操作,如果这个操作没有完成任务即发送完所有的数据,如果返回通知,那么后续第二个异步IO序列就开始执行了,这样就会导致实际发送情况和设计顺序不符了。导致错误。

最可靠的保证就是所有投递的异步IO在待发送队列都是有序排列阻塞式方法的,只有前面一个完成,下一个IO请求才会执行,依序执行。既保证了效率、又确保有序准确实现设计意图。那么模型这么设计的目的无非是为了不产生用户层面的阻塞,同时提高了两次IO之前的切换效率,无需从内核态转换到用户态(不过也不一定,姑且这么理解它的效率优化)。那么这就要求任何一次WSASEND请求,无论你请求多少数据1MB也好,10MB也好,都需要发送完成后,才会通知完成端口或者其他的异步IO通知发生。

不知道这么理解是否正确。

引用 1 楼 sinservice 的回复:
你设想一种情况:

  你wsasend了两次,分别称为a和b,假设a完成了一半的字节,这时a从GetIo...Status函数里反馈出来,与此同时,socket又可写了,这时完成端口不等你相应,就开始执行b的操作。

请基于这种情形考虑一下。

#7


这种做法当然没有问题。不过这不是异步IO的初衷。异步IO同样需要同步控制,但是不是这么个用法,可行但是不佳。

你有否注意到WSASend函数的参数
int WSASend(
  __in          SOCKET s,
  __in          LPWSABUF lpBuffers,
  __in          DWORD dwBufferCount,
  __out         LPDWORD lpNumberOfBytesSent,
  __in          DWORD dwFlags,
  __in          LPWSAOVERLAPPED lpOverlapped,
  __in          LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

lpBuffers可以为一个数组比如
WSASend(..,&wsaBuffer[2],2,...);
也就是说一次异步IO请求发送两块buffer出去,类似于你投递了两次WSASend请求,每次一个了。如果一次投递两个buffer,那么这里的发送顺序就是wsaBuffer[0]-->wsaBuffer[1]-->....一次发送了。

当然,这里我的问题就出现了。什么时候再完成端口上得到完成通知?会得到几个完成端口通知?
网上的说话众说纷纭,我个人认为大多都是理解错误的,或者是我的理解有问题,那么大多理解就是正确的了。我的理解,这种情况下只能出现一个完成通知,那就是wsaBuffer[1]内的数据按照其指示长度全部发送成功后,得到了底层TCP协议栈的确认后才会通知SOCKET编程应用接口:任务完成!
引用 3 楼 lzl_2008 的回复:
我也搞不清楚,但我觉得既然是异步,就不能假设它有序。
我的做法是对于同一Socket,无论是WSASend还是WSARecv,都保证同一时间只有一个。就是说只有上一个请求完成后才投递下一个请求。

#8


引用 6 楼 teleinfor 的回复:
请明示。

我也纳闷这个问题。这不单单是IOCP的问题,所有的异步SOCKET工作模式都会遇到类似的问题。按照我开局的假设,对一次WSASend-s1操作,如果这个操作没有完成任务即发送完所有的数据,如果返回通知,那么后续第二个异步IO序列就开始执行了,这样就会导致实际发送情况和设计顺序不符了。导致错误。

最可靠的保证就是所有投递的异步IO在待发送队列都是有序排列阻塞式方法的,只有前面……


我的意见是,即按照3楼所说,同时仅保证有一个写操作或读操作。否则,无法保证我1L所说之情况不发生。你说的在完成端口内“阻塞”(就是说要完成端口将所有字节完全做完),这是不可能的,完成端口不可能是这样设定的。

至于你7L所说的,当然是一个WSASend对应一个回馈,而不是一个buffer对应一个回馈,这是显而易见的。

#9


看来这个问题还是没有一个清晰的正确的结论出来。

重叠IO模型和IOCP模型都会遇到类似的问题。目前来看可以明确的就是对于每一个WSASend()操作,对应一个完成通知应该是可以明确的。不过具体发送了多少数据就成为一个问题了。那么,有一种情况我们不妨来分析一下:

WSASend(...,&wsaBuffer[3],3,...);

也就是一次WSASend投递了多个WSABUF的情况发生了。那么如果一次完成通知并没有发所有的buffer发送出去。就会出现疑问:
1 - 如果再一次的对剩余数据进行投递,那么为了保证有序数据传递。就必然要求这个SOCKET端口上不能存在其他未决的异步IO WSASend请求。如果存在,在这个间隙就有可能传递这个WSASend请求的数据了。这样就会和设计的数据传递顺序出现冲突。

如果仅仅是允许在一个端口上同时存在一个IO请求,那么这样子有何很多资料所说的异步提交请求有冲突,很显然这么做就成了一种意义上的同步方式了。

引用 8 楼 sinservice 的回复:
引用 6 楼 teleinfor 的回复:

请明示。

我也纳闷这个问题。这不单单是IOCP的问题,所有的异步SOCKET工作模式都会遇到类似的问题。按照我开局的假设,对一次WSASend-s1操作,如果这个操作没有完成任务即发送完所有的数据,如果返回通知,那么后续第二个异步IO序列就开始执行了,这样就会导致实际发送情况和设计顺序不符了。导致错误。

最可靠的保证就是所有投递的异步……

#10


看来这个问题还是没有一个清晰的正确的结论出来。

重叠IO模型和IOCP模型都会遇到类似的问题。目前来看可以明确的就是对于每一个WSASend()操作,对应一个完成通知应该是可以明确的。不过具体发送了多少数据就成为一个问题了。那么,有一种情况我们不妨来分析一下:

WSASend(...,&wsaBuffer[3],3,...);

也就是一次WSASend投递了多个WSABUF的情况发生了。那么如果一次完成通知并没有发所有的buffer发送出去。就会出现疑问:
1 - 如果再一次的对剩余数据进行投递,那么为了保证有序数据传递。就必然要求这个SOCKET端口上不能存在其他未决的异步IO WSASend请求。如果存在,在这个间隙就有可能传递这个WSASend请求的数据了。这样就会和设计的数据传递顺序出现冲突。

如果仅仅是允许在一个端口上同时存在一个IO请求,那么这样子有何很多资料所说的异步提交请求有冲突,很显然这么做就成了一种意义上的同步方式了。

引用 8 楼 sinservice 的回复:
引用 6 楼 teleinfor 的回复:

请明示。

我也纳闷这个问题。这不单单是IOCP的问题,所有的异步SOCKET工作模式都会遇到类似的问题。按照我开局的假设,对一次WSASend-s1操作,如果这个操作没有完成任务即发送完所有的数据,如果返回通知,那么后续第二个异步IO序列就开始执行了,这样就会导致实际发送情况和设计顺序不符了。导致错误。

最可靠的保证就是所有投递的异步……

#11


如果仅仅是允许在一个端口上同时存在一个IO请求,那么这样子有何很多资料所说的异步提交请求有冲突,很显然这么做就成了一种意义上的同步方式了。

===

不,这里不是同步的。同步是指,调用者必须等待调用完成。而现在,调用者可以干其他事情,而由完成端口替你完成操作。这可以大大节约调用者的线程数量,依然是异步的。

#12


翻看往年对IOCP类似问题讨论,大多是无果而终,都没有一个清晰的结论出来。

总结下来基本上对IOCP的理解:
1、 完成端口连接线程负责对客户端的连接接受,建立和完成端口的关联,投递异步操作开始IO
2、 工作线程根据CPU的数量建立一定数量的工作服务线程,负责具体IO操作的完成和通知
3、 异步IO操作的提交会进入IOCP系统缓冲队列,由系统根据线程的工作忙闲状态按照LILO方式进行调度,而对于提交的数据队列按照FIFO方式进行操作

针对理解(3)的进一步扩展:
比如同一个客户端SOCKET上面提交了多次的send请求,使用WSASend()函数。每次提交一个待发送的数据buffer,那么按照提交的顺序这些buffer会按照FIFO的原则进入IOCP的任务队列,也就是IO_PENDING任务队列,等待发送。工作线程根据系统调度情况从任务队列获取数据进行发送操作。当然工作线程的调度是按照LILO方式进行以提高线程的工作调度效率,提升整体工作效能。工作线程获取目标数据后,进入系统内核底层进行数据的发送操作,这里一旦到了底层数据发送操作就和上层的各种模型无关了。任何的数据到了底层都需要进入TCP/IP协议栈缓冲区,然后是网卡硬件缓冲区等待顺序发送,由TCP协议保证发送的可靠性。

这里追究细节,正因为对细节的不清楚,所以问题就出现了:
工作线程按照FIFO方式获取待发送buffer后,就开始提交底层进行发送。如果,这里的数据提交方式是严格按照SOCKET提交的FIFO方式往底层提交数据,并且保证提交的数据批量发送完成后,后续等待线程才有机会依序提交自己的任务数据发送,那么就可以保证数据的有序性。一旦数据提交到TCP协议栈,我们就无需关心了。TCP协议自然会保证发送有序。如果,这里的线程工作不是按照前述设想,比如前面的一个线程提交的数据没有全部提交或者没有按照请求全部发送完成,就通知完成端口了。那么这个线程就是去了而对底层资源的占有,向用户级报告提交结果,由用户决定继续提交剩余数据,这时其他线程就会抢占资源进行数据发送,就会导致发送的数据顺序和用户最初提交的数据不一致了。比如初始顺序为A-B-C,那么实际的发送顺序就可能成为A1-B-A2-C或者其他情况了。

我个人分析下来认为上述第二种假设不太可能发生,如果机制上这种情况可能。那么就十分的混乱了。每个批次发送的数据结构完全被打乱了,接收方无法对接收数据进行正确的复原。因为你自定义的包头等信息完全有可能被打乱,无法根据HEAD + DATA方式进行数据的组合。

继续探讨。看来大家对于异步IO网络编程的理解,还不是十分的彻底清楚啊。

#13


我倒是觉得ABC发送顺序,最终的接收顺序倒是有可能打乱,比如:
发送顺序:
ABC
可能的接收顺序:
ABC
ACB
BAC
BCA
CAB
CBA
但是一次请求A、B、C,应该不会出现分段发送的情况,否则就难以预料结果了。

#14


其实我很清楚,只要你连续WSASend两次,就可能会出错,所以,要保证同一时刻仅有一个WSASend或WSARecv未完成。

#15


如同磁盘和磁带的区别, 外设不同, 得到的io结果可能是不同的

#16


看看tcp粘包怎么解决的吧

#17


个人认为
对于一次WSASend,GQCS要么返回的字节数为0,要么是全部发送出去,
不会出现发送部分数据就返回(起码目前实践中还没出现)。

MSDN的IOCP例子GQCS对WSASend返回的字节数进行了判断,不知是何用意?

#18


WSASend不能保证,AcceptEx和WSARecv貌似可以这样做.

#19


引用 17 楼 stjay 的回复:
个人认为
对于一次WSASend,GQCS要么返回的字节数为0,要么是全部发送出去,
不会出现发送部分数据就返回(起码目前实践中还没出现)。

MSDN的IOCP例子GQCS对WSASend返回的字节数进行了判断,不知是何用意?


这跟你的测试环境有关,在公网,一定会出现部分完成的情况。

#20


算了,结合多方面信息总结下吧:
1、 WSASend发送操作不一定一次把提交的数据buffer发送完成,有可能出现只发送一部分的情况发生
2、 如果同时提交了多个并发的WSASend在同一个SOCKET上面,那么发送完成通知和具体发送数据的情况就难以预料了
3、 根据经验来看,在同一个时间点上保证目标SOCKET只有一个未决的WSASend或者WSARecv是合理的实现方法

仅供各位参考。有兴趣的朋友再继续研究吧。我今天弄了一个WSASend和WSARecv并发在一个SOCKET上面,都出现了程序崩溃,由于是服务进程,没有来得及跟踪到底为何发生crash。

异步IO真是个麻烦事,要研究透彻才用得好。

#21


希望本帖不会误导将来查阅的朋友。

#22


刚刚又遇到一个奇怪的问题:
我在发送数据的中间过程,投递了一个WSARecv接收异步IO操作。结果导致服务程序运行不正常,崩溃了。一直没有查找出原因来。我这么做的目的就是加入了一个心跳包,发送数据的过程当中需要检测处理Heart-beat packet

在同一个SOCKET端口上同时出现WSARecv和WSASend两个未决的操作,应该是可以的啊?应该不牵涉到同步控制等问题。

#23


引用 20 楼 teleinfor 的回复:
算了,结合多方面信息总结下吧:
1、 WSASend发送操作不一定一次把提交的数据buffer发送完成,有可能出现只发送一部分的情况发生
2、 如果同时提交了多个并发的WSASend在同一个SOCKET上面,那么发送完成通知和具体发送数据的情况就难以预料了
3、 根据经验来看,在同一个时间点上保证目标SOCKET只有一个未决的WSASend或者WSARecv是合理的实现方法

仅供各位……


同意此三点意见。

#24


引用 22 楼 teleinfor 的回复:
刚刚又遇到一个奇怪的问题:
我在发送数据的中间过程,投递了一个WSARecv接收异步IO操作。结果导致服务程序运行不正常,崩溃了。一直没有查找出原因来。我这么做的目的就是加入了一个心跳包,发送数据的过程当中需要检测处理Heart-beat packet

在同一个SOCKET端口上同时出现WSARecv和WSASend两个未决的操作,应该是可以的啊?应该不牵涉到同步控制等问题。


有可能的一个原因:

GetIo...Status函数可以在不同的线程“同时返回”,那么,对同一个套接字的Send和Recv操作的完成是可能并发处理的。

看看是不是这里的问题?

#25


分析了下,找到了可能的原因:IOCP每次操作的PER_IO_OPERATION_DATA设计的不好,导致WSASend和WSARecv共享使用了overlapped数据结构,冲突导致异常。

很多网络资料或者书籍上面对IOCP的介绍都是非常粗浅和不全面的,包括《windows网络编程》目前总结来看。第一版本的作者没有交代清楚,后来的人就是天下书籍都是一大抄。

基本上可以得出如下结论供各位后来者参考:
1、WSASend/WSRecv对目标数据的异步IO有可能会被中断,不一定一次性按照设计发送或者接收期望长度的数据。有可能需要再一次或者多次的投递异步IO操作进行数据接收或者发送;
2、如果同时提交了多个异步IO操作,不管是send还是Recv或者二者的混合。那么完成通知的顺序以及每次完成后IO的数据长度就完全不可预料,这主要是由于线程池的工作机制决定的。要完全自己组织数据的形式。
3、根据国内外讨论结果,总结出国际通行的国际经验:在同一个时刻保持在一个SOCKET端口上只有一个未决的send或者recv或者send和recv,也就是说同类型的IO操作同一个时间点上保持只有一个会比较合理,从效率和稳定性都比较容易控制。

引用 24 楼 sinservice 的回复:
引用 22 楼 teleinfor 的回复:

刚刚又遇到一个奇怪的问题:
我在发送数据的中间过程,投递了一个WSARecv接收异步IO操作。结果导致服务程序运行不正常,崩溃了。一直没有查找出原因来。我这么做的目的就是加入了一个心跳包,发送数据的过程当中需要检测处理Heart-beat packet

在同一个SOCKET端口上同时出现WSARecv和WSASend两个未决的操作,应该……

#1


你设想一种情况:

  你wsasend了两次,分别称为a和b,假设a完成了一半的字节,这时a从GetIo...Status函数里反馈出来,与此同时,socket又可写了,这时完成端口不等你相应,就开始执行b的操作。

请基于这种情形考虑一下。


#2


这个问题其实是很多人忽视的(简直不可思议),它直接影响整个设计的正确性。

#3


我也搞不清楚,但我觉得既然是异步,就不能假设它有序。
我的做法是对于同一Socket,无论是WSASend还是WSARecv,都保证同一时间只有一个。就是说只有上一个请求完成后才投递下一个请求。

#4


引用 3 楼 lzl_2008 的回复:
我也搞不清楚,但我觉得既然是异步,就不能假设它有序。
我的做法是对于同一Socket,无论是WSASend还是WSARecv,都保证同一时间只有一个。就是说只有上一个请求完成后才投递下一个请求。


不错,我认为这是唯一正确的方式。否则,就请考虑我1楼所说的那种情况。

#5


iocp, 单个socket同一时间多次i或多次o没有意义,多socket异步io适用。(相对多线程同步模型而言,效率高)
iocp原理上是前摄器模式。

#6


请明示。

我也纳闷这个问题。这不单单是IOCP的问题,所有的异步SOCKET工作模式都会遇到类似的问题。按照我开局的假设,对一次WSASend-s1操作,如果这个操作没有完成任务即发送完所有的数据,如果返回通知,那么后续第二个异步IO序列就开始执行了,这样就会导致实际发送情况和设计顺序不符了。导致错误。

最可靠的保证就是所有投递的异步IO在待发送队列都是有序排列阻塞式方法的,只有前面一个完成,下一个IO请求才会执行,依序执行。既保证了效率、又确保有序准确实现设计意图。那么模型这么设计的目的无非是为了不产生用户层面的阻塞,同时提高了两次IO之前的切换效率,无需从内核态转换到用户态(不过也不一定,姑且这么理解它的效率优化)。那么这就要求任何一次WSASEND请求,无论你请求多少数据1MB也好,10MB也好,都需要发送完成后,才会通知完成端口或者其他的异步IO通知发生。

不知道这么理解是否正确。

引用 1 楼 sinservice 的回复:
你设想一种情况:

  你wsasend了两次,分别称为a和b,假设a完成了一半的字节,这时a从GetIo...Status函数里反馈出来,与此同时,socket又可写了,这时完成端口不等你相应,就开始执行b的操作。

请基于这种情形考虑一下。

#7


这种做法当然没有问题。不过这不是异步IO的初衷。异步IO同样需要同步控制,但是不是这么个用法,可行但是不佳。

你有否注意到WSASend函数的参数
int WSASend(
  __in          SOCKET s,
  __in          LPWSABUF lpBuffers,
  __in          DWORD dwBufferCount,
  __out         LPDWORD lpNumberOfBytesSent,
  __in          DWORD dwFlags,
  __in          LPWSAOVERLAPPED lpOverlapped,
  __in          LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

lpBuffers可以为一个数组比如
WSASend(..,&wsaBuffer[2],2,...);
也就是说一次异步IO请求发送两块buffer出去,类似于你投递了两次WSASend请求,每次一个了。如果一次投递两个buffer,那么这里的发送顺序就是wsaBuffer[0]-->wsaBuffer[1]-->....一次发送了。

当然,这里我的问题就出现了。什么时候再完成端口上得到完成通知?会得到几个完成端口通知?
网上的说话众说纷纭,我个人认为大多都是理解错误的,或者是我的理解有问题,那么大多理解就是正确的了。我的理解,这种情况下只能出现一个完成通知,那就是wsaBuffer[1]内的数据按照其指示长度全部发送成功后,得到了底层TCP协议栈的确认后才会通知SOCKET编程应用接口:任务完成!
引用 3 楼 lzl_2008 的回复:
我也搞不清楚,但我觉得既然是异步,就不能假设它有序。
我的做法是对于同一Socket,无论是WSASend还是WSARecv,都保证同一时间只有一个。就是说只有上一个请求完成后才投递下一个请求。

#8


引用 6 楼 teleinfor 的回复:
请明示。

我也纳闷这个问题。这不单单是IOCP的问题,所有的异步SOCKET工作模式都会遇到类似的问题。按照我开局的假设,对一次WSASend-s1操作,如果这个操作没有完成任务即发送完所有的数据,如果返回通知,那么后续第二个异步IO序列就开始执行了,这样就会导致实际发送情况和设计顺序不符了。导致错误。

最可靠的保证就是所有投递的异步IO在待发送队列都是有序排列阻塞式方法的,只有前面……


我的意见是,即按照3楼所说,同时仅保证有一个写操作或读操作。否则,无法保证我1L所说之情况不发生。你说的在完成端口内“阻塞”(就是说要完成端口将所有字节完全做完),这是不可能的,完成端口不可能是这样设定的。

至于你7L所说的,当然是一个WSASend对应一个回馈,而不是一个buffer对应一个回馈,这是显而易见的。

#9


看来这个问题还是没有一个清晰的正确的结论出来。

重叠IO模型和IOCP模型都会遇到类似的问题。目前来看可以明确的就是对于每一个WSASend()操作,对应一个完成通知应该是可以明确的。不过具体发送了多少数据就成为一个问题了。那么,有一种情况我们不妨来分析一下:

WSASend(...,&wsaBuffer[3],3,...);

也就是一次WSASend投递了多个WSABUF的情况发生了。那么如果一次完成通知并没有发所有的buffer发送出去。就会出现疑问:
1 - 如果再一次的对剩余数据进行投递,那么为了保证有序数据传递。就必然要求这个SOCKET端口上不能存在其他未决的异步IO WSASend请求。如果存在,在这个间隙就有可能传递这个WSASend请求的数据了。这样就会和设计的数据传递顺序出现冲突。

如果仅仅是允许在一个端口上同时存在一个IO请求,那么这样子有何很多资料所说的异步提交请求有冲突,很显然这么做就成了一种意义上的同步方式了。

引用 8 楼 sinservice 的回复:
引用 6 楼 teleinfor 的回复:

请明示。

我也纳闷这个问题。这不单单是IOCP的问题,所有的异步SOCKET工作模式都会遇到类似的问题。按照我开局的假设,对一次WSASend-s1操作,如果这个操作没有完成任务即发送完所有的数据,如果返回通知,那么后续第二个异步IO序列就开始执行了,这样就会导致实际发送情况和设计顺序不符了。导致错误。

最可靠的保证就是所有投递的异步……

#10


看来这个问题还是没有一个清晰的正确的结论出来。

重叠IO模型和IOCP模型都会遇到类似的问题。目前来看可以明确的就是对于每一个WSASend()操作,对应一个完成通知应该是可以明确的。不过具体发送了多少数据就成为一个问题了。那么,有一种情况我们不妨来分析一下:

WSASend(...,&wsaBuffer[3],3,...);

也就是一次WSASend投递了多个WSABUF的情况发生了。那么如果一次完成通知并没有发所有的buffer发送出去。就会出现疑问:
1 - 如果再一次的对剩余数据进行投递,那么为了保证有序数据传递。就必然要求这个SOCKET端口上不能存在其他未决的异步IO WSASend请求。如果存在,在这个间隙就有可能传递这个WSASend请求的数据了。这样就会和设计的数据传递顺序出现冲突。

如果仅仅是允许在一个端口上同时存在一个IO请求,那么这样子有何很多资料所说的异步提交请求有冲突,很显然这么做就成了一种意义上的同步方式了。

引用 8 楼 sinservice 的回复:
引用 6 楼 teleinfor 的回复:

请明示。

我也纳闷这个问题。这不单单是IOCP的问题,所有的异步SOCKET工作模式都会遇到类似的问题。按照我开局的假设,对一次WSASend-s1操作,如果这个操作没有完成任务即发送完所有的数据,如果返回通知,那么后续第二个异步IO序列就开始执行了,这样就会导致实际发送情况和设计顺序不符了。导致错误。

最可靠的保证就是所有投递的异步……

#11


如果仅仅是允许在一个端口上同时存在一个IO请求,那么这样子有何很多资料所说的异步提交请求有冲突,很显然这么做就成了一种意义上的同步方式了。

===

不,这里不是同步的。同步是指,调用者必须等待调用完成。而现在,调用者可以干其他事情,而由完成端口替你完成操作。这可以大大节约调用者的线程数量,依然是异步的。

#12


翻看往年对IOCP类似问题讨论,大多是无果而终,都没有一个清晰的结论出来。

总结下来基本上对IOCP的理解:
1、 完成端口连接线程负责对客户端的连接接受,建立和完成端口的关联,投递异步操作开始IO
2、 工作线程根据CPU的数量建立一定数量的工作服务线程,负责具体IO操作的完成和通知
3、 异步IO操作的提交会进入IOCP系统缓冲队列,由系统根据线程的工作忙闲状态按照LILO方式进行调度,而对于提交的数据队列按照FIFO方式进行操作

针对理解(3)的进一步扩展:
比如同一个客户端SOCKET上面提交了多次的send请求,使用WSASend()函数。每次提交一个待发送的数据buffer,那么按照提交的顺序这些buffer会按照FIFO的原则进入IOCP的任务队列,也就是IO_PENDING任务队列,等待发送。工作线程根据系统调度情况从任务队列获取数据进行发送操作。当然工作线程的调度是按照LILO方式进行以提高线程的工作调度效率,提升整体工作效能。工作线程获取目标数据后,进入系统内核底层进行数据的发送操作,这里一旦到了底层数据发送操作就和上层的各种模型无关了。任何的数据到了底层都需要进入TCP/IP协议栈缓冲区,然后是网卡硬件缓冲区等待顺序发送,由TCP协议保证发送的可靠性。

这里追究细节,正因为对细节的不清楚,所以问题就出现了:
工作线程按照FIFO方式获取待发送buffer后,就开始提交底层进行发送。如果,这里的数据提交方式是严格按照SOCKET提交的FIFO方式往底层提交数据,并且保证提交的数据批量发送完成后,后续等待线程才有机会依序提交自己的任务数据发送,那么就可以保证数据的有序性。一旦数据提交到TCP协议栈,我们就无需关心了。TCP协议自然会保证发送有序。如果,这里的线程工作不是按照前述设想,比如前面的一个线程提交的数据没有全部提交或者没有按照请求全部发送完成,就通知完成端口了。那么这个线程就是去了而对底层资源的占有,向用户级报告提交结果,由用户决定继续提交剩余数据,这时其他线程就会抢占资源进行数据发送,就会导致发送的数据顺序和用户最初提交的数据不一致了。比如初始顺序为A-B-C,那么实际的发送顺序就可能成为A1-B-A2-C或者其他情况了。

我个人分析下来认为上述第二种假设不太可能发生,如果机制上这种情况可能。那么就十分的混乱了。每个批次发送的数据结构完全被打乱了,接收方无法对接收数据进行正确的复原。因为你自定义的包头等信息完全有可能被打乱,无法根据HEAD + DATA方式进行数据的组合。

继续探讨。看来大家对于异步IO网络编程的理解,还不是十分的彻底清楚啊。

#13


我倒是觉得ABC发送顺序,最终的接收顺序倒是有可能打乱,比如:
发送顺序:
ABC
可能的接收顺序:
ABC
ACB
BAC
BCA
CAB
CBA
但是一次请求A、B、C,应该不会出现分段发送的情况,否则就难以预料结果了。

#14


其实我很清楚,只要你连续WSASend两次,就可能会出错,所以,要保证同一时刻仅有一个WSASend或WSARecv未完成。

#15


如同磁盘和磁带的区别, 外设不同, 得到的io结果可能是不同的

#16


看看tcp粘包怎么解决的吧

#17


个人认为
对于一次WSASend,GQCS要么返回的字节数为0,要么是全部发送出去,
不会出现发送部分数据就返回(起码目前实践中还没出现)。

MSDN的IOCP例子GQCS对WSASend返回的字节数进行了判断,不知是何用意?

#18


WSASend不能保证,AcceptEx和WSARecv貌似可以这样做.

#19


引用 17 楼 stjay 的回复:
个人认为
对于一次WSASend,GQCS要么返回的字节数为0,要么是全部发送出去,
不会出现发送部分数据就返回(起码目前实践中还没出现)。

MSDN的IOCP例子GQCS对WSASend返回的字节数进行了判断,不知是何用意?


这跟你的测试环境有关,在公网,一定会出现部分完成的情况。

#20


算了,结合多方面信息总结下吧:
1、 WSASend发送操作不一定一次把提交的数据buffer发送完成,有可能出现只发送一部分的情况发生
2、 如果同时提交了多个并发的WSASend在同一个SOCKET上面,那么发送完成通知和具体发送数据的情况就难以预料了
3、 根据经验来看,在同一个时间点上保证目标SOCKET只有一个未决的WSASend或者WSARecv是合理的实现方法

仅供各位参考。有兴趣的朋友再继续研究吧。我今天弄了一个WSASend和WSARecv并发在一个SOCKET上面,都出现了程序崩溃,由于是服务进程,没有来得及跟踪到底为何发生crash。

异步IO真是个麻烦事,要研究透彻才用得好。

#21


希望本帖不会误导将来查阅的朋友。

#22


刚刚又遇到一个奇怪的问题:
我在发送数据的中间过程,投递了一个WSARecv接收异步IO操作。结果导致服务程序运行不正常,崩溃了。一直没有查找出原因来。我这么做的目的就是加入了一个心跳包,发送数据的过程当中需要检测处理Heart-beat packet

在同一个SOCKET端口上同时出现WSARecv和WSASend两个未决的操作,应该是可以的啊?应该不牵涉到同步控制等问题。

#23


引用 20 楼 teleinfor 的回复:
算了,结合多方面信息总结下吧:
1、 WSASend发送操作不一定一次把提交的数据buffer发送完成,有可能出现只发送一部分的情况发生
2、 如果同时提交了多个并发的WSASend在同一个SOCKET上面,那么发送完成通知和具体发送数据的情况就难以预料了
3、 根据经验来看,在同一个时间点上保证目标SOCKET只有一个未决的WSASend或者WSARecv是合理的实现方法

仅供各位……


同意此三点意见。

#24


引用 22 楼 teleinfor 的回复:
刚刚又遇到一个奇怪的问题:
我在发送数据的中间过程,投递了一个WSARecv接收异步IO操作。结果导致服务程序运行不正常,崩溃了。一直没有查找出原因来。我这么做的目的就是加入了一个心跳包,发送数据的过程当中需要检测处理Heart-beat packet

在同一个SOCKET端口上同时出现WSARecv和WSASend两个未决的操作,应该是可以的啊?应该不牵涉到同步控制等问题。


有可能的一个原因:

GetIo...Status函数可以在不同的线程“同时返回”,那么,对同一个套接字的Send和Recv操作的完成是可能并发处理的。

看看是不是这里的问题?

#25


分析了下,找到了可能的原因:IOCP每次操作的PER_IO_OPERATION_DATA设计的不好,导致WSASend和WSARecv共享使用了overlapped数据结构,冲突导致异常。

很多网络资料或者书籍上面对IOCP的介绍都是非常粗浅和不全面的,包括《windows网络编程》目前总结来看。第一版本的作者没有交代清楚,后来的人就是天下书籍都是一大抄。

基本上可以得出如下结论供各位后来者参考:
1、WSASend/WSRecv对目标数据的异步IO有可能会被中断,不一定一次性按照设计发送或者接收期望长度的数据。有可能需要再一次或者多次的投递异步IO操作进行数据接收或者发送;
2、如果同时提交了多个异步IO操作,不管是send还是Recv或者二者的混合。那么完成通知的顺序以及每次完成后IO的数据长度就完全不可预料,这主要是由于线程池的工作机制决定的。要完全自己组织数据的形式。
3、根据国内外讨论结果,总结出国际通行的国际经验:在同一个时刻保持在一个SOCKET端口上只有一个未决的send或者recv或者send和recv,也就是说同类型的IO操作同一个时间点上保持只有一个会比较合理,从效率和稳定性都比较容易控制。

引用 24 楼 sinservice 的回复:
引用 22 楼 teleinfor 的回复:

刚刚又遇到一个奇怪的问题:
我在发送数据的中间过程,投递了一个WSARecv接收异步IO操作。结果导致服务程序运行不正常,崩溃了。一直没有查找出原因来。我这么做的目的就是加入了一个心跳包,发送数据的过程当中需要检测处理Heart-beat packet

在同一个SOCKET端口上同时出现WSARecv和WSASend两个未决的操作,应该……