Linux进程线程学习笔记:进程间通信 之 管道

                                           Linux进程线程学习笔记:进程间通信 之 管道

                                               周银辉 

 

中秋和国庆长假连起来耍了19天,平时学的点东西都快全忘了,那么赶紧补习下Linux吧。

所谓“进程间通信(IPC,inter-process communication)”,按照其目的讲就是让进程之间能够“共享数据”,“传输数据”,“事件通知”,“进程通知”等,我所知道的一共有“管道” “信号” “消息(报文)” “共享内存” “套接字” 这几种方式,我们会挨个挨个说,今天就说说管道。

所谓管道嘛,顾名思义类似于我们生活中的水管,只不过其中流动的是“数据”或者说一个一个字节,只能单向流动的我们称为“半双工”,能双向流动的称为“全双工”,其有两个端点,数据流入的那一端称为“写端”,反之则称为“读端”,这两个端点实际上是两个“描述字”。这样的管道可以连接在两个进程之间,成为数据传输的通道。

按照是否有名字,管道分为“未命名管道”也就是你经常看到的pipe,以及“有名管道”也就是你经常看到的FIFO(first in first out, 和数据结构中的FIFO一样,其也是按照“先进先出”的法则传输数据)。

 

1,pipe

pipe一词虽然是“管道”二字的英文翻译,但在这里其不是管道的统称,而是一种最基本和简单的管道形式:未命名管道。由于其没有名字(或者说id之类的),所以其无法在两个毫无干系的两个进程间使用。试想一下,进程A创建了一个管道,进程B无法去找到该管道并使用它,因为没有任何可拿去查找的凭据,连名字都没有... 但在一种特殊情况下其是有用的,如下图,假设进程A创建了一个管道:

  

其中的箭头代表数据写入和读出,很明显,进程A可以从管道的写端将数据写入,然后再从读端读出,但这似乎没有什么意义。
但,如果在管道建立以后,我们将进程A进行一次fork(),有意思的事情发生了, 子进程会复制父进程的大部分信息,这些信息里当然包含了代表了管道读端和写端的两个“描述字”(但管道仍然只有一份,就像被两个进程读写的某个硬盘文件只有一份一样),所以其就演变成下图这个样子:

 如果此时,我们将上图中左上角以及右下角的两个箭头抛弃掉(相当于是说将这两个箭头所对应的描述字关闭), 那么就如下所示咯:
  

哈,注意到了吗?我们建立了一个从进程A流向进程B的数据通道。

 

那么,将上述过程写成代码的形式就很简单了。

首先是 int pipe(int f[2]) 这个函数,其需要头文件<unistd.h>,这个函数将创建一个未命名管道,并将管道的读端描述字包含在f[0]中,将写端描述字放在f[1]中,然后你就可以像利用普通文件描述字一样来读写数据了。

然后是fork函数,不多讲,不了解的同学一定要先搞清楚了,比如看这篇文章。
再次是int close(int fd) 函数, 其需要头文件<unistd.h>,其用于关闭指定的文件描述字。

最后是write和read函数, 其需要头文件<unistd.h>,用于读写数据。

OK,上代码:

#include  < stdio.h >
#include 
< unistd.h >
#include 
< string .h >
#include 
< stdlib.h >

#define  BUFF_SZ 256

int  main()
{
    printf(
" app start...\n " );
    
    pid_t          pid;
    
int             pipe_fd[ 2 ];
    
char            buf[BUFF_SZ];
    
const   char      data[]  =   " hi, this is the test data " ;
    
int             bytes_read;
    
int             bytes_write;
    
    
// clear buffer, all bytes as 0
    memset(buf,  0 sizeof (buf));
    
    
// creat pipe
     if (pipe(pipe_fd)  <   0 )
    {
        printf(
" [ERROR] can not create pipe\n " );
        exit(
1 );
    }
    
    
    
// fork an new process
     if ( 0   ==  (pid = fork()))
    {
        
// close the write-point of pipe in child process
        close(pipe_fd[ 1 ]);
        
        
// read bytes from read-point of pipe in child process
         if ((bytes_read  =  read(pipe_fd[ 0 ], buf, BUFF_SZ))  >   0 )
        {
            printf(
" %d bytes read from pipe : '%s'\n " , bytes_read, buf);
        }
        
        
// close read-point of pipe in child process
        close(pipe_fd[ 0 ]);
        exit(
0 );
    }
    
    
    
// close read-point of pipe in parent process
    close(pipe_fd[ 0 ]);
    
    
// write bytes to write-point of pipe in parent process
     if ((bytes_write  =  write(pipe_fd[ 1 ], data, strlen(data))))
    {
        printf(
" %d bytes wrote to pipe : '%s'\n " , bytes_write, data);
    }
    
    
// close write-point of pipe in parent process
    close(pipe_fd[ 1 ]);
    
    
// wait child process exit
    waitpid(pid, NULL,  0 );
    
    printf(
" app end\n " );
    
    
return   0 ;
}

 

运行输出为:

app start...
25  bytes wrote to pipe : 'hi ,  this is the test data'
25  bytes read from pipe : 'hi ,  this is the test data'
app end

 

 

2,FIFO

与“无名管道”不同的是,FIFO拥有一个名称来标志它,所谓的名称实际上就是一个路径,比如“/tmp/my_pipe”,其对应到磁盘上的一个管道文件,如果我们用file命令来查看其文件类型的话,会得到如下输出:

ZHOU-YHmatoMacBook-Pro:tmp zhouyh$ file my_fifo 
my_fifo: fifo (named pipe)

 

为了简化对FIFO的理解,我们可以这样来假想:进程A在磁盘上创建了一个名为my_pipe的文件,并向其中写入一些数据,然后进程B打开该文件,并将数据从文件中读出,这样我们便实现了进程A和进程B之间的通信。大致原理如此,只不过FIFO做了更精细的一些操作,以便实现起来更可靠。

 

另外,我们需要知道的是,FIFO是单向(半双工)传输数据的。

函数 int mkfifo (char* path, mode_t mode) 负责创建FIFO管道,其需要头文件<sys/stat.h>,参数path即要创建的管道文件存放位置,mode参数即文件权限,更多的参考这里。 

FIFO管道创建完成以后,便可以使用open函数来打开它,然后进行读写操作了。 

看下面这个简单的demo,其将测试数据由进程A传递给进程B(为防止混淆视线,我将一些条件判断和异常处理代码删掉了): 

先创建一个程序A,其负责创建FIFO管道,并向其中写入一些数据:

/*
 * process A: create FIFO and write data
 
*/

#include 
< stdio.h >
#include 
< stdlib.h >
#include 
< string .h >
#include 
< fcntl.h >
#include 
< limits.h >
#include 
< sys / types.h >
#include 
< sys / stat.h >
#include 
< unistd.h >

#define  FIFO_NAME "/tmp/my_fifo"

int  main()
{
    
int  pipe_fd;
    
    
// if the pipe file do not exist
     if  (access(FIFO_NAME, F_OK)  ==   - 1 )
    {
        
// creat FIFO pipe file
        mkfifo(FIFO_NAME,  0777 );
    }
    
    
// open FIFO pipe file.
    
// this will be brocked until some one open another end point(read-point) of this pipe
    pipe_fd  =  open(FIFO_NAME, O_WRONLY);
    
    
// write data into pipe 
    write(pipe_fd,  " hi, this is a test " , PIPE_BUF);
    
    
// close FIFO pipe file descriptor
    close(pipe_fd);
    
    
return   0 ;
}

然后创建程序B,它从管道中读取数据并显示出来:

/*
 * process B: read data from FIFO
 
*/

#include 
< stdio.h >
#include 
< stdlib.h >
#include 
< string .h >
#include 
< fcntl.h >
#include 
< limits.h >
#include 
< unistd.h >
#include 
< sys / types.h >
#include 
< sys / stat.h >

#define  FIFO_NAME "/tmp/my_fifo"
#define  BUFFER_SIZE PIPE_BUF

int  main()
{
    
int  pipe_fd;
    
    
char  buffer[BUFFER_SIZE  +   1 ];
    
// reset all bytes in buffer as '\0' 
    memset(buffer,  ' \0 ' sizeof (buffer));
    
    
// open FIFO pipe file.
    
// this will be brocked until some one open another end point(write-point) of this pipe
    pipe_fd  =  open(FIFO_NAME, O_RDONLY);
    
    
if (read(pipe_fd, buffer, BUFFER_SIZE)  >   0 )
    {
        printf(
" data from FIFO : %s\n " , buffer);
    }
    
    
// close pipe file descriptor
    close(pipe_fd);

    
return   0 ;
}

运行下程序便会发现,无论是先运行A或是B,先运行起来的都会等待另外一个,这时open函数第二个参数的原因,我们可以添加O_NONBLOCK选项来取消阻塞。关于open函数,更多的看这里 

 

下面这个demo比较有意思,在程序A中敲入字符,其会立即传递给程序B(无需等待回车键),然后程序B会将它显示出来,关于如何取消控制台对回车键的等待,可以参考这里。 

#include  < stdio.h >
#include  < stdlib.h >
#include  < string .h >
#include  < fcntl.h >
#include  < limits.h >
#include  < sys / types.h >
#include  < sys / stat.h >
#include  < unistd.h >
#include  < termios.h >


#define  FIFO_NAME "/tmp/my_fifo"
#define  BUFFER_SIZE PIPE_BUF

static   struct  termios oldt;

// restore terminal settings
void  restore_terminal_settings( void )
{
    tcsetattr( 0 , TCSANOW,  & oldt);   /*  Apply saved settings  */
}

// make terminal read 1 char at a time
void  disable_waiting_for_enter( void )
{
    
struct  termios newt;
    
    
// Save terminal settings
    tcgetattr( 0 & oldt); 
    
// init new settings
    newt  =  oldt;  
    
// change settings
    newt.c_lflag  &=   ~ (ICANON  |  ECHO);
    
// apply settings
    tcsetattr( 0 , TCSANOW,  & newt);
    
// make sure settings will be restored when program ends
    atexit(restore_terminal_settings);
}

int  main()
{
    
int  pipe_fd;
    
int  res;

    
char  buffer[BUFFER_SIZE  +   1 ];
    memset(buffer,  ' \0 ' sizeof (buffer));
    
    
// if the pipe file do not exist
     if  (access(FIFO_NAME, F_OK)  ==   - 1 )
    {
        
// creat FIFO pipe file
        res  =  mkfifo(FIFO_NAME,  0777 );
        
if  (res  !=   0 )
        {
            fprintf(stderr,  " Could not create fifo %s\n " , FIFO_NAME);
            exit(EXIT_FAILURE);
        }
    }
    
    
// open FIFO pipe file.
    
// this will be brocked until some one open another end point(read-point) of this pipe
    pipe_fd  =  open(FIFO_NAME, O_WRONLY);
    
    
// if FIFO pipe file open sucessfully
     if  (pipe_fd  !=   - 1 )
    {
        printf( " input something and press RETURN\n " );
        
char  ch;
        
        disable_waiting_for_enter();
        
        
while  ((ch  =  getchar())  !=   ' \n '
        {
            buffer[ 0 =  ch;
            
            
// write data into pipe 
            res  =  write(pipe_fd, buffer, BUFFER_SIZE);
            
            
if  (res  ==   - 1 )
            {
                fprintf(stderr,  " Write error on pipe\n " );
                exit(EXIT_FAILURE);
            }
            
        }
        
        
// close FIFO pipe file descriptor
        close(pipe_fd);
    }
    
else
    {
        exit(EXIT_FAILURE);
    }
    
    printf( " Process %d finish\n " , getpid());
    exit(EXIT_SUCCESS);
}

 

#include  < stdio.h >
#include  < stdlib.h >
#include  < string .h >
#include  < fcntl.h >
#include  < limits.h >
#include  < unistd.h >
#include  < sys / types.h >
#include  < sys / stat.h >

#define  FIFO_NAME "/tmp/my_fifo"
#define  BUFFER_SIZE PIPE_BUF

int  main()
{
    
int  pipe_fd;
    
int  res;
    
    
char  buffer[BUFFER_SIZE  +   1 ];
    
// reset all bytes in buffer as '\0' 
    memset(buffer,  ' \0 ' sizeof (buffer));
    
    
// open FIFO pipe file.
    
// this will be brocked until some one open another end point(write-point) of this pipe
    pipe_fd  =  open(FIFO_NAME, O_RDONLY);
    
    
if  (pipe_fd  !=   - 1 )
    {
        printf( " data read from buffer : \n " );
        
        
// read all data from pipe file by BUFFER_SIZE each time
         do
        {
            res  =  read(pipe_fd, buffer, BUFFER_SIZE);
            
if (res > 0 )
            {
                printf( " %s " , buffer);
                fflush(stdout);
            }
            
        } while (res  >   0 );
        
        
// close pipe file descriptor
        close(pipe_fd);
    }
    
else
    {
        exit(EXIT_FAILURE);
    }
    
    printf( " \nProcess %d finish\n " , getpid());
    exit(EXIT_SUCCESS);
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值