转载地址:https://www.cnblogs.com/charlesblc/p/6202402.html
看我之前的文章就知道,一般对于网络读的socket,都会加上O_NONBLOCK,非阻塞的选项。
int setnonblocking(int fd) {
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
为什么要加上呢。是为了效率。下面详细说一下阻塞和非阻塞。
基本概念:
阻塞IO: 必须做完IO操作才会返回。
非阻塞IO:操作成功与否,都会返回,需要通过其他方式判断具体操作是否成功。
读
阻塞与非阻塞的区别:没有数据到达的时候,是否立刻返回。
读(read, recv, msgrcv):
注意,这里的读,只是负责把数据从底层系统缓存copy到我们指定的位置。实际的数据到达是系统做的。
阻塞情况(read, recv, msgrcv的行为):
如果没有数据,会一直等待;
有数据时候会读到用户指定的缓存区,但是如果数据量比较少,少于参数指定的大小,read也会立即返回,而不会一直等到数据足够。
阻塞读的原则:数据不超过指定长度的时候,有多少读多少,没有数据就会一直等待。
所以一般情况下,都需要采用循环读的方式,因为一次read不能保证读完需要的全部数据。
非阻塞情况(read, recv, msgrcv的行为)
没有数据,就立即返回;
有数据,也是采用有多少读多少的方式来处理。
所以,read完一次,要判断读到的数据长度或者错误码再决定是否再次读取。注意这里的EAGAIN错误码是需要继续读取,而返回0是对方已关闭连接。
复制代码
对非阻塞socket而言,EAGAIN不是一种错误。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。错误信息为Resource temporarily unavailable,errno代码为11(EAGAIN)。
如果出现EINTR即errno为4,错误描述Interrupted system call,操作也应该继续。
EINTR指操作被中断唤醒,需要重新读/写。
而EAGAGIN不需要重新读/写已经操作的数据。
最后,如果recv的返回值为0,那表明连接已经断开,我们的接收操作也应该结束。
复制代码
综上,对于读而言,阻塞与非阻塞的区别在于,没有数据到达的时候是否立刻返回。
而recv函数有一个 MSG_WAITALL的参数。
recv(sockfd, buff, buff_size, MSG_WAITALL);
这个参数意味着recv会争取等到数据填满buff_size再返回,但是如果有中断的情况, recv还是会被大端,造成没有读完buff_size的长度。
所以即使采用了recv+MSG_WAITALL的方式,还是要循环读取,当然在大多数情况下是能读满的。
注意:MSG_WAITALL只能在阻塞模式下使用,和非阻塞模式不能同时使用。
写
写(write/send/msgsnd)的本质也是把用户态数据copy到系统底层去,然后由系统进行发送和实际写操作。只要完成了copy,就意味着写完成。
阻塞情况(write/send/msgsnd的行为)
与阻塞读有多少读多少不同的是,阻塞写会一直阻塞,直到所有数据都完成,再返回。
这是因为,读的时候不知道需要读多少,防止一直等不到足够的数据;而写的时候是知道要写多少数据的。不过也可能被中断,大多数情况是能够写完的。
非阻塞情况(write/send/msgsnd的行为)
非阻塞写,就是有多少写多少。能够写多少是根据本地网络拥塞情况为标准的,当网络拥塞严重的时候,网络层没有足够的内存来进行写操作,就会出现写不完的情况;这时候,阻塞写除非被中断,都会等到数据都写完;而非阻塞写,就是能写多少算多少。
IO模式设置方式
Socket:
方法1: 文章开始的方式,对flags加O_NONBLOCK; (注:如果想设置成非阻塞,这样:flags&~O_NONBLOCK)
方法2: recv, send函数的参数,最后一个参数设置成 MSG_DONTWAIT,如下:
recv(sockfd, buff, buff_size, MSG_DONTWAIT);
send(sockfd, buff, buff_size, MSG_DONTWAIT);
对当次的函数,为非阻塞。
普通文件:
方法1: open函数的第二个参数加上 O_NONBLOCK,函数说明如下:
复制代码
open函数用来打开或创建一个文件,若成功返回文件描述符,否则返回-1。
pathname是要打开或创建文件的名字。
oflag参数是下列一个或多个常量执行按位或运算的结果杀
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开
上面三个常量必须指定一个并且只能指定一个,下面一些常量则是可选的:
O_APPEND 将写入追加到文件的尾端
O_CREAT 若文件不存在,则创建它。使用该选项时,需要第三个参数mode,用来指定新文件的访问权限位
O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则会出错
O_TRUNC 如果此文件存在,而且为只写或读写模式成功打开,则将其长度截短为0
O_NOCTTY 如果pathname指的是终端设备,则不将该设备分配作为此进程的控制终端
O_NONBLOCK 如果pathname指的是一个FIFO文件、块设备文件或字符设备文件,则此选项将文件的本次打开操作和后续的I/O操作设置为非阻塞模式
复制代码
方法2,同socket的方法1,用F_SETFL和flags|O_NONBLOCK.
消息队列:
msgsnd和msgrcv的最后一个参数加上 IPC_NOWAIT:
复制代码
int msgsnd (int msqid, const void *ptr, size_t length, IPC_NOWAIT) ;
参数flag的值可以指定为IPC_NOWAIT。这类似于文件IO的非阻塞IO标志。若消息队列已满,则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN。
如果没有指定IPC_NOWAIT,则进程阻塞直到下述情况出现为止:①有空间可以容纳要发送的消息 ②从系统中删除了此队列(返回EIDRM“标识符被删除”)③捕捉到一个信号,并从信号处理程序返回(返回EINTR)
ssize_t msgrcv (int msqid, void* ptr, size_t length, long type, IPC_NOWAIT) ;
参数ptr指定所接收消息的存放位置。参数length指定了数据部分大小(只想要多长的数据)
参数type指定希望从队列中读出什么样的消息。
type == 0 返回队列中的第一个消息
type > 0 返回队列中消息类型为type的第一个消息
type < 0 返回队列中消息类型值小于或等于type绝对值的消息,如果这种消息有若干个。则取类型值最小的消息。
(如果一个消息队列由多个客户进程和一个服务器进程使用,那么type字段可以用来包含客户进程的进程ID)
参数flag可以指定为IPC_NOWAIT,使操作不阻塞。
复制代码
这类似于文件IO的非阻塞IO标志。比如msgsnd,若消息队列已满,则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN。