阻塞與非阻塞的IO網絡讀寫


看我之前的文章就知道,一般對於網絡讀的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。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM