【Unix/Linux编程实践】用管道实现双向通信—以bc计算器为例

原创 2015年12月16日 12:48:11

前言

几乎每个版本的Unix都包含bc计算器。

事实上,大多数版本的bc只分析输入,然后在内部启动了dc计算器程序,并通过管道与其通信。dc是一个基于栈的计算器,它接收逆波兰表达式,执行运算后将结果送到标准输出。

bc从连接到dc标准输出的管道上读取结果,再把结果转发给用户。

事实上,bc为我们提供了用户界面,并使用dc提供的服务。这是一个简单的客户/服务器模型。

bc/dc对被称为协同进程(coroutines)。

编写bc的思路

1.bc创建两个管道(使用pipe);

2.bc创建一个新进程(用于运行dc,使用fork);

3.新进程创建后,在运行exec dc之前,将其标准输入和标准输出重定向到管道;

4.运行exec dc;

5.在父进程bc中,读取并分析用户的输入,将命令通过管道传给dc,dc读取响应,并把响应通过管道传给用户。

代码实现与分析

/** tinybc.c    * a tiny calculator that uses dc to do its work
 **         * demonstrates bidirectional pipes
 **         * input looks like number op number which
 **           tinybc converts into number \n number \n op \n p
 **           and passes result back to stdout
 **
 **     +-----------+                +----------+
 ** stdin  >0           >== pipetodc ====>          |
 **         |  tinybc   |                |   dc -   |
 ** stdout <1           <== pipefromdc ==<          |
 **     +-----------+                +----------+
 **
 **         * program outline
 **             a. get two pipes
 **             b. fork (get another process)
 **             c. in the dc-to-be process,
 **                  connect stdin and out to pipes
 **                  then execl dc
 **             d. in the tinybc-process, no plumbing to do
 **                  just talk to human via normal i/o
 **                  and send stuff via pipe
 **             e. then close pipe and dc dies
 **                     * note: does not handle multiline answers
 **/

#include <stdio.h>

#define oops(m, x)  { perror(m); exit(x); }

main()
{
    int pid, todc[2], fromdc[2];

    /*创建两个管道*/

    if ( pipe(todc) == -1 || pipe(fromdc) == -1 )
        oops("pipe failed", 1);

    /*fork一个新进程,此时父子进程共享以上两个管道*/

    if ( (pid = fork()) == -1 )
        oops("cannot fork", 2);

    if ( pid == 0 )     /*子进程,执行dc*/
        be_dc(todc, fromdc);
    else                /*父进程,执行bc*/
    {
        be_bc(todc, fromdc);
        wait(NULL);     /*等待子进程结束*/
    }
}

be_dc(int in[2], int out[2])
{
    /*将文件描述符in[0](读数据端)复制到文件描述符0(即stdin)上*/
    if ( dup2(in[0], 0) == -1 )
        oops("dc:cannot redirect stdin",3);
    close(in[0]);       /*关闭原文件描述符,于是读数据端只剩下fd 0*/
    close(in[1]);       /*关闭写数据端*/

    /*原理同上,将标准输出重定向到另一条管道的写数据端*/
    if ( dup2(out[1], 1) == -1 )
        oops("dc:cannot redirect stdout",4);
    close(out[1]);
    close(out[0]);

    /* now execl dc with the - option */
    execlp("dc", "dc", "-", NULL);
    oops("Cannot run dc",5);
}

be_bc(int todc[2], int fromdc[2])
{
    int num1, num2;
    char op[BUFSIZ], message[BUFSIZ], *fgets();
    FILE *fpout, *fpin, *fdopen();

    /*setup*/
    close(todc[0]);     /*只写不读*/
    close(fromdc[1]);   /*只读不写*/

    /*把一个通向管道的连接转换成FILE * 类型值,之后可以使用标准缓存的I/O操作来对其进行操作*/
    fpout = fdopen(todc[1], "w");
    fpin = fdopen(fromdc[0], "r");
    if ( fpout == NULL || fpin == NULL)
        fatal("Error convering pipes to streams");

    /*main loop*/

    /*接受用户输入*/
    while ( printf("tinybc: "), fgets(message, BUFSIZ, stdin) != NULL)
    {
        /*从message按照指定格式读取数据*/
        if (sscanf(message, "%d%[-+*/^]%d", &num1, op, &num2) != 3)
        {
            printf("syntax error\n");
            continue;
        }       

        /*写数据到管道*/
        if (fprintf(fpout, "%d\n%d\n%c\np\n", num1, num2, *op) == EOF)
            fatal("Error writing");
        fflush(fpout);

        /*从管道读数据*/
        if (fgets(message, BUFSIZ, fpin) == NULL)
            break;
        printf("%d %c %d = %s", num1, *op, num2, message);
    }
    fclose(fpout);      /*close pipe*/
    fclose(fpin);       /*dc will see EOF*/
}

fatal(char mess[])
{
    fprintf(stderr, "Error: %s\n", mess);
    exit(1);
}

结语

本代码参考自《Unix/Linux编程实践教程》第11章。

自己模仿着敲了一遍,印象加深了很多,一个例子把pipe,fork,dup,exec等知识都融入进来了。

其中一个需要注意的地方便是使用fdopen来打开文件描述符,使得可以使用fprintf和fgets来通过管道和dc进行通信。

版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/jiange_zh

相关文章推荐

Linux----进程间通信-管道与两个命名管道实现进程双向通信

在Linux系统中,有时候需要多个进程之间相互协作,共同完成某项任务,进程之间或线程之间有时候需要传递信息,有时候需要同步协调彼此工作,则就会出现进程间通信(interprocess communi...

命名管道用于进程间双向通信

  • 2017年05月10日 11:49
  • 4.68MB
  • 下载

使用socketpair⽤来创建双向通信的管道

#include #include #include #include #include #include int main() { pid_t pid; int fd[2]; socketp...

socketpair创建双向通信的管道(全双工通信)

socketpair创建双向通信的管道(全双工通信)

c++实现的双向通信的socket小程序

  • 2017年06月05日 19:00
  • 337KB
  • 下载

绑定服务Messenger双向通信的实现

  • 2016年11月14日 14:46
  • 26.14MB
  • 下载

ZigBee多机双向通信编程方法【星形网络】

ZigBee星形网络组网通信

Socket编程(多线程、双向通信)

一、概述       关于Socket编程的基本方法在基础篇里已经讲过,今天把它给完善了。加入了多线程,这样UI线程就不会被阻塞;实现了客户端和服务器的双向通信,只要客户端发起了连接并成功连接后...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:【Unix/Linux编程实践】用管道实现双向通信—以bc计算器为例
举报原因:
原因补充:

(最多只允许输入30个字)