recv()

来源:互联网 发布:c语言入门经典txt 编辑:程序博客网 时间:2024/05/15 02:51
说明我的想法之前,我先纠正一下上面我犯的几个错误,也许说明这些错误对楼主也有帮助。且听我慢慢道来,嘿嘿
我的犯的最大错误是,没有搞清楚“模式”和“选项”,BLOCK是一种模式,而TIMEOUT只是一个在特定模式下的选项。
先说模式,一般SOCKET默认是BLOCK(阻断)模式,这种模式对send函数来说是同步的,而NONBLOCK(非阻断)模式对send函数来说则是异步的。
楼主说send函数返回的错误是WSAEWOULDBLOCK,那么即是说,将SOCKET模式设置成了NONBLOCK,因为WSAEWOULDBLOCK在文档里是这么定义的:The socket is marked as nonblocking and the requested operation would block. 这句是说,socket被设置成了非阻断模式,然而程序员所要求的操作却将要导致阻断。这时,才会报WSAEWOULDBLOCK错误。
文档里还有一句话:If no buffer space is available within the transport system to hold the data to be transmitted, send will block unless the socket has been placed in a nonblocking mode. 这句是说,如果传输系统已经没有缓冲区来存储发送数据(我仍然理解为发送窗口已满),那么,除非将SOCKET设置成了NONBLOCK(非阻断)模式,否则send函数就会BLOCK(阻断)。这句话是什么意思呢?我的理解是,NONBLOCK(非阻断)模式下永远不会发生BLOCK(阻断)的情况。这就明确了我上面一篇贴子里的错误,那时我说,“此时,send函数才是真正地停止了运行,just stop!它会停止继续向发送缓存里写数据, 以后每次调用它时(因为有循环)都会发出警告说“WSAEWOULDBLOCK””,我说这句话是以SOCKET为阻断模式下为前提的,确实,send函数这个时候是停止了运行,也停止了继续向缓存里写数据,但是,程序将永远停留在这个send调用上,send函数也不会返回,于是后面的循环也不可能发生,直到发送窗口有了空间并将核心驱动模块缓存里的所有数据(注1)填入其中,程序才会跳过刚才的send调用而接着执行循环。导致这种一直阻断的原因大多是由于接收端的问题,所以我在我的程序里设置了TIMEOUT的选项,让send等待一段时间,等超过这段时间后如果还是阻断,那么就报TIMEOUT错误,好跳过这个send调用,继续执行循环。
[注1] 注意,是所有数据,在阻断模式下,如果要求发送的数据不能一次全部填入发送窗口,那么send就会阻断。文档里有这么两句话:
    On nonblocking stream oriented sockets, the number of bytes written can be between 1 and the requested length.
    If no error occurs, send returns the total number of bytes sent, which can be less than the number indicated by len for nonblocking sockets.
这两句的意思是,只有在面向流的非阻断模式下,send才可能会返回1到要求发送数据大小之间的一个数。言外之意,阻断模式下要不就成功返回要求发送的所有数据的大小,要不就一个字节也不发送并且永远停留在这次send调用上,再没有其他的可能性了。这才是BLOCK(阻断)的真正含义,所谓的“同步”也正是这个意思——执行完才能过去,否则没门儿!
好,现在就可以分析一下楼主的解决方案了。
先分析第一条:“首次发生WSAEWOULDBLOCK时,应该记录下此时的状态,从此时起,就不应该再调用send函数了,而是将待发送的数据以自己的方式存储起来.”既然发生了WSAEWOULDBLOCK错误,那么就说明楼主将SOCKET设置成了非阻断模式,这种模式有这个好处:哪怕发送窗口还有1byte的空间,那么send就将1byte的数据填入发送窗口,从而正常返回1,未发送出去的数据就交给核心驱动模块去发送(和本次send调用就没什么关系了),后面的循环也得以继续执行,这就是NONBLOCK模式下所谓的“异步”!其实是看起来像异步。而楼主所说的“将待发送的数据以自己的方式存储起来”是没有必要的,因为系统会自动存储这些未发送的数据,等待发送窗口有了空间再将其发送出去。
后面的两条方案是在SOCKET为阻断模式下才有必要实施的,但是,阻断模式下系统也会自动把本次要发送的所有数据暂存下来,等待发送窗口有了空间就能将其发送出去,然后send就会返回指定发送的数据大小,一切也都是自动的,不需要人为控制。而且,等待FD_WRITE消息是个消极的措施(因为如果导致BLOCK的因素只是暂时的话,系统迟早会将所有数据发送出去,那时候自然就会得到FD_WRITE的消息),我认为这个时候最好将选项设为TIMEOUT,不会因为本次send的阻断而影响了后续send的执行[注2],这就相当于人为“制造”了一个NONBLOCK(非阻断)模式下的发送机制,
[注2]当然,这样是要消耗内存的。因为,如果上次的阻断一直没有得到解决(这个解决过程是系统自动完成的),后续的send调用理所当然仍然还是阻断,而我们后续的千千万万个send函数仍然在向本地缓存里写数据,占用内存就是在自然不过的事情了。(这时只好祈祷接收端赶快处理好它的问题,好让我们的“士兵”不要老挤在“护城河”里出不去了,呵呵)。
就这些了,上面说的可能过于罗嗦,但为了把我的意思表达清楚,也不得不这么做了,呵呵,如果有什么不同的见解,还是希望提出来,大家一起分析讨论。谢谢~