📜  C语言中管道的非阻塞I / O

📅  最后修改于: 2021-05-25 19:53:56             🧑  作者: Mango

前提条件:pipe()系统调用

当pipe()中的I / O块发生时?
考虑两个过程,一个过程实时收集数据(读取数据),另一个过程绘制数据(写入数据)。这两个过程通过管道连接,数据采集过程为数据绘图过程提供动力。两个过程的数据采集速度是不同的。
管道中的默认行为是,如果伙伴进程较慢,则管道的写入和读取端将表现出阻塞行为。这很不好,因为数据获取过程可能要等待绘图过程(写数据)。因此,在数据获取过程中,管道中的块读取调用和程序挂起。如果我们不希望这种情况发生,则必须在读取结束调用之前关闭写入中的进程。
用简单的语言,

  • 读取调用可获取与请求一样多的数据,或者与管道一样多的数据(以较少者为准)
  • 如果管道是空的
    • 如果没有进程的写端打开,则对管道的读取将返回EOF(返回值0)
    • 如果某个进程打开了用于写入的管道,则读取将阻止新数据的进入

管道的非阻塞I / O

有时,不阻塞I / O会很方便,例如,我们不希望在另一个输入的情况下对一个读取调用进行阻塞。解决方案是给定的函数:

To specify non-blocking option:
       #include 
       int fd; 
       fcntl(fd, F_SETFL, O_NONBLOCK);
    范围:
  • fd:文件描述符
  • F_SETFL:将文件状态标志设置为arg指定的值。我们此处的文件访问模式仅用于O_NONBLOCK标志。
  • O_NONBLOCK:用于非阻塞选项。
    返回值:
  • 0:成功返回
  • -1:返回错误,设置errorno

此函数成功运行后,如果管道成功,则对读/写的调用返回-1
为空/已满
,并将errno设置为EAGAIN

示例:孩子每隔3秒钟向父母写一次“ hello”,而父母每秒进行一次非阻塞读取。

// C program to illustrate
// non I/O blocking calls
#include 
#include 
#include  // library for fcntl function
#define MSGSIZE 6
char* msg1 =“hello”;
char* msg2 =“bye !!”;
  
int main()
{
    int p[2], i;
  
    // error checking for pipe
    if (pipe(p) < 0)
        exit(1);
  
    // error checking for fcntl
    if (fcntl(p[0], F_SETFL, O_NONBLOCK) < 0)
        exit(2);
  
    // continued
    switch (fork()) {
  
    // error
    case -1:
        exit(3);
  
    // 0 for child process
    case 0:
        child_write(p);
        break;
  
    default:
        parent_read(p);
        break;
    }
    return 0;
}
void parent_read(int p[])
{
    int nread;
    char buf[MSGSIZE];
  
    // write link
    close(p[1]);
  
    while (1) {
  
        // read call if return -1 then pipe is
        // empty because of fcntl
        nread = read(p[0], buf, MSGSIZE);
        switch (nread) {
        case -1:
  
            // case -1 means pipe is empty and errono
            // set EAGAIN
            if (errno == EAGAIN) {
                printf(“(pipe empty)\n”);
                sleep(1);
                break;
            }
  
            else {
                perror(“read”);
                exit(4);
            }
  
        // case 0 means all bytes are read and EOF(end of conv.)
        case 0:
            printf(“End of conversation\n”);
  
            // read link
            close(p[0]);
  
            exit(0);
        default:
  
            // text read
            // by default return no. of bytes
            // which read call read at that time
            printf(“MSG = % s\n”, buf);
        }
    }
}
void child_write(int p[])
{
    int i;
  
    // read link
    close(p[0]);
  
    // write 3 times "hello" in 3 second interval
    for (i = 0; i < 3; i++) {
        write(p[1], msg1, MSGSIZE);
        sleep(3);
    }
  
    // write "bye" one times
    write(p[1], msg2, MSGSIZE);
  
    // here after write all bytes then write end
    // doesn't close so read end block but
    // because of fcntl block doesn't happen..
    exit(0);
}

输出:

(pipe empty)
MSG=hello
(pipe empty)
(pipe empty)
(pipe empty)
MSG=hello
(pipe empty)
(pipe empty)
(pipe empty)
MSG=hello
(pipe empty)
(pipe empty)
(pipe empty)
MSG=bye!!
End of conversation

原子写入管道

原子意味着没有其他过程观察到它部分完成了。如果写入的数据大小不大于PIPE_BUF(4096 bytes),则读取或写入管道数据是原子的。这意味着数据传输似乎是一个瞬时单位,这意味着系统中的其他任何部分都无法观察到其部分完成的状态。原子I / O可能不会立即开始(它可能需要等待缓冲区空间或数据),但是一旦开始,它将立即完成。

最多可写PIPE_BUF(4096字节)的数据是原子的。读取或写入大量数据可能不是原子操作;例如,可以散布来自共享描述符的其他进程的输出数据。同样,一旦写入了PIPE_BUF字符,将阻止进一步的写入,直到读取了某些字符

示例:进程1同时发送100字节的消息,进程2发送100字节的消息不保证顺序,但是管道将接收所有一条消息,然后再接收其他所有消息。

在非原子写入中,对于较大的写入没有这样的保证,数据可能会像这样混乱地混合在一起:

管道容量

  • 管道可以容纳有限数量的字节。
  • 写满管道并在管道已满时阻塞
    • 它们阻塞,直到另一个进程在管道的另一端读取了足够的数据,并在传输完所有要写入的数据后返回
  • 管道的容量至少为512字节,通常更多(取决于系统)
// C program to illustrate
// finding capacity of pipe
#include 
#include 
#include 
int count = 0;
  
// SIGALRM signal handler
void alrm_action(int signo)
{
    printf("Write blocked after %d characters\n", count);
    exit(0);
}
int main()
{
    int p[2];
    char c = 'x';
  
    // SIGALRM signal
    signal(SIGALRM, alrm_action);
  
    // pipe error check
    if (pipe(p) == -1)
        exit(1);
  
    while (1) {
        alarm(5);
  
        // write 'x' at one time when capacity full
        // write() block and after 5 second alarm
        write(p[1], &c, 1);
  
        // send signal and alrm_action handler execute.
        ++count;
        alarm(0);
    }
}

输出:

Write blocked after 65536 characters 
//output depend on the system so output may change in different system

在这里,在while循环中,在write()调用在管道中写入一个字符“ x”后,设置了第一个5秒警报。而count变量用于在管道中写入count字符。 strong>警报(0)表示取消设置的5秒警报。一段时间后,当管道容量已满时,将阻塞write()调用,并且在设置警报铃声5秒钟并发送信号SIGALRM后,程序将不执行下一条指令。之后, alram_action处理程序执行并打印管道可以写入的最大字符。

想要从精选的最佳视频中学习和练习问题,请查看《基础知识到高级C的C基础课程》。