linux c編程:非阻塞I/O


通常來說,從普通文件讀數據,無論你是采用 fscanf,fgets 也好,read 也好,一定會在有限的時間內返回。但是如果你從設備,比如終端(標准輸入設備)讀數據,只要沒有遇到換行符(‘\n’),read 一定會“堵”在那而不返回。還有比如從網絡讀數據,如果網絡一直沒有數據到來,read 函數也會一直堵在那而不返回。

read 的這種行為,稱之為 block,一旦發生 block,本進程將會被操作系統投入睡眠,直到等待的事件發生了(比如有數據到來),進程才會被喚醒。

系統調用 write 同樣有可能被阻塞,比如向網絡寫入數據,如果對方一直不接收,本端的緩沖區一旦被寫滿,就會被阻塞。

比如下面的程序
int main() {
  char buf[10];
  int len;
  while(1) {
    // STDIN_FILENO 是標准輸入的描述符,它的值是 0. STDOUT_FILENO 是標准輸出的描述符,它的值是 1.
    len = read(STDIN_FILENO, buf, 10);
    write(STDOUT_FILENO, buf, len);
  }
  return 0;
}
gcc –o main.out main.c
./main.out 
如果不向終端輸入數據,程序將永遠阻塞在read系統調用處。要規避這個問題,我們就需要用到非阻塞的IO。
對於一個給定的描述符有兩種方法對其指定非阻塞I/O:
1)      如果調用open獲得描述符,則可指定O_NONBLOCK標志
2)      對於已打開的一個描述符,則可調用fcntl,由該函數打開O_NONBLOCK文件狀態標志。
程序如下:
set_f1是設置讀取的方式為非阻塞,clr_f1則是清除非阻塞的方式


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>



void set_f1(int fd,int flags){
    int val;
    if ((val=fcntl(fd,F_GETFL,0)) < 0)
        printf("fcntl F_GETFL error");
    val|=flags;
    if (fcntl(fd,F_SETFL,val) < 0)
        printf("fcntl F_sETFL error");
}

void clr_f1(int fd,int flags){
    int val;
    if ((val=fcntl(fd,F_GETFL,0)) < 0)
        printf("fcntl F_GETFL error");
    val&=~flags;
    if (fcntl(fd,F_SETFL,val) < 0)
        printf("fcntl F_sETFL error");
}


char buf[50000];

int main()
{

    int ntowrite,nwrite;
    char *ptr;
    ntowrite=read(STDIN_FILENO,buf,sizeof(buf));
    fprintf(stderr,"read %d bytes\n",ntowrite);
    set_f1(STDOUT_FILENO,O_NONBLOCK);
    ptr=buf;
    while(ntowrite > 0){
        errno=0;
        nwrite=write(STDOUT_FILENO,ptr,ntowrite);
        fprintf(stderr,"nwrite=%d,errno=%d\n",nwrite,errno);
        if(nwrite > 0){
            ptr+=nwrite;
            ntowrite-=nwrite;
        }
    }
    clr_f1(STDOUT_FILENO,O_NONBLOCK);
    return 0;
}

 

若標准輸出是普通文件,則可以期望write只執行一次

root@maple-VirtualBox:/home/maple/codeblock_prj/func_test# ./main.out < /etc/services > temp.file
read 19605 bytes
nwrite=19605,errno=0
root@maple-VirtualBox:/home/maple/codeblock_prj/func_test# ls -al temp.file
-rw-r--r-- 1 root root 19605 8月  12 15:33 temp.file

但是,如果標准輸出是終端。則有時會返回錯誤。

root@maple-VirtualBox:/home/maple/codeblock_prj/func_test# cat stderr.out
read 19605 bytes
nwrite=14907,errno=0
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=-1,errno=11
nwrite=4698,errno=0

程序發出了多個write調用,但是只有部分真正輸出了數據。其他的都只返回了錯誤。這種形式的循環成為輪詢。在多用戶系統上會浪費CPU時間。


免責聲明!

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



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