目錄
八、管道
1、什麼是管道
- 管道是Unix中最古老的進程間通信的形式。
- 我們把從一個進程連接到另一個進程的一個數據流稱爲一個"管道"
- 我們通常把是把一個進程的輸出連接或“管接”(經過管道來連接)到另一個進程的輸入。
2、在shell中使用管道
-
鏈接shell命令:把一個進程的輸出直接饋入另一個的輸入,命令格式如下
cmd1 | cmd2
#生成一個8位的隨機密碼
tr -dc A-Za-z0-9_ </dev/urandom | head -c 8 | xargs
#查看系統中所有的用戶名稱,並按字母排序
awk -F: '{print $1}' /etc/passwd | sort
#列出當前用戶使用最多的5個命令(print的列數根據實際情況而定)
history | awk '{print $2}' | sort | uniq -u | sort -rn | head -5
#查看系統中有哪些用戶的登陸shell時/bin/bash
cat /etc/passwd | grep "/bin/bash" | cut -d: -f1,6
#cut -d: -f1,6 表示以:爲分隔符顯示第1和第6列的內容-d指定分隔符,-f指定列
#查看當前目錄的子目錄個數
ls -l | cut -c 1 | grep "d" | wc -l
#ls -l 長格式列出當前目錄的所有內容,每行的第一個字符表示文件的類
#cut -c 1 截取每行的第一個字符
#grep "d" 獲取文件類型是目錄的行
#wc -l 統計grep命令輸出的行數,即子目錄個數
#合併兩個文件的內容
cat 1.txt | paste -d: 2.txt -
#paste -d: 2.txt - 表示以:爲分割符合並兩個文件,合併時2.txt文件的內容在前 -代表1.txt文件
3、管道特點
- 管道是半雙工的,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道
- 只能用於父子進程或者兄弟進程之間(具有親緣關係的進程)進行通信;通常,一個管道由一個進程創建,然後該進程調用fork,此後父、子進程之間就可應用該管道。
4、pipe函數
- 功能:創建一無名管道
- 原型:
#include <unistd.h>
int pipe(int pipefd[2]);
-
參數:
- pipefd[0]:表示讀端
- pipefd[1]:表示寫端
-
返回值:成功返回0,失敗返回錯誤代碼
示例
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main()
{
int pipefd[2];
char buf[20];
if(pipe(pipefd) == -1){
printf("error\n");
}
int fd = fork();
if(fd == 0){
read(pipefd[0],buf,20);
printf("%s\n",buf);
close(pipefd[0]);
close(pipefd[1]);
}else if(fd > 0){
write(pipefd[1],"hello world",20);
close(pipefd[0]);
close(pipefd[1]);
}else{
printf("error\n");
}
return 0;
}
5、管道讀寫規則
-
如果試圖從管道寫端讀取數據,或者向管道讀端寫入數據都將導致錯誤發生
-
當沒有數據可讀時,read調用就會阻塞,即進程暫停執行,一直等到有數據來到爲止。
-
如果管道的另一端已經被關閉,也就是沒有進程打開這個管道並向它寫數據時,read調用就會阻塞
-
如果管道的寫端不存在,則認爲已經讀到了數據的末尾,讀函數返回的讀出字節數爲0;
-
當管道的寫端存在時,如果請求的字節數目大於PIPE_BUF,則返回管道中現有的數據字節數
-
向管道中寫入數據時,linux將不保證寫入的原子性,管道緩衝區一有空閒區域,寫進程就會試圖向管道寫入數據。如果讀進程不讀走管道緩衝區中的數據,那麼寫操作將一直阻塞。
6、複製文件描述符dup、dup2 、fcntl
- 複製文件描述符可以有三種辦法:
- dup、dup2 、fcntl
- dup系統調用從頭開始搜文件描述符數組,並且在找到第一個空閒文件描述符時完成它的複製。
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd,int newfd);//dup2系統調用複製操作之前,如果newfd已被打開,先關閉。
fcntl()函數
- 功能:實現對文件描述符的多種控制
- 原型:
#include <unistd.h>
#include <fcntl.h>
int fcntl(int oldfd,F_DUPFD,int minifd);
-
參數:
-
minifd:從指定的minifd開始搜索空閒文件描述符,找到時,複製oldfd,返回值爲新的文件描述符。
-
oldfd:要被複制的文件描述符
-
說明:另外fcntl還有其他的作用。
dup示例
//實現cat /etc/passwd | wc -l
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pipefd[2];
if(pipe(pipefd) == -1){
fprintf(stderr,"error creating pipe\n");
exit(1);
}
int fd = fork();
if(fd == 0){
close(1);//關閉標準輸出
dup(pipefd[1]);
close(pipefd[1]);
close(pipefd[0]);
execlp("cat","cat","/etc/passwd",0);
fprintf(stderr,"error trying to exec ls\n");
sleep(1);
exit(1);
}else if(fd > 0){
close(0);//關閉標準輸入
dup(pipefd[0]);
close(pipefd[0]);
close(pipefd[1]);
execlp("wc","wc","-l",0);
fprintf(stderr,"error trying to exec wc\n");
exit(1);
}else{
fprintf(stderr,"error fork\n");
}
return 0;
}
7、popen函數
- 作用:允許一個程序把另外一個程序當作一個新的進程來啓動,並能對它發送數據或接收數據
FILE* popen(const char *command,const char *open_mode);
-
參數:
- command:待運行程序的名字和相應的參數
- open_mode:必須是“r”或“w”
-
如果操作失敗,popen會返回一個空指針
-
每個popen調用都必須指定“r”或“w”,在popen的標準實現裏不支持任何其他的選項
-
如果open_mode是“r”,調用者程序利用popen返回的那個“FILE*”類型的指針用一般的stdio庫函數(比如fread)就可以讀這個文件流
-
如果open_mode是“w”,調用者程序就可以使用fwrite向被調用命令發送數據
pclose函數
- 作用:關閉popen打開的與之關聯的文件流
int pclose(FILE *stream_to_close);
- pclose調用只有在popen啓動的進程結束之後才能返回。
- 如果在調用pclose的時候它仍然在運行,pclose將等待該進程的結束
示例
//通過popen實現cat /etc/passwd | wc -l
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
FILE *read_fp;
FILE *write_fp;
char buffer[BUFSIZ + 1];//BUFSIZ的值等於一個常量值,這個值是8192,一般是會用來做數組的長度。
int chars_read;
memset(buffer,0,sizeof(buffer));
read_fp = popen("cat /etc/passwd","r");
if(read_fp != NULL){
chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp);
pclose(read_fp);
if(chars_read > 0){
write_fp = popen("wc -l","w");
if(write_fp != NULL){
fwrite(buffer,sizeof(char),strlen(buffer),write_fp);
pclose(write_fp);
exit(EXIT_SUCCESS);
}
}
}
exit(EXIT_FAILURE);
return 0;
}
8、命名管道:FIFO文件
- 管道應用的一個限制就是只能在相關的程序之間進行,這些程序是由一個共同的祖先進程啓動的。
- 如果我們想在不相關的進程之間交換數據,可以使用FIFO文件來做這項工作,它經常被稱爲命名管道。
- 命名管道是一種特殊類型的文件
創建一個命名管道
- 命名管道可以從命令行上創建,推薦的命令行方法是使用下面這個命令:
$ mkfifo filename - 命名管道也可以從程序裏創建,相關函數有:
int mkfifo(const char *filename,mode_t mode);
int mknod(const char *filename,mode_t mode | S_IFIFO,(dev_t) 0);
用mkfifo創建一個命名管道
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int res = mkfifo("/tmp/my_fifo",0777);
if(res == 0)
printf("FIFO created\n");
exit(EXIT_SUCCESS);
return 0;
}
9、命名管道(FIFO文件)的打開規則
訪問一個FIFO文件
- FIFO和通過pipe調用創建的管道不同,它是一個有名字的文件而表示一個打開的文件描述符。
- 在對它進行讀或寫操作之前必須先打開它
- FIFO文件也要用open和close函數來打開或關閉,除了一些額外的功能外,整個操作過程與我們前面介紹的文件操作是一樣的
- 傳遞給open調用的是一個FIFO文件的路徑名,而不是一個正常文件的路徑名
用open打開FIFO文件
- 在打開FIFO文件時需要注意一個問題:即程序不能以O_RDWR模式打開FIFO文件進行讀寫
- 如果確實需要在程序之間雙向傳遞數據的話,我們可以同時使用一對FIFO或管道,一個方向配一個;還可以用先關閉再重新打開FIFO的辦法明確地改變數據流的方向
命名管道的打開規則
- 如果當前打開操作是爲讀而打開FIFO時,若已經有相應進程爲寫而打開該FIFO,則當前打開操作將成功返回;否則,可能阻塞直到有相應進程爲寫而打開該FIFO(當前打開操作設置了阻塞標誌,即只設置了O_RDONLY),反之,如果當前打開操作沒有設置了非阻塞標誌,即O_NONBLOCK,則返回成功
- 如果當前打開操作是爲寫而打開FIFO時,如果已經有相應進程爲讀而打開該FIFO,則當前打開操作將成功返回;否則,可能阻塞直到有相應進程爲讀而打開該FIFO(當前打開操作設置了阻塞標誌);或者,返回ENXIO錯誤(當前打開操作沒有設置阻塞標誌)。
10、命名管道(FIFO文件)讀寫規則
- 對於沒有設置阻塞標誌的寫操作:
- 當要寫入的數據量大於PIPE_BUF時,linux將不再保證寫入的原子性。在寫滿所有FIFO空閒緩衝區後,寫操作返回
- 當要寫入的數據量不大於PIPE_BUF時,linux將保證寫入的原子性。如果當前FIFO空閒緩衝區能夠容納請求寫入的字節數,寫完後成功返回;如果當前FIFO空閒緩衝區不能夠容納請求寫入的字節數,則返回EAGAIN錯誤,提醒以後再寫。
示例
- 先在終端:mkfifo myfifo
//fifo_r.cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd = open("myfifo",O_RDONLY);
if(fd == -1){
perror("open fifo");
exit(1);
}
char buf[1024]={0};
int fd_r;
while(1){
memset(&buf,0,sizeof(buf));
fd_r = read(fd,&buf,sizeof(buf));
if(fd_r == -1){
perror("read fifo");
}
printf("read:%s\n",buf);
sleep(1);
}
return 0;
}
//fifo_w.cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#define MYFIFO "./myfifo"
int main()
{
if(access(MYFIFO,F_OK) == -1){//檢查調用進程是否可以訪問文件路徑名
int res = mkfifo(MYFIFO,0777);
if(res < 0)
exit(EXIT_FAILURE);
}
int fd = open(MYFIFO,O_WRONLY);
if(fd == -1){
perror("open fifo");
exit(1);
}
char buf[1024]="hello world!";
int fd_r;
while(1){
fd_r = write(fd,&buf,sizeof(buf));
if(fd_r == -1){
perror("write fifo");
}
printf("write:%s\n",buf);
sleep(1);
}
return 0;
}
,0777);
if(res < 0)
exit(EXIT_FAILURE);
}
int fd = open(MYFIFO,O_WRONLY);
if(fd == -1){
perror("open fifo");
exit(1);
}
char buf[1024]="hello world!";
int fd_r;
while(1){
fd_r = write(fd,&buf,sizeof(buf));
if(fd_r == -1){
perror("write fifo");
}
printf("write:%s\n",buf);
sleep(1);
}
return 0;
}
專欄 《linux網絡編程》 將持續更新中…
從linux 零基礎 到 高併發服務器架構
如果我的文章能夠幫到您,可以點個贊!
您的每次 點贊、關注、收藏 都是對我最大的鼓勵!