关闭

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

标签: unix通信管道C-S
529人阅读 评论(0) 收藏 举报
分类:

前言

几乎每个版本的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进行通信。

0
0
查看评论

bc: 一个非常有用的UNIX/LINUX命令行计算器

command-line calculations using bc Come From:http://www.basicallytech.com/blog/index.php?/archives/23-command-line-calculations-using-bc.html ...
  • devyfong
  • devyfong
  • 2012-10-13 01:03
  • 1405

linux/unix 退出bc 计算器

输入bc进入 进入后输入 :quit 退出 ---------------------
  • yytry8
  • yytry8
  • 2012-03-24 23:31
  • 5972

《Unix-Linux编程实践教程》读书笔记(一)

写在最前:          第一遍:零零散散的花了近两个月的时间读了本书的第一遍,这是一本很适合刚刚掌握一些计算机基础知识的人读的书。学习的思路非常明确,能做什么?怎么做?自己动手?三个方面符合认知顺序,由浅入深让人易...
  • yongchurui
  • yongchurui
  • 2014-03-27 23:20
  • 3996

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

在Linux系统中,有时候需要多个进程之间相互协作,共同完成某项任务,进程之间或线程之间有时候需要传递信息,有时候需要同步协调彼此工作,则就会出现进程间通信(interprocess communication 或者 IPC)信号也是进程间通信的一种机制,尽管其主要作用不是这个,一个进程向另一...
  • qq_36782456
  • qq_36782456
  • 2017-05-29 19:45
  • 1089

《Unix-Linux编程实践教程》读书笔记(十)

第十章 I/O重定向和管道 1.      shell脚本中 # ls > my.file                 #who| sort > ...
  • yongchurui
  • yongchurui
  • 2014-05-29 23:30
  • 1010

unix/linux编程实践教程------学习笔记

从今天起开始学习基础linux知识。其实之前也曾经看过这几方面的内容。只不过一直了解不深刻。现在用鸟哥的书为教材,开始学习linux基本操作。大概花两个星期左右吧。看完这个以后,准备将主要工作放到linux上进行。后面的路线大致为:UNIX环境高级编程 ———nginx网络服务器的学习..........
  • z8756413
  • z8756413
  • 2015-08-29 15:33
  • 380

Linux计算器bc学习

Linux内的bc计算器是一个非常强大的计算器,内置了一套类似C语言的语法。学习bc可以不用高级编程语言的情况下解决一系列的问题。但是目前网上没有找到很好的中文教程,在此自己写一个,方便以后查阅。
  • pdcxs007
  • pdcxs007
  • 2015-09-05 10:53
  • 1778

unix/linux编程实践教程读后感(1)

一周之前书到手了,其实之前买了
  • u011542994
  • u011542994
  • 2014-06-17 15:58
  • 768

【Unix/Linux编程实践】 动手实现简单的more

最近都在看一些理论方面的书,缺乏实践真的是云里雾里的,于是今天开始看《Unix/Linux编程实践教程》,理论实践相结合!自己动手来实现linux的一些功能,确实挺有趣的,而且还能加深对系统的理解~版本一/*more01.c *read and print 24 lines then pause ...
  • jiange_zh
  • jiange_zh
  • 2015-12-06 12:33
  • 523

《Unix/linux编程实践教程》第二章部分习题相关知识整理

open和fopen的区别(2.3)1.缓冲文件系统缓冲文件系统的特点是:在内存开辟一个“缓冲区”,为程序中的每一个文件使用,当执行读文件的操作时,从磁盘文件将数据先读入内存“缓冲区”, 装满后再从内存“缓冲区”依此读入接收的变量。执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区”装满...
  • u014507230
  • u014507230
  • 2015-04-27 19:00
  • 378
    个人资料
    • 访问:409456次
    • 积分:6842
    • 等级:
    • 排名:第4070名
    • 原创:273篇
    • 转载:16篇
    • 译文:0篇
    • 评论:156条
    Personal Information

    中山大学 计算机科学与技术

    腾讯科技 后台开发工程师

    e-mail:jiangezh@qq.com

    GitHub:https://github.com/jiangeZh

    博客专栏
    最新评论