阻塞与非阻塞设置

基本概念:

阻塞IO::

socket 的阻塞模式意味着必须要做完IO 操作(包括错误)才会返回。

非阻塞IO::

非阻塞模式下无论操作是否完成都会立刻返回,需要通过其他方式来判断具体操作是否成功。

 

IO模式设置:
一般对于一个socket 是阻塞模式还是非阻塞模式两种方式::

 方法1、fcntl 设置;

方法2、recv,send 系列的参数。(读取,发送时,临时将sockfd或filefd设置为非阻塞)

 

方法一、fcntl 函数可以将一个socket 句柄设置成非阻塞模式:
flags = fcntl(sockfd, F_GETFL, 0); //获取文件的flags值。

 fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); //设置成非阻塞模式;

flags = fcntl(sockfd,F_GETFL,0);

fcntl(sockfd,F_SETFL,flags&~O_NUNBLOCK); //设置成阻塞模式;

设置之后每次的对于sockfd 的操作都是非阻塞的。

 

方法二、recv, send 函数的最后有一个flag 参数可以设置成MSG_DONTWAIT

临时将sockfd 设置为非阻塞模式,而无论原有是阻塞还是非阻塞

recv(sockfd, buff, buff_size,MSG_DONTWAIT); //非阻塞模式的消息发送

send(scokfd, buff, buff_size, MSG_DONTWAIT); //非阻塞模式的消息接受

 

 

阻塞与非阻塞的区别: //阻塞和非阻塞的区别在于没有数据到达的时候是否立刻返回.

读(read/recv/msgrcv):

读的本质来说其实不能是读,在实际中, 具体的接管数据不是由这些调用来进行,是由于系统底层自动完成的。read 也好,recv 也好只负责把数据从底层缓冲copy 到我们指定的位置.

对于读来说(read, 或者recv) ::

阻塞情况下::

在阻塞条件下,read/recv/msgrcv的行为::

1、如果没有发现数据在网络缓冲中会一直等待,

2、当发现有数据的时候会把数据读到用户指定的缓冲区,但是如果这个时候读到的数据量比较少,比参数中指定的长度要小,read 并不会一直等待下去,而是立刻返回

read 的原则::数据在不超过指定的长度的时候有多少读多少,没有数据就会一直等待

所以一般情况下::我们读取数据都需要采用循环读的方式读取数据,因为一次read 完毕不能保证读到我们需要长度的数据,

read 完一次需要判断读到的数据长度再决定是否还需要再次读取

非阻塞情况下::

在非阻塞的情况下,read 的行为::

1、如果发现没有数据就直接返回,

2、如果发现有数据那么也是采用有多少读多少的进行处理

所以::read 完一次需要判断读到的数据长度再决定是否还需要再次读取

 

对于读而言:: 阻塞和非阻塞的区别在于没有数据到达的时候是否立刻返回.
recv 中有一个MSG_WAITALL 的参数::

recv(sockfd, buff, buff_size, MSG_WAITALL),
在正常情况下recv 是会等待直到读取到buff_size 长度的数据,但是这里的WAITALL 也只是尽量读全,在有中断的情况下recv 还是可能会被打断,造成没有读完指定的buff_size的长度。

所以即使是采用recv + WAITALL 参数还是要考虑是否需要循环读取的问题在实验中对于多数情况下recv (使用了MSG_WAITALL)还是可以读完buff_size

所以相应的性能会比直接read 进行循环读要好一些。

 

注意:: //使用MSG_WAITALL时,sockfd必须处于阻塞模式下,否则不起作用。

//所以MSG_WAITALL不能和MSG_NONBLOCK同时使用。

要注意的是使用MSG_WAITALL的时候,sockfd 必须是处于阻塞模式下,否则WAITALL不能起作用。

 

 

阻塞与非阻塞的区别: //
写(send/write/msgsnd)::

写的本质也不是进行发送操作,而是把用户态的数据copy 到系统底层去,然后再由系统进行发送操作,send,write返回成功,只表示数据已经copy 到底层缓冲,而不表示数据已经发出,更不能表示对方端口已经接管到数据.
对于write(或者send)而言,

阻塞情况下:: //阻塞情况下,write会将数据发送完。(不过可能被中断)

阻塞的情况下,是会一直等待,直到write 完,全部的数据再返回这点行为上与读操作有所不同。

原因::

读,究其原因主要是读数据的时候我们并不知道对端到底有没有数据,数据是在什么时候结束发送的,如果一直等待就可能会造成死循环,所以并没有去进行这方面的处理;

写,而对于write, 由于需要写的长度是已知的,所以可以一直再写,直到写完.不过问题是write 是可能被打断吗,造成write 一次只write 一部分数据, 所以write 的过程还是需要考虑循环write, 只不过多数情况下一次write 调用就可能成功.

 

非阻塞写的情况下:: //

非阻塞写的情况下,是采用可以写多少就写多少的策略.与读不一样的地方在于,有多少读多少是由网络发送的那一端是否有数据传输到为标准,但是对于可以写多少是由本地的网络堵塞情况为标准的,在网络阻塞严重的时候,网络层没有足够的内存来进行写操作,这时候就会出现写不成功的情况,阻塞情况下会尽可能(有可能被中断)等待到数据全部发送完毕, 对于非阻塞的情况就是一次写多少算多少,没有中断的情况下也还是会出现write 到一部分的情况.


1. windows平台上无论利用socket()函数还是WSASocket()函数创建的socket都是阻塞模式的:

  1. SOCKET WSAAPI socket(  
  2.   _In_ int af,  
  3.   _In_ int type,  
  4.   _In_ int protocol  
  5. );  
  6.   
  7. SOCKET WSASocket(  
  8.   _In_ int                af,  
  9.   _In_ int                type,  
  10.   _In_ int                protocol,  
  11.   _In_ LPWSAPROTOCOL_INFO lpProtocolInfo,  
  12.   _In_ GROUP          g,  
  13.   _In_ DWORD         dwFlags  
  14. );  
SOCKET WSAAPI socket(
  _In_ int af,
  _In_ int type,
  _In_ int protocol
);

SOCKET WSASocket(
  _In_ int                af,
  _In_ int                type,
  _In_ int                protocol,
  _In_ LPWSAPROTOCOL_INFO lpProtocolInfo,
  _In_ GROUP          g,
  _In_ DWORD         dwFlags
);

Linux平台上可以在利用socket()函数创建socket时指定创建的socket是异步的:

  1. int socket(int domain, int type, int protocol);  
int socket(int domain, int type, int protocol);

在type的参数中设置SOCK_NONBLOCK标志即可,例如:

  1. int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);  
int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
2. 另外,windows和linux平台上accept()函数返回的socekt也是阻塞的,linux另外提供了一个accept4()函数,可以直接将返回的socket设置为非阻塞模式:
  1. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);  
  2.    
  3. int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);  
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
 
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

只要将accept4()最后一个参数flags设置成SOCK_NONBLOCK即可。

3. 除了创建socket时,将socket设置成非阻塞模式,还可以通过以下API函数来设置:

linux平台上可以调用fcntl()或者ioctl()函数,实例如下:

  1. fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);  
  2.    
  3. ioctl(sockfd, FIONBIO, 1);  //1:非阻塞 0:阻塞  
fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);
 
ioctl(sockfd, FIONBIO, 1);  //1:非阻塞 0:阻塞

参考: http://blog.sina.com.cn/s/blog_9373fc760101i72a.html

但是网上也有文章说(文章链接:http://blog.csdn.net/haoyu_linux/article/details/44306993),linux下如果调用fcntl()设置socket为非阻塞模式,不仅要设置O_NONBLOCK模式,还需要在接收和发送数据时,需要使用MSG_DONTWAIT标志,在recv,recvfrom和send,sendto数据时,将flag设置为MSG_DONTWAIT。是否有要进行这种双重设定的必要,笔者觉得没有这个必要。因为linux man手册上recv()函数的说明中关于MSG_DONTWAIT说明如下:

Enables nonblocking operation; if the operation would block, the call fails with the error EAGAIN or EWOULDBLOCK (this can also be enabled using the O_NONBLOCK flag  with the F_SETFL fcntl(2)).

通过这段话我觉得要么通过设置recv()函数的flags标识位为MSG_DONTWAIT,要么通过fcntl()函数设置O_NONBLOCK标识,而不是要同时设定。

windows上可调用ioctlsocket函数:
  1. int ioctlsocket(  
  2.   _In_    SOCKET s,  
  3.   _In_    long   cmd,  
  4.   _Inout_ u_long *argp  
  5. );  
int ioctlsocket(
  _In_    SOCKET s,
  _In_    long   cmd,
  _Inout_ u_long *argp
);
将cmd参数设置为 FIONBIO,*argp=0即设置成阻塞模式,而*argp非0即可设置成非阻塞模式。但是windows平台需要注意一个地方,如果你对一个socket调用了WSAAsyncSelect()或WSAEventSelect()函数后,你再调用ioctlsocket()函数将该socket设置为非阻塞模式,则会失败,你必须先调用WSAAsyncSelect()通过设置lEvent参数为0或调用WSAEventSelect()通过设置lNetworkEvents参数为0来分别禁用WSAAsyncSelect()或WSAEventSelect()。再次调用ioctlsocket()将该socket设置成阻塞模式才会成功。因为调用WSAAsyncSelect()或WSAEventSelect()函数会自动将socket设置成非阻塞模式。msdn上的原话是:

The WSAAsyncSelect and WSAEventSelect functions automatically set a socket to nonblocking mode. If WSAAsyncSelect or WSAEventSelect has been issued on a socket, then any attempt to use ioctlsocket to set the socket back to blocking mode will fail with WSAEINVAL.


To set the socket back to blocking mode, an application must first disable WSAAsyncSelect by calling WSAAsyncSelect with the lEvent parameter equal to zero, or disable WSAEventSelect by calling WSAEventSelect with the lNetworkEvents parameter equal to zero.

网址:https://msdn.microsoft.com/en-us/library/windows/desktop/ms738573(v=vs.85).aspx



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值