兩個程序之間傳遞數據的一種簡單方法是使用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將等待該進程的結束。
#include <stdio.h>#define SIZE 1024*100int 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;
}
管道是單向的、先進先出的,它把一個進程的輸出和另一個進程的輸入連接在一起。一個進程(寫進程)在管道尾部寫入數據,另一個進程(讀進程)從管道的頭部讀出數據。管道包括無名管道和有名管道兩種,前者用于父進程和子進程間的通信,后者可用于運行于同一系統中的任意兩個進程間的通信。
無名管道由pipe( )函數創建:
int pipe(int filedis[2]);
當一個管道建立時,它會創建兩個文件描述符:filedis[0]fi用于讀管道,ledis[1]
用于寫管道。
管道用于不同進程間通信。通常先創建一個管道,在通過fork函數創建一個子進程,該子進程會繼承父進程所創建的管道描述符。必須在系統調用fork()前調用pipe(),否則子進程將不會繼承文件描述符。
1、單個進程中的管道
#include <stdio.h>
#include <unistd.h>#define SIZE 1024*100int 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;
}
2、父子進程通過管道進行通信
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>#define SIZE 1024// 子進程通過管道從父進程接收數據
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;
}
3、父子進程通過管道實現文件復制
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>#define SIZE 1024// 子進程通過管道從父進程接收數據
void child_do(int *fd)
{// 將管道的寫端關閉close (fd[1]);int fd_write = open ("2.mmap", O_WRONLY|O_CREAT, 0777);if (fd_write == -1){perror ("open");return;}int ret;char buf [SIZE];// read 從管道讀數據,如果管道沒有數據可讀,read 會阻塞// 如果 管道的寫端 被關閉, read 返回 0while (ret = read (fd[0], buf, SIZE)){if (ret == -1){perror ("read");break;}// 把從父進程接收的數據寫入到新文件中write (fd_write, buf, ret);}printf ("文件復制完成\n");// 關閉讀端close (fd[0]);close (fd_write);
}// 父進程通過管道向子進程發送數據
void father_do(int *fd)
{// 將管道讀端關閉close (fd[0]);int fd_read = open ("1.mmap", O_RDONLY);if (fd_read == -1){perror ("open");return;}int ret;char buf[SIZE];while (ret = read (fd_read, buf, SIZE)){if (ret == -1){perror ("read");break;}// 把讀到的內容發送給子進程write (fd[1], buf, ret);}// 關閉寫端close (fd[1]);close (fd_read);
}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;
}
4、管道讀端關閉,寫端繼續寫數據
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>#define SIZE 1024// 子進程通過管道從父進程接收數據
void child_do(int *fd)
{close (fd[1]);close (fd[0]);
}void father_do(int *fd)
{// 將管道讀端關閉close (fd[0]);printf ("等待子進程關閉讀端\n");sleep(2);// 所有讀端都關閉了,寫端繼續往管道寫入數據// 如果管道所有的讀端都被關閉,繼續寫數據系統默認的操作是使程序退出write (fd[1], "hello", 5);printf ("11111111111111111111111111111111\n");// 關閉寫端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)和無名管道基本相同,但也有不同點:無名管道只能由父子進程使用;但是通過命名管道,不相關的進程也能交換數據。
命名管道具有很好的使用靈活性,表現在:
1) 既可用于本地,又可用于網絡。
2) 可以通過它的名稱而被引用。
3) 支持多客戶機連接。
4) 支持雙向通信。
5) 支持異步重疊I/O操作。
1、創建命名管道
#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。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>int main()
{int ret = mkfifo("/home/mkfifo", 0777);if (ret == -1){perror ("mkfifo");return -1;}return 0;
}
2、命名管道的傳輸
當打開FIFO時,非阻塞標識(O_NONBLOCK)將對以后的讀寫產生影響:
1、沒有使用O_NONBLOCK:訪問要求無法滿足時進程將阻塞。如果試圖讀取空的FIFO,將導致進程阻塞。
2、使用O_NONBLOCK:訪問要求無法滿足時不阻塞,立刻出錯返回。errno是ENXIO。
寫入:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>#define SIZE 1024int 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;
}
讀取:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define SIZE 1024int 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;
}
管道作為進程間通信的4種方式之一,他并不會保存數據,區別與共享內存。