標準流管道
像文件操作有標準 io 流一樣,管道也支持文件流模式。用來創建連接到另一進程的管道,是通過函數 popen 和 pclose。
函數原型:
#include <stdio.h>
FILE* popen(const char* command, const char* open_mode);
int pclose(FILE* fp);
函數popen()
FILE* popen(const char* command, const char* open_mode);
popen允許一個程序將另一個程序作爲新進程來啓動,並可以傳遞數據給它或者通過它接收數據。這裏“另一個進程”是可以執行一定操作的可執行文件,例如用戶執行“ls -l”或者./pipe。由於這類操作很常見,所以將一系列創建過程合併到一個函數popen()中完成,這個函數會完成以下步驟:
https://blog.csdn.net/zzyoucan/article/details/9226599
創建一個管道
fork()一個子進程
在父子進程中關閉不需要的文件描述符
執行exec()函數族調用
執行函數中指定的命令
參數command 字符串是要運行的程序名。
參數open_mode 必須是“r”或“w”。
如果 open_mode 是“r”,被調用程序的輸出就可以被調用程序(popen)使用,調用程序利用popen 函數返回的 FILE*文件流指針,就可以通過常用的 stdio 庫函數(如 fread)來讀取被調用程序的輸出。
如果 open_mode 是“w”,調用程序(popen)就可以用 fwrite 向被調用程序發送數據,而被調用程序可以在自己的標準輸入上讀取這些數據。
該函數調用成功返回文件流指針,調用失敗返回-1。
函數pclose()
函數 pclose():用 popen 啓動的進程結束時,我們可以用 pclose 函數關閉與之關聯的文件流。
popen_ls.c
#include <func.h>
int main(int argc, char* argv[])
{
FILE *fp;
fp=popen("ls","r");
ERROR_CHECK(fp,NULL,"popen");
char buf[128];
fread(buf,sizeof(char),sizeof(buf)-1,fp);
printf("buf=%s\n",buf);
pclose(fp);
return 0;
}
buf=add
add.c
a.out
popen_ls.c
popen_r
popen_r.c
popen_w.c
print
print.c
popen_read_print.c
#include <func.h>
int main(int argc, char* argv[])
{
FILE *fp;
fp=popen("./print","r");
ERROR_CHECK(fp,NULL,"popen");
char buf[128];
fgets(buf,sizeof(buf),fp);
printf("buf=%s\n",buf);
pclose(fp);
return 0;
}
print.c
#include <func.h>
int main(int argc, char* argv[])
{
printf("I am print\n");
return 0;
}
popen_write_add.c
#include <func.h>
int main(int argc, char* argv[])
{
FILE *fp;
fp=popen("./add","w");
ERROR_CHECK(fp,NULL,"popen");
char buf[128]="3 4";
fputs(buf,fp);
pclose(fp);
return 0;
}
add.c
#include <func.h>
int main(int argc, char* argv[])
{
int i,j;
scanf("%d %d",&i,&j);
printf("sum=%d\n",i+j);
return 0;
}
無名管道PIPE
管道通訊原理
兩個進程之間進行通信,因爲它們擁有各自獨有的進程地址空間,所以必須使兩個進程指向一塊公共內存,而這塊內存就是在內核中開闢出來的緩衝區。
管道的兩端,一端負責輸入,一端負責輸出,管道的兩端分別連接兩個進程。進程1負責將數據輸入到緩衝區,進程2將緩衝區的數據讀出,這樣就實現了兩個進程的通信。
無名管道的特點
1、只能在親緣關係進程間通信(父子或兄弟),無名管道只能由創建進程所訪問。通常情況下,父進程創建一個管道,並使用它來與其子進程進行通信(該子進程由 fork() 來創建)。子進程繼承了父進程的打開文件。由於管道是一種特殊類型的文件,因此子進程也繼承了父進程的管道。
2、半雙工(固定的讀端和固定的寫端),數據只能單向流動。無名管道是單向的,只允許單向通信。如果需要雙向通信,那麼就要採用兩個管道。
3、無名管道是一種特殊的文件,不屬於某種文件系統,自己單獨構成,可以用 read、write 等系統調用,並且無名管道只能存在於內存中。
4、管道數據的讀出和寫入:一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩衝區的末尾,並且每次都是從緩衝區的頭部讀出數據。
函數原型:
#include <unistd.h>
int pipe(int fds[2]);
管道在程序中用一對文件描述符表示, 其中一個文件描述符有可讀屬性, 一個有可寫屬性。fds[0]是讀屬性,fds[1]是寫屬性。
函數 pipe()
用於創建一個無名管道,如果成功,fds[0]存放可讀的文件描述符,fds[1]存放可寫文件描述符,並且函數返回 0,否則返回-1。
通過調用 pipe 獲取這對打開的文件描述符後,一個進程就可以從 fds[0]中讀數據,而另一個進程就可以往 fds[1]中寫數據。當然兩進程間必須有繼承關係,才能繼承這對打開的文件描述符。
管道不是真正的物理文件,不是持久的,即兩進程終止後,管道也自動消失了。
管道兩端的關閉是有先後順序的, 如果先關閉寫端則從另一端讀數據時, read 函數將返
回 0,表示管道已經關閉。
但是如果先關閉讀端,則從另一端寫數據時,將會使寫數據的進程接收到 SIGPIPE 信號,如果寫進程不對該信號進行處理,將導致寫進程終止,如果寫進程處理了該信號,則寫數據的 write 函數返回一個負值,表示管道已經關閉。
SIGPIPE信號
什麼時候會產生在這個信號?
寫管道時,如果管道的讀端被close了話,向管道“寫”數據的進程會被內核發送一個SIGPIPE信號,發這個信號的目的就是想通知你,管道所有的“讀”都被關閉了。
這就好比別人把水管的出口(讀)給堵住了,結果你還一直往裏面灌水(寫),別人跟定會警告你,因爲你這樣可能會對水管造成損害,道理其實是類似的。
由於這個信號的默認動作是終止,所以收到這個信號的進程會被終止,如果你不想被終止的話,你可以忽略、捕獲、或者屏蔽這個信號。
pipe_sigpipe.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
void print_err(char *estr)
{
perror(estr);
exit(-1);
}
int main(void)
{
int ret = 0;
int pipefd[2] = {0};
ret = pipe(pipefd);
if(ret == -1) print_err("pipe fail");
ret = fork();
if(ret > 0)
{
signal(SIGPIPE, SIG_IGN);
close(pipefd[0]);
while(1)
{
write(pipefd[1], "hello", 5);
sleep(1);
}
}
else if(ret == 0)
{
close(pipefd[1]);
close(pipefd[0]);
while(1)
{
char buf[30] = {0};
bzero(buf, sizeof(buf));
read(pipefd[0], buf, sizeof(buf));
printf("child, recv data:%s\n", buf);
}
}
return 0;
}
父進程嘗試在向管道寫入數據的時候,被內核發送了SIGPIPE信號,父進程收到SIGPIPE信號之後就會被終止。如果不想要終止我們就可以進行忽略或者捕獲操作。使用signal函數:
signal(SIGPIPE,SIG_IGN); 就可以把信號設置爲忽略。
只有當管道所有的讀端都被關閉時,纔會產生這個信號,只有還有一個讀端開着,就不會產生。
SIGPIPE信號引用自:
https://blog.csdn.net/qq_43648751/article/details/104680142
pipe_father_write_son_read.c
創建父子進程,創建無名管道,父寫子讀
#include <func.h>
int main(int argc, char* argv[])
{
int fds[2];
//int fds1[2];
pipe(fds); //管道的讀端就存在fds[0],寫端存在fds[1]
if(!fork())
{
close(fds[1]); //關閉寫端,因爲子進程要讀數據
char buf[128]={0};
read(fds[0],buf,sizeof(buf));
printf("I am child gets = %s\n",buf);
exit(0);
}
else
{
close(fds[0]); //關閉讀端,因爲父進程要寫數據
write(fds[1],"I hen niu",9);
wait(NULL);
}
return 0;
}
命名管道FIFO
無名管道只能在親緣關係的進程間通信大大限制了管道的使用,有名管道突破了這個限制,通過指定路徑名的範式實現不相關進程間的通信。
因爲有文件名,所以進程可以直接調用open函數打開文件,從而得到文件描述符,不需要像無名管道一樣,必須在通過繼承的方式才能獲取到文件描述符。
所以任何兩個進程之間,如果想要通過“有名管道”來通信的話,不管它們是親緣的還是非親緣的,只要調用open函數打開同一個“有名管道”文件,然後對同一個“有名管道文件”進行讀寫操作,即可實現通信。
總之,不管是親緣進程還是非親緣進程,都可以使用有名管道來通信。
創建FIFO文件 mkfifo
創建 FIFO 文件與創建普通文件很類似,只是創建後的文件用於 FIFO。
創建FIFO文件函數原型:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
參數 pathname 爲要創建的 FIFO 文件的全路徑名;
參數 mode 爲文件訪問權限
如果創建成功,則返回 0,否則-1。
create_fifo.c
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc,char *argv[]) //演示通過命令行傳遞參數
{
if(argc != 2){
puts("Usage: MkFifo.exe {filename}");
return -1;
}
if(mkfifo(argv[1], 0666) == -1){
perror("mkfifo fail");
return -2;
}
return 0;
}
刪除FIFO文件 unlink
刪除FIFO文件的函數原型爲:
#include <unistd.h>
int unlink(const char *pathname);
delete_fifo.c
#include <func.h>
int main(int argc, char* argv[])
{
ARGS_CHECK(argc,2);
int ret;
ret=unlink(argv[1]);
ERROR_CHECK(ret,-1,"unlink");
return 0;
}
創建和刪除FIFO文件:
用命令mkfifo創建 不能重複創建,用命令unlink刪除
創建完畢之後,就可以訪問FIFO文件了:一個終端:cat < myfifo;
另一個終端:echo “hello” > myfifo;
打開、關閉 FIFO 文件 open close
對 FIFO 類型的文件的打開/關閉跟普通文件一樣, 都是使用 open 和 close 函數。
如果打開時使用 O_WRONLY 選項,則打開 FIFO 的寫入端,如果使用 O_RDONLY 選項,則打開
FIFO 的讀取端,寫入端和讀取端都可以被幾個進程同時打開。
如果以讀取方式打開 FIFO, 並且還沒有其它進程以寫入方式打開 FIFO, open 函數將被阻塞。同樣,如果以寫入方式打開 FIFO,並且還沒其它進程以讀取方式 FIFO,open 函數也將被阻塞。
與無名管道PIPE 相同,關閉 FIFO 時,如果先關讀端,將導致繼續往 FIFO 中寫數據的進程接收 SIGPIPE 的信號。
讀寫FIFO文件 read write
可以採用與普通文件相同的讀寫方式讀寫 FIFO
先用管道創建命令創建管道MyFifo.pip
fifo_write.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
int fdFifo = open("MyFifo.pip",O_WRONLY); //1. 打開(判斷是否成功打開略)
write(fdFifo, "hello", 6); //2. 寫
close(fdFifo); //3. 關閉
return 0;
}
fifo_read.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
char szBuf[128];
int fdFifo = open("MyFifo.pip",O_RDONLY); //1. 打開
if(read(fdFifo,szBuf,sizeof(szBuf)) > 0) //2. 讀
puts(szBuf);
close(fdFifo); //3. 關閉
return 0;
}
gcc –o write write.c
gcc –o read read.c
./write //發現阻塞,要等待執行./read
./read
在屏幕上輸出 hello
有名管道雙向通信
同樣的,使用一個“有名管道”是無法實現雙向通信的,因爲也涉及到搶數據的問題。所以雙向通信時需要兩個管道。
圖解:
關閉端口之後的圖解:
當我們在一個進程裏面既要進行讀,也要進行寫的時候,如果如果沒有數據,就會阻塞在read函數,那麼就會導致write函數不能執行,所以我們給出下面圖解,讓兩個進程的讀寫都不會互相干擾到。
讓程序1和程序2分別創建一個子進程,在第一個程序裏面父進程讀管道2,子進程寫管道1,在另一個程序裏面父進程寫管道2,子進程讀管道1。那麼程序就會併發運行互不干擾,並且在捕獲SININT信號在刪除文件的時候只需要一個進程進行刪除即可。我們把刪除文件的操作放在父進程,子進程在收到SIGINT信號之後只是正常的終止進程。
圖解:
代碼演示:我們對於函數進行封裝,以便於我們創建多個管道文件
文件 name_pipe.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"
void print_err(char *estr)
{
perror(estr);
exit(-1);
}
int creat_open_fifo(char *fifoname, int open_mode)
{
int ret = -1;
int fd = -1;
ret = mkfifo(fifoname, 0664);
if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail");
fd = open(fifoname, open_mode);
if(fd == -1) print_err("open fail");
return fd;
}
void signal_fun(int signo)
{
//unlink();
remove(FIFONAME1);
remove(FIFONAME2);
exit(-1);
}
int main(void)
{
char buf[100] = {0};
int ret = -1;
int fd1 = -1;
int fd2 = -1;
fd1 = creat_open_fifo(FIFONAME1, O_WRONLY);
fd2 = creat_open_fifo(FIFONAME2, O_RDONLY);
ret = fork();
if(ret > 0)
{
signal(SIGINT, signal_fun);
while(1)
{
bzero(buf, sizeof(buf));
scanf("%s", buf);
write(fd1, buf, sizeof(buf));
}
}
else if(ret == 0)
{
while(1)
{
bzero(buf, sizeof(buf));
read(fd2, buf, sizeof(buf));
printf("%s\n", buf);
}
}
return 0;
}
文件:name_pipe_read.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"
void print_err(char *estr)
{
perror(estr);
exit(-1);
}
int creat_open_fifo(char *fifoname, int open_mode)
{
int ret = -1;
int fd = -1;
ret = mkfifo(fifoname, 0664);
fd = open(fifoname, open_mode);
if(fd == -1) print_err("open fail");
return fd;
}
void signal_fun(int signo)
{
//unlink();
remove(FIFONAME1);
remove(FIFONAME2);
exit(-1);
}
int main(void)
{
char buf[100] = {0};
int ret = -1;
int fd1 = -1;
int fd2 = -1;
fd1 = creat_open_fifo(FIFONAME1, O_RDONLY);
fd2 = creat_open_fifo(FIFONAME2, O_WRONLY);
ret = fork();
if(ret > 0)
{
signal(SIGINT, signal_fun);
while(1)
{
bzero(buf, sizeof(buf));
read(fd1, buf, sizeof(buf));
printf("recv:%s\n", buf);
}
}
else if(ret == 0)
{
while(1)
{
bzero(buf, sizeof(buf));
scanf("%s", buf);
write(fd2, buf, sizeof(buf));
}
}
return 0;
}
有名管道雙向通信內容引用自:
https://blog.csdn.net/qq_43648751/article/details/104724453
管道示例:基於管道的客戶端服務器程序
程序說明:
1、服務器端:
維護服務器管道,接受來自客戶端的請求並處理(本程序爲接受客戶端發來的字符串,將小寫字母轉換爲大寫字母。)然後通過每個客戶端維護的管道發給客戶端。
2. 客戶端
向服務端管道發送數據,然後從自己的客戶端管道中接受服務器返回的數據。
服務器端 server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
typedef struct tagmag
{
int client_pid ;
char my_data[512] ;
}MSG;
int main()
{
int server_fifo_fd , client_fifo_fd;
char client_fifo[256];
MSG my_msg ;
char * pstr ;
memset(&my_msg , 0 , sizeof(MSG));
mkfifo("SERVER_FIFO_NAME",0777);
server_fifo_fd = open("./SERVER_FIFO_NAME",O_RDONLY);
if(server_fifo_fd == -1)
{
perror("server_fifo_fd");
exit(-1);
}
int iret ;
while( (iret = read(server_fifo_fd , &my_msg ,sizeof(MSG))>0))
{
pstr = my_msg.my_data ;
printf("%s\n",my_msg.my_data);
while(*pstr != '\0')
{
*pstr = toupper(*pstr);
pstr ++ ;
}
memset(client_fifo , 0 , 256);
sprintf(client_fifo , "CLIENT_FIFO_%d" , my_msg.client_pid);
client_fifo_fd = open(client_fifo , O_WRONLY);
if(client_fifo_fd == -1)
{
perror("client_fifo_fd");
exit(-1);
}
write(client_fifo_fd , &my_msg, sizeof(MSG));
printf("%s\n", my_msg.my_data);
printf("OVER!\n");
close(client_fifo_fd);
}
return 0 ;
}
客戶端代碼:client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
typedef struct tagmag
{
int client_pid ;
char my_data[512] ;
}MSG;
int main()
{
int server_fifo_fd,client_fifo_fd ;
char client_fifo[256]={0};
sprintf(client_fifo,"CLIENT_FIFO_%d",getpid());
MSG my_msg ;
memset(&my_msg , 0 , sizeof(MSG));
my_msg.client_pid = getpid();
server_fifo_fd = open("./SERVER_FIFO_NAME",O_WRONLY);
mkfifo(client_fifo , 0777);
while(1)
{
int n = read(STDIN_FILENO, my_msg.my_data, 512);
my_msg.my_data[n] = '\0' ;
write(server_fifo_fd , &my_msg , sizeof(MSG));
client_fifo_fd = open(client_fifo , O_RDONLY);
//memset(&my_msg , 0 , sizeof(MSG));
n = read(client_fifo_fd, &my_msg , sizeof(MSG));
my_msg.my_data[n]= 0 ;
write(STDOUT_FILENO, my_msg.my_data ,strlen(my_msg.my_data) );
close(client_fifo_fd);
}
unlink(client_fifo);
return 0;
}