看我之前的文章就知道,一般對於網絡讀的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的行為):
1. 如果沒有數據,會一直等待;
2. 有數據時候會讀到用戶指定的緩存區,但是如果數據量比較少,少於參數指定的大小,read也會立即返回,而不會一直等到數據足夠。
阻塞讀的原則:數據不超過指定長度的時候,有多少讀多少,沒有數據就會一直等待。
所以一般情況下,都需要采用循環讀的方式,因為一次read不能保證讀完需要的全部數據。
非阻塞情況(read, recv, msgrcv的行為)
1. 沒有數據,就立即返回;
2. 有數據,也是采用有多少讀多少的方式來處理。
所以,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,函數說明如下:
#include <fcntl.h>int open(const char *pathname, int oflag, ... /* mode_t mode */); 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。