linux c编程:非阻塞I/O

Posted 一张红枫叶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux c编程:非阻塞I/O相关的知识,希望对你有一定的参考价值。

通常来说,从普通文件读数据,无论你是采用 fscanf,fgets 也好,read 也好,一定会在有限的时间内返回。但是如果你从设备,比如终端(标准输入设备)读数据,只要没有遇到换行符(‘ ’),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 ",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 ",nwrite,errno);
        if(nwrite > 0){
            ptr+=nwrite;
            ntowrite-=nwrite;
        }
    }
    clr_f1(STDOUT_FILENO,O_NONBLOCK);
    return 0;
}

 

若标准输出是普通文件,则可以期望write只执行一次

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

但是,如果标准输出是终端。则有时会返回错误。

[email protected]:/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时间。


























































































































以上是关于linux c编程:非阻塞I/O的主要内容,如果未能解决你的问题,请参考以下文章

linux 网络编程 ---高级I/O

linux 网络编程 ---高级I/O

Linux设备驱动中的IO模型---阻塞和非阻塞IO

Linux学习_多路I/O复用

网络编程之五种I/O模型

socket阻塞与非阻塞,同步与异步I/O模型