管道是單向的、先進先出的,它把一個進程的輸出和另一個進程的輸入連接在一起。一個進程(寫進程)在管道尾部寫入數據,另一個進程(讀進程)從管道的頭部讀出數據。
兩個程序之間傳遞數據的一種簡單方法是使用popen和pclose。
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
popen函數允許一個程序將另一個程序作爲新進程來啓動,並可以傳遞數據給它或者通過它接收數據。command字符串是要運行的程序名和相應的參數。type必須是"r"或"w"。
如果type是"r",被調程序的輸出就可以被調用程序使用,調用程序利用popen函數返回的FILE *文件流指針,可以讀取被調程序的輸出;如果type是"w",調用程序就可以向被調程序發送數據,而被調程序可以在自己的標準輸入上讀取這些數據。
pclose函數只在popen啓動的進程結束後才返回。如果調用pclose時它仍在運行,pclose將等待該進程的結束。
案例:
int main()
{
FILE *fp = popen("ps -ef", "r");
if (fp == NULL)
{
perror ("popen");
return -1;
}
char buf[SIZE] = {0};
int ret = fread(buf, sizeof(char), SIZE-1, fp);
// printf ("讀到的數據:\n %s\n", buf);
FILE *fp2 = popen("grep a.out", "w");
if (fp2 == NULL)
{
perror ("popen");
return -1;
}
fwrite (buf, sizeof(char), ret, fp2);
printf ("寫入完成\n");
pclose (fp);
pclose (fp2);
return 0;
}
從案例可以看出通過popen這個函數可以實現與終端語句ps -ef | grep a.out相同的功能。
管道包括無名管道和有名管道兩種,前者用於父進程和子進程間的通信,後者可用於運行於同一系統中的任意兩個進程間的通信。
無名管道由pipe()函數創建:
int pipe(int filedis[2]);
當一個管道建立時,它會創建兩個文件描述符:
filedis[0]用於讀管道,filedis[1]用於寫管道。
案例:
int main()
{
int fd[2];
int ret = pipe(fd);
if (ret == -1)
{
perror ("pipe");
return -1;
}
ret = write (fd[1], "hello", 5);
printf ("寫入 %d 個字節\n", ret);
char ch;
while (1)
{
// 如果管道里面沒有數據可讀,read會阻塞
ret = read (fd[0], &ch, 1);
if (ret == -1)
{
perror ("read");
break;
}
printf ("讀到 %d 字節: %c\n", ret, ch);
}
close (fd[0]);
close (fd[1]);
return 0;
}
從案例可以看出通過pipe 這個函數,可以創建一個管道,fd[0]用於讀,f[1]用於寫。
管道用於不同進程間通信。通常先創建一個管道,在通過fork函數創建一個子進程,該子進程會繼承父進程所創建的管道描述符。
案例:
void child_do(int *fd)
{
// 將管道的寫端關閉
close (fd[1]);
char buf [SIZE];
while (1)
{
// 從父進程讀取數據
int ret = read (fd[0], buf, SIZE-1);
if (ret == -1)
{
perror ("read");
break;
}
buf[ret] = '\0';
printf ("子進程讀到 %d 字節數據: %s\n", ret, buf);
}
// 關閉讀端
close (fd[0]);
}
// 父進程通過管道向子進程發送數據
void father_do(int *fd)
{
// 將管道讀端關閉
close (fd[0]);
char buf[SIZE];
while (1)
{
fgets (buf, SIZE, stdin);
// 向子進程發送數據
int ret = write (fd[1], buf, strlen(buf));
printf ("父進程發送了 %d 字節數據\n", ret);
}
// 關閉寫端
close (fd[1]);
}
int main()
{
int fd[2];
// 創建管道
int ret = pipe(fd);
if (ret == -1)
{
perror ("pipe");
return -1;
}
// 創建子進程
pid_t pid = fork();
switch (pid)
{
case -1:
perror ("fork");
break;
case 0: // 子進程
child_do(fd);
break;
default:
father_do(fd);
break;
}
return 0;
}
在這個案例中,父進程用來寫,子進程則用來讀,這樣父子進程,就可以通過管道進行通信了。
命名管道(FIFO)和無名管道基本相同,但也有不同點:無名管道只能由父子進程使用;但是通過命名管道,不相關的進程也能交換數據。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname: FIFO文件名
mode:屬性(同文件操作)
一旦創建了一個FIFO,就可用open打開它,一般的文件訪問函數(close、read、write等)都可用於FIFO。
命名管道的創建比較簡單,就不多說了,下面介紹一個用命名管道進行的信息交互的案例:
int main()
{
int fd = open("/home/mkfifo", O_WRONLY);
if (fd== -1)
{
perror ("mkfifo");
return -1;
}
char buf[SIZE];
while (1)
{
fgets (buf, SIZE, stdin);
write (fd, buf, strlen(buf));
}
return 0;
}
int main()
{
int fd = open("/home/mkfifo", O_RDWR);
if (fd == -1)
{
perror ("mkfifo");
return -1;
}
char buf[SIZE];
while (1)
{
int ret = read (fd, buf, SIZE);
buf[ret] = '\0';
printf ("讀到 %d 字節: %s\n", ret, buf);
}
return 0;
}
在打開兩個命名管道後,一個進行讀操作,一個進行寫操作。
關閉管道只需要將兩個文件描述符關閉即可,可以使用普通的close函數逐個關閉。