阻塞模式下的send、recv、close总结

来源:互联网 发布:qq空间网络音乐 编辑:程序博客网 时间:2024/05/17 22:16
http://blog.csdn.net/feimashenhua/article/details/5444165


对于send函数:


    send函数只负责将数据提交给协议层。
   当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲区的长度,如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;
    如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据;
    如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么 send就比较s的发送缓冲区的剩余空间和len;
    如果len大于剩余空间大小,send就一直等待协议把s的发送缓冲中的数据发送完,如果len小于剩余空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。
    如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;
    如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。
    如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket 函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)  


对于recv函数:


    recv 先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果 recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0
    对方优雅的关闭socket并不影响本地recv的正常接收数据;如果协议缓冲区内没有数据,recv返回0,指示对方关闭;如果协议缓冲区有数据,则返回对应数据(可能需要多次recv),在最后一次recv时,返回0,指示对方关闭;




对于"关闭连接"
socket连接的关闭分为:"优雅关闭"和"强制关闭";
MSDN上有说明:closesocket的关闭动作依赖于socket选项SO_LINGER和 SO_DONTLINGER;(SO_DONTLINGER为缺省值),其含义如下:
选项          阻塞时间  关闭方式  等待关闭与否
SO_DONTLINGER 不关心    优雅      否
SO_LINGER     零        强制      否
SO_LINGER     非零      优雅      是
MSDN上,还有说明:为了确保数据能被对方接收,应用程序应当在调用closesocket之前调用shutdown。
closesocket应最后被调用,以便系统释放socket句柄及相关资源。


 


粘包、丢包 及TCP信息收发


初涉socket编程的朋友经常有下面一些疑惑:
1. 为什么我发了3次,另一端只收到2次?
2. 我每次发送都成功了,为什么对方收到的信息不完整?


这些疑惑往往是对send和recv这两个函数理解不准确所致。send和recv都 提供了一个长度参数。对于send而言,这是你希望发送的字节数,而对于recv而言,则是希望收到的最大字节数。


1。 send


send 函数的原型是:int send(SOCKET sd, const char * buffer, int len, int flag). 其中len指出buffer中包含的实际字节数,也是程序员希望发出的最大字节数。而这个函数的返回值是实际发送出去的字节数。在网络程序中,正常情况是 这个返回值小于len,也就是说buffer中的内容没有完全被发送出去。


为了确保一个缓冲区内的内容被完全被发送出去,我们需要如下代 码:


int res;
int pos = 0; //下一次发送需要开始的位置:
while(pos < len)
{
res = send(sd, buffer + pos , len - pos, 0);
if(res <=0) goto err_handler; //去错误处理
pos += res;
}


这 样经过多次send,可以确保buffer内的内容都别发送出去。


为了避免发送线程被阻塞,应该考虑把上述代码放到一个子线程中,并通过 队列来缓冲所有收发。


2. recv


recv的原型是:int recv(SOCKET sd, char * buffer, int len, int flag),其各个参数的含义同前面send。需要注意的是,系统并不会等待bufer被填满了再返回,而是一旦有数据被收到,就立刻返回。因此不要期望 实际收到的数据长度就等于len。


你可以使用前面send的循环算法确保收到len个字节,也可以使用内容驱动的方法实现分段数据分析。 不过这个就超出本文内容,也就不再赘述。


3. 粘包
所谓粘包,是指发送端发送的两个报文,在接收端被拼在一起。由于TCP是面向 流的协议,报文与报文之间是没有分界符号的。在接收端,所有的数据都逻辑上拼在一起给你。举例来说,你分10次发送10个长度为10的报文,在接收端,你 可能只收到一个长度为100的报文,而不会收到10个消息。


为了解决这个问题,你必须在接收端有能力把这些报文分隔开来。如果消息长度总 是固定的,这就比较容易,只要按长度取出即可。如果长度不固定,一般有两种方法解决:


a)使用特征字节。例如:如果是聊天程序,发送的是 普通文本,一些字符是绝对不可能出现在正文中的,你可以使用这些字符做分隔符隔离不同消息。我们可以使用'/0'做分隔,一般对于全文本传输是比较安全 的。


b) 在发送方发送正文前,先发送一个长度。例如你要发送2345字节的内容,你可以先发送一个2字节的长度给对方,然后再发正文。接收放只要收到这个长度信 息,就可以正确的分包。需要注意的,到底用多少字节来发送长度是应该预先约定的,一般2字节就足够,不过你约定4字节也是可以的。还要注意的是,如果接收 一次报文后,解包完毕还剩下一部分内容,这些内容应该留给下次报文分包使用,而不能扔掉。


4. 丢包
丢包一般都是由于对上面说的 理解不足引起的,因为TCP本身是确保不丢包的。












关于socket阻塞与非阻塞情况下的recv、seng、read、write返回值问题 
1、阻塞模式与非阻塞模式下recv的返回值各代表什么意思?有没有区别?(就我目前了解阻塞与非阻塞recv返回值没有区分,都是<0:出错,=0:连接关闭,>0接收到数据大小,特别:返回值<0时并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的,继续接收。只是阻塞模式下recv会阻塞着接收数据,非阻塞模式下如果没有数据会返回,不会阻塞着读,因此需要循环读取)。


2、阻塞模式与非阻塞模式下write的返回值各代表什么意思?有没有区别?(就我目前了解阻塞与非阻塞write返回值没有区分,都是<0:出错,=0:连接关闭,>0发送数据大小,特别:返回值<0时并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的,继续发送。只是阻塞模式下write会阻塞着发送数据,非阻塞模式下如果暂时无法发送数据会返回,不会阻塞着write,因此需要循环发送)。


3、阻塞模式下read返回值 < 0 && errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN时,连接异常,需要关闭,read返回值 < 0 && (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)时表示没有数据,需要继续接收,如果返回值大于0表示接送到数据。
   非阻塞模式下read返回值 < 0表示没有数据,= 0表示连接断开,> 0表示接收到数据。
   这2种模式下的返回值是不是这么理解,有没有跟详细的理解或跟准确的说明?


4、阻塞模式与非阻塞模式下是否send返回值< 0 && (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)表示暂时发送失败,需要重试,如果send返回值<= 0, && errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN时,连接异常,需要关闭,如果send返回值 > 0则表示发送了数据?send的返回值是否这么理解,阻塞模式与非阻塞模式下send返回值=0是否都是发送失败,还是那个模式下表示暂时不可发送,需要重发?


5、很多人说阻塞模式下read会阻塞着读,是否这样?我和同事试了不会阻塞read。


6、网络上找了很多资料,说的都很笼统,就分大于0,小于0,等于0,并没有区分阻塞与非阻塞,更没有区分一个错误号,希望哪位高手能按上面的问题逐条回答一下,越详细越好,平时少上CSDN,分少,见谅。






IO操作选择阻塞还是不阻塞,主要还是依赖设备是否为低速设备。
read一般都是非阻塞的,读不到就返回。


recv你的理解差不多,其实也是read,只不过读取的设备是socket接收缓冲区而已。


send不管它是不是阻塞,它发送成功只表示数据写入缓冲区,不能代表数据被对面成功接受。 

原创粉丝点击