网上大多数的建议都是WSASEND采用线性模式,即建立一个发送缓冲,当上一次send完成之后,再进行下一次的投递。那么WSASEND什么情况下会出现部分发送呢?
在MSDN中IOCP的列子是对得到的发送的字节值进行了判断的,而在wsaSend函数的描述中也有这样一句:Note The successful completion of a WSASend does not indicate that the data was successfully delivered.
我首先想到的是当发送缓冲区不足的时候,会不会造成wsaSend部分发送返回。做了个实验,连续发送10M的数据(肯定大于缓冲区了)。第一次直接返回成功(对端并未进行Recv),第二次返回IO_PENDING.看来不是这样的。查了《windows 网络编程技术》其中有这样一段话:
When an application makes a send call, if there is sufficient buffer space, the data is copied into the socket's send buffers, the call completes immediately with success, and the completion is posted. On the other hand, if the socket's send buffer is full, then the application's send buffer is locked and the send call fails withWSA_IO_PENDING. After the data in the send buffer is processed (for example, handed down to TCP for processing), then Winsock will process the locked buffer directly. That is, the data is handed directly to TCP from the application's buffer and the socket's send buffer is completely bypassed。
当发送缓冲不足的时候,会内存锁定,我另一端调用recv,收到wsasend的完成信号时,发送的字节数=要发送的字节数,并没有部分发送。
下面是我网上找到的一片帖子忘了出自哪里了。
---------------------------------------------------------------------------------------------------------
对于WSASend使用,一直有些疑惑,虽然对开发影响不大,但是总是很别扭。
疑惑1:
按照MSDN的说法:
1)不必等待WSASend发送成功,可以连续调用WSASend发送数据。
2)可以给WSASend提供一个Buffer数组,一次发送多个不连续的缓冲区
3)使用WSASend发送成功后,提供的数据不保证能够被全部发送出去
这样是否存在这样的问题:
假如我连续投递了5个WSASend发送数据,如果第3个WSASend的数据没有完全发送出去,而第4个WSASend又被接受,岂不是导致错误,因为系统无法得知我的第4个WSASend何时投递。
如果第3个发送数据的第2个参数是一个Buffer数组,我为了发送剩余数据,岂不要检查到底发送了几个Buffer?
为了保险起见,我的项目中没有连续投递过WSASend,也没有使用过多Buffer的功能,而是老老实实地在WSASend发送成功后,检查数据是否发送完全,如果没有,继续发送剩余数据,直到一次数据全部发送出去后,才发送下一个数据包。
疑惑2:
数据发送成功的含义(WSASend调用返回STATUS_SUCCESS或完成例程被调用或完成例程被调用或在完成端口上dequeue了一个完成包),可能情况:
1)数据被提交到tdi Client(AFD),就认为数据发送成功了
2)数据被提交到到tdi Server(如TCP),加入tcp的发生队列,就认为数据被发送成功了
3)数据被提交到网卡的发送缓冲区,就认为数据发送成功了
4)数据被网卡发送出去,就认为发送成功了
5)数据被对方成功接收,收到确认,就表示发送成功了。
以上情况到底属于那一种呢?按照MSDN的说法,发送请求被传输层消费掉了,就认为发送成功了,不知大家是如何理解这句话。
对于以上两个疑问,网络上也是没有一个定论,看来要搞清楚以上两个问题,不深入windows源码是无解了。
先说说WSASend的调用过程吧(基于NT4源码),源码就不贴了,免得MS找麻烦:
WSASend->WSPSend->NtDeviceIoControlFile->AFDSend【Tdi Client】->TcpSendData【Tdi Server】->TdiSend->TcpSend->IPTransmit【Network Layer】->SendIPPacket->下面进入链路层,没有找到相关源码
NtDeviceIoControlFile:
将发送请求和完成例程被包装成IRP,发送给"device/afd"
AFDSend:
根据buffer数组生成MDL链
如果TDI不支持数据缓冲,这里要将数据缓冲下来
调用TdiBuildSend构造发送到tdi的发送请求IRP
将生成新的IRP发送到“device/tcp”
AFDSend要么将完整数据提交到Tdi,要么失败,这里不会导致发送部分数据
TcpSendData:
构造TdiRequest并调用TdiSend处理,没有数据缓冲
TdiSend:
构造TcpRequest,并将该Request挂入TCB(TCP的传输控制块)的发送队列
调用TCPSend进一步处理
返回TDI_PENDING
该部分也不会导致数据不完整发送。
TCPSend:
检查TCB中发送队列的情况,决定是否启动一次发送,如果不满足发送条件,就返回了
如果符合发送条件,就构造TCP数据包,发送数据,这个过程比较复杂,多为TCP协议的细节处理
可以看出,WSASend一般到TCPSend的开始部分就返回了,TCPSend本身无返回值,是由TdiSend调用完后就直接返回了Pending。
从源代码上看,除了发送的数据的字节为0,否则WSASend是不会返回STATUS_SUCCESS,不出错的话,一定是返回Pending状态
但是应用层何时收到发送成功通知呢?
我们知道,完成例程指针被存在了最上层的那个Irp里了,在执行IoCompleteRequest的时候,完成例程会被调用,细节就不说了,检索源代码,有两个地方会导致IoCompleteRequest被最终调用,一个是链路层调用IP层的完成例程的时候,一层层调用下去,最终导致最上层的那个IRP的完成例程被调用,另一个是再处理TcpReceive的ACK的时候,也有可能完成掉一些发送请求。
结论:
纵观NT4源代码,没有发现WSASend发送部分数据的可能(也许有,我没看出来)
基于WSASend不会发送部分数据,WSASend的确可以重叠发送(按照投递顺序将发送请求挂入TCB的发送队列),不必串行,在一定程度上的确能够提高效率。
所谓发送完成,应该是链路层调用了上层的完成例程,但是链路层何时调用上层的完成例程,由于源代码缺乏,不得而知,请知情者赐教!
--------------------------------------------------------------------------------------
看来WSASend是在把请求放入TCB队列就返回了。Google过一些英文网站,得到类似的回答
Actually, a partial overlapped send guarantees all subsequently
scheduled sends will completely fail (assuming a TCP socket).
A partial overlapped send will never happen in practice, however,
due to the implementation of the socket buffer (partial sends don't
exist in Microsoft's WinSock implementations so far). There's an
exception if you set the send buffer size to zero - then your overlapped
buffers replace the socket buffer and the socket may break part way
through an overlapped buffer.
貌似连续wsaSend是可行的。
再看MSDN 有这样一段话
For non-overlapped sockets, the last two parameters (lpOverlapped,lpCompletionRoutine) are ignored andWSASend adopts the same blocking semantics assend. Data is copied from the buffer(s) into the transport's buffer. If the socket is non-blocking and stream-oriented, and there is not sufficient space in the transport's buffer,WSASend will return with only part of the application's buffers having been consumed. Given the same buffer situation and a blocking socket,WSASend will block until all of the application buffer contents have been consumed.
之前的理解有误,这段话应该联合起来理解,对于未使用overlapped的socket,最后两个参数是被忽略的 这时候wsasend表现就和send一样,数据被拷贝到发送缓冲区,在这种情况下(wsasend像send) 如果是面向流的nonblockingmodel的套接字 并且发送缓冲区不足的情况下,wsasend返回拷贝到发送缓冲区的字节数,如果是blocking socket wsasend知道发送完毕才返回。(这个行为和send是一致的,也就是只有在这种情况下wsaSend才会部分发送)
什么是non-overlapped sockets?之前一直把non-overlapped sockets 当做blocking socket .
再看windows网络编程
Blocking sockets cause concern because any Winsock API call on a blocking socket can do just that—block for some period of time. Most Winsock applications follow a producer-consumer model in which the application reads (or writes) a specified number of bytes and performs some computation on that data.
Once a socket is placed in non-blocking mode, Winsock API calls that deal with sending and receiving data or connection management return immediately. In most cases, these calls fail with the error WSAEWOULDBLOCK, which means that the requested operation did not have time to complete during the call. For example, a call torecv returnsWSAEWOULDBLOCK if no data is pending in the system's input buffer. Often additional calls to the same function are required until it encounters a successful return code
No –Blocking socket是不同于non-overlapped socket
之前对于overlap的理解还是有些误区
现在的理解是 overlap是一种异步使用方式 与block no blocking 不是一个感念。
overlapIO不只是socket,还包括readfile等等同样iocp对应的也不只nonblockingsocket而是 overlapIO
Overlapped sockets merely means that you can use the sockets for overlapped IO. It doesn't mean that your socket is non-blocking.
完成端口和重叠IO的例子都没有指定socket必须是non-blocking socket.
non-blocking socket 是指不满足当前条件的情况下 返回,当满足当前需求,还是操作完成才返回,例如将发送缓冲区填满。
而overlap socket是只要当前情况不能立即执行完毕 便会返回pending 也就是说 在发送缓冲区填满前就已经返回了。
在我自己的测试程序中 我把发送和接受缓冲区大小都设置为了0 所以每次发送返回都是peding,系统锁定发送缓冲,当发送完成后得到完成通知。
所以重叠IO和完成IO 与block socket non blocking socket 是两码事 ,我接下来测试下用blocksocket 与non blockingsocket 在iocp上有什么影响 ,个人现在感觉应该是一样的。(经过测试 用blocking socket iocp仍可正常工作,单non-overlapped socket不行 默认的socket()创建出的是支持overlapped的)
现在的系统使用non-blocking socket必须使用overlap模式
没想到这篇文章写了一年之后才来更新 呵呵 写的比较乱 原谅
- 下一篇:Rpc远程过程调用
-
7楼
tanweiwu2012-05-15 21:52发表 [回复] [引用][举报]
-
-
在网络很差的情况下,
WSASend也能一次性发完所有数据吗?
然后给一个完成通知???-
Re:
TANG_XIAO_BIN2012-07-23 01:57发表 [回复] [引用][举报]
-
- 回复tanweiwu:貌似数据不是发送成功后才通知的,而是在数据离开本地之前通知,具体什么时候不得而知
-
-
6楼
Squall_zy2012-05-11 17:31发表 [回复] [引用][举报]
-
-
你的实验不够彻底,文中有部分错误。
1、WSASend的确不能只发送部分数据。但这和Overlapped模式无关,无论阻塞模式、非阻塞模式、Overlapped、非Overlapped,WSASend的表现都一样。
2、当你的发送内容比缓冲区大,但你依然发送成功了,你没想过为什么嘛?这部分数据是被拷贝了,还是被锁定了?为什么第二次的WSASend会遇到WSAEWOULDBLOCK错误?为什么和第一次不一样?你想过为什么嘛?你提出了这个问题,但是你没有解释清楚。
-
5楼
diligent902012-03-29 21:06发表 [回复] [引用][举报]
-
-
读了您的文章对我有很多的帮助。
请问,您是如何判断自己的IOCP是blocking socket,我得IOCP一定是异步IO,也是就no-blocking socket,非阻塞模式???
-
4楼
sinservice2012-03-17 01:29发表 [回复] [引用][举报]
-
-
lz共享一下NT4源代码吧
codechurch@gmail.com
我跟你一起读读吧。