socket阻塞和非阻塞模式

本文讨论Linux下的socket套接字

一、同步、异步阻塞和非阻塞

  1. 同步:主动请求并等待IO操作完成的方式
  2. 异步:主动请求数据后,可以去处理其它任务,随后等待IO操作完毕的通知
  3. 阻塞:线程持续等待资源中数据准备完成,直到返回响应结果
  4. 非阻塞:线程直接返回结果,不会持续等待资源准备数据结束后才响应结果

总结:

  • 同步与异步是指IO操作,同步是线程等待IO的完成,异步是IO完成时线程会收到通知
  • 阻塞与非阻塞是指线程,阻塞可能发生在IO期间,也可能发生在IO之前

例如,对于一个简单的服务器程序(socket、bind、listen、accept),我们通常会说程序会阻塞在accept()处,而accept()函数的操作是同步的

二、什么是socket阻塞/非阻塞模式?

1) 建立连接 connect

  • 阻塞方式下,connect首先发送SYN请求到服务器,当客户端收到服务器返回的SYN的确认时,则connect返回,否则的话一直阻塞。
  • 非阻塞方式,connect将启用TCP协议的三次握手,但是connect函数并不等待连接建立好才返回,而是立即返回,返回的错误码为EINPROGRESS,表示正在进行某种过程。

2)接收连接 accept

  • 阻塞模式下调用accept()函数,没有新连接时,进程会进入睡眠状态,直到有可用的连接,才返回。
  • 非阻塞模式下调用accept()函数立即返回,有连接返回客户端套接字描述符。没有新连接时,将返回EWOULDBLOCK错误码,表示本来应该阻塞。

3)读写操作 read/write

  • 当读写缓冲区被填满后,阻塞模式下程序会阻塞在read/write,而非阻塞模式时read/write会直接返回错误码

三、如何设置socket阻塞/非阻塞模式?

  • socket()函数创建的socket默认是阻塞
  • socket()函数原型:
int socket(int domain, int type, int protocol);

/*1.domain:协议域,又称协议族,它决定了socket的地址类型
* 2.type:指定socket类型,它与protocol对应
* 3.protocol:指定传输协议,如IPPROTO_TCP指定传输协议为TCP,IPPTOTO_UDP指定传输协议为UDP,当protocol为0时,会自动选择type类型对应的默认协议
*/
  • 将socket设置为非阻塞模式有两种办法:
  1. 创建socket时,指定创建的socket为非阻塞(type参数中设置SOCK_NONBLOCK标志)
int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
  1. 使用fcntl()和ioctl()函数设置socket为非阻塞模式
fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);  
   
ioctl(sockfd, FIONBIO, 1);  //1:非阻塞 0:阻塞