復(fù)雜的編程環(huán)境通常使用多個(gè)相關(guān)的進(jìn)程來執(zhí)行有關(guān)操作。進(jìn)程之間必須進(jìn)行通信,來共享資源和信息。因此,要求內(nèi)核提供必要的機(jī)制,這些機(jī)制通常稱為進(jìn)程間通信(InterProcess Communication, IPC)。
一、Linux平臺(tái)通信方式發(fā)展史
- 早期通信方式:早期的Unix IPC包括管道、FIFO和信號(hào)
- AT&T的貝爾實(shí)驗(yàn)室,對(duì)Unix早期的進(jìn)程間通信進(jìn)行了改進(jìn)和擴(kuò)充,形成了“system V IPC”,其通信進(jìn)程主要局限在單個(gè)計(jì)算機(jī)內(nèi)。
- BSD(加州大學(xué)伯克利分校的伯克利軟件發(fā)布中心),跳過了只能在同一計(jì)算機(jī)通信的限制,形成了基于套接字(socket)的進(jìn)程間通信機(jī)制。
二、進(jìn)程間通信方式???
- 早期通信:無名管道(pipe),有名管道(fifo)、信號(hào)(sem)
- system V IPC:共享內(nèi)存(share memory) 、信號(hào)燈集(semaphore)、消息隊(duì)列(message queue)
- BSD:套接字(socket)
三、無名管道
3.1 特點(diǎn)???
- 只能用于具有親緣關(guān)系的進(jìn)程間進(jìn)行通信
- 半雙工通信,具有固定的讀端與寫端
(單工:只能單方面?zhèn)鬏斝畔?>廣播
半雙工:可以雙向,但是同一時(shí)間只能一個(gè)方向傳輸信息
全雙工:可以雙向同時(shí)傳輸信息) - 無名管道可以看作一種特殊的文件,對(duì)它的讀寫采用文件IO:read、write
- 管道是基于文件描述符通信方式。當(dāng)一個(gè)無名管道創(chuàng)建會(huì)自動(dòng)創(chuàng)建兩個(gè)文件描述符,分別的fd[0]、fd[1],其中fd[0]固定的讀端,fd[1]固定的寫端
3.2 函數(shù)pipe
int pipe(int fd[2])
- 功能:創(chuàng)建無名管道
- 參數(shù):文件描述符(fd[0]:讀端 fd[1]:寫端)
- 返回值:成功 0;失敗 -1
注??:管道要用文件I/O進(jìn)行操作(read,write,close)且管道創(chuàng)建后,fd[0]=3,fd[1]=4
例:
3.3 注意事項(xiàng)???
- 當(dāng)管道中無數(shù)據(jù)時(shí),讀操作會(huì)阻塞;管道中無數(shù)據(jù),將寫端關(guān)閉,讀操作會(huì)立即返回
- 管道中裝滿(管道大小64K)數(shù)據(jù)寫阻塞,一旦有4k空間,寫繼續(xù),直到寫滿為止
- 只有在管道的讀端存在時(shí),向管道中寫入數(shù)據(jù)才有意義。否則,會(huì)導(dǎo)致管道破裂,向管道中寫入數(shù)據(jù)的進(jìn)程將收到內(nèi)核傳來的SIGPIPE信號(hào) (通常Broken pipe錯(cuò)誤)。(GDB調(diào)試可以查看到)
代碼示例:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int fd[2] = {0};
if (pipe(fd) < 0)
{
perror("pipe error");
return -1;
}
printf("fd[0]:%d fd[1]:%d\n", fd[0], fd[1]);
char buf1[32] = {"hello world!"};
char buf2[32] = {0};
// write(fd[1],buf1,strlen(buf1)); //往管道寫入buf1
// ssize_t s = read(fd[0],buf2,32); //從管道讀取數(shù)據(jù)到buf2
// printf("%s %d\n",buf2,s);
// close(fd[0]);
// close(fd[1]);
#if 0
// 1.管道中無數(shù)據(jù),讀阻塞
read(fd[0], buf2, 32);
#endif
#if 0
// 2.將寫端關(guān)閉,讀操作會(huì)立即返回
close(fd[1]);
read(fd[0],buf2,32);
#endif
#if 1
//3.1 當(dāng)無名管道中寫滿數(shù)據(jù)64k,寫阻塞
char buf[65536] = {0};
write(fd[1], buf, 65536);
printf("full\n");
write(fd[1], "a", 1);
printf("write a ok\n");
//至少讀出4k的空間,才能繼續(xù)寫
read(fd[0], buf, 4095);
write(fd[1], 'a', 1);
printf("write 'a' ok\n");
#endif
#if 1
// 3.1 將讀端關(guān)閉,繼續(xù)寫
close(fd[0]);
write(fd[1], "a", 1);
printf("ok...\n");
#endif
// close(fd[0]);
// close(fd[1]);
return 0;
}
第三種情況管道破裂,通過GDB調(diào)試查看到的結(jié)果如下:
3.4 練習(xí)
父子進(jìn)程實(shí)現(xiàn)通信,父進(jìn)程循環(huán)從終端輸入數(shù)據(jù),子進(jìn)程循環(huán)打印數(shù)據(jù),輸入一次打印一次,當(dāng)輸入quit結(jié)束,使用無名管道
/*
練習(xí):父子進(jìn)程實(shí)現(xiàn)通信,父進(jìn)程循環(huán)從終端輸入數(shù)據(jù),
子進(jìn)程循環(huán)打印數(shù)據(jù),當(dāng)輸入quit結(jié)束,使用無名管道
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
char buf[32] = {0};
int fd[2] = {0};
if(pipe(fd)<0) //創(chuàng)建無名管道
{
perror("pipe err");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
perror("fork err");
return -1;
}
else if(pid == 0)
{
while (1) //子進(jìn)程循環(huán)從管道讀取數(shù)據(jù),管道為空阻塞
{
read(fd[0],buf,32);
if(strcmp(buf,"quit")==0)
exit(0);
printf("%s\n",buf);
}
}
else
{
while(1)//循環(huán)從終端輸入數(shù)據(jù),循環(huán)往管道寫入數(shù)據(jù)
{
//scanf("%s",buf);
fgets(buf,32,stdin);
if(buf[strlen(buf)-1] == '\n')
buf[strlen(buf)-1] = '\0';
write(fd[1],buf,strlen(buf)+1);
if(strcmp(buf,"quit")==0)
exit(0);
}
wait(NULL);
}
close(fd[0]);
close(fd[1]);
return 0;
}
四、有名管道
4.1 特點(diǎn)???
- 可以用于兩個(gè)不相關(guān)的進(jìn)程之間通信
- 有名管道可以通過路徑名指出,在文件系統(tǒng)中可見,但內(nèi)容存放在內(nèi)存里
- 通過文件IO操作
- 遵循先進(jìn)先出,故不支持lseek操作
- 半雙工通信
4.2 函數(shù) mkfifo
int mkfifo(const char *filename,mode_t mode);
- 功能:創(chuàng)健有名管道
- 參數(shù):
- filename:有名管道文件名
- mode:權(quán)限
- 返回值:成功:0;失?。?1,并設(shè)置errno號(hào)
注意對(duì)錯(cuò)誤的處理方式:????
如果錯(cuò)誤是file exist時(shí),注意加判斷,如:if(errno == EEXIST)
執(zhí)行如下代碼:
第一次運(yùn)行:
再次運(yùn)行:
處理方式:捕捉錯(cuò)誤碼,進(jìn)行過濾即可??
- 由上面的有名管道特點(diǎn)的第二條可以知道,寫入有名管道的內(nèi)容并非存放在文件中,而是存在內(nèi)存,也就是說有名管道文件的大小為0,下面進(jìn)行驗(yàn)證:
在寫后面加一個(gè)while死循環(huán),運(yùn)行后會(huì)等寫完阻塞,不讀出數(shù)據(jù)。然后在終端可以查看當(dāng)前管道文件的大小,結(jié)果是大小為0??
4.3 注意事項(xiàng)??
- 只寫方式,寫阻塞,直到另一個(gè)進(jìn)程將讀打開
- 只讀方式,讀阻塞,直到另一個(gè)進(jìn)程將寫打開
- 可讀可寫,管道中無數(shù)據(jù),讀阻塞。
驗(yàn)證1,2:
創(chuàng)建兩個(gè)c文件,一個(gè)以只讀方式打開有名管道,從中讀數(shù)據(jù);另一個(gè)以只寫方式打開同一個(gè)有名管道,從中寫數(shù)據(jù)。
只讀
只寫
通過運(yùn)行可以看到,運(yùn)行了其中任意一個(gè),會(huì)發(fā)生阻塞,只有當(dāng)再運(yùn)行另一個(gè)才可以解除阻塞,兩個(gè)程序得以順利執(zhí)行下去。驗(yàn)證了只寫方式,寫阻塞,直到另一個(gè)進(jìn)程將讀打開; 只讀方式,讀阻塞,直到另一個(gè)進(jìn)程將寫打開。
還可以得知,上面程序并不是在read或iwrite發(fā)生阻塞,而是在open函數(shù)處發(fā)生了阻塞。
補(bǔ)充:如果所有寫進(jìn)程都關(guān)閉命名管道,則只讀進(jìn)程的讀操作會(huì)認(rèn)為到達(dá)文件末尾,讀操作解除阻塞并返回
驗(yàn)證:
以只讀方式打開有名管道的程序代碼1.c??
以只寫方式打開有名管道程序代碼2.c??
先執(zhí)行1.c,會(huì)發(fā)生阻塞;再執(zhí)行2.c,2.c不會(huì)往管道寫入數(shù)據(jù)(保證1.c不會(huì)因?yàn)楣艿乐杏袛?shù)據(jù)而解除阻塞),2.c間隔1秒關(guān)閉管道,可以看到原先阻塞的1.c也會(huì)在2.c執(zhí)行1秒后解除阻塞,且read返回值為0。
4.4 練習(xí)
通過有名管道實(shí)現(xiàn)cp文件復(fù)制
方法一:兩個(gè)c文件,一個(gè)只讀管道,一個(gè)只寫管道
3cp_MkfifoToDest.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
if(argc != 2)
{
printf("Please input %s <des>\n",argv[0]);
return -1;
}
if(mkfifo("./fifo",0666) < 0)//創(chuàng)建有名管道
{
//處理文件已存在的情況
if(errno == EEXIST)//EEXTST=17
{
printf("file eexist\n");
}
else
{
perror("mkfifo err");
return -1;
}
}
//打開管道和目標(biāo)文件
int fd = open("./fifo",O_RDONLY);
//此處一定不要用可讀可寫的方式打開
//若以可讀可寫的方式打開,管道中無數(shù)據(jù)則讀阻塞
if(fd<0)
{
perror("open fifo err");
return -1;
}
int dest = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open dest err");
return -1;
}
//循環(huán)讀管道,寫目標(biāo)文件
ssize_t s;
char buf[32] = {0};
while ((s=read(fd,buf,32)) != 0)
write(dest,buf,s);
close(fd);
close(dest);
return 0;
}
3cp_SrcToMkfifo.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
if(argc != 2)
{
printf("Please input %s <src>\n",argv[0]);
return -1;
}
if(mkfifo("./fifo",0666) < 0)//創(chuàng)建有名管道
{
//處理文件已存在的情況
if(errno == EEXIST)//EEXTST=17
{
printf("file eexist\n");
}
else
{
perror("mkfifo err");
return -1;
}
}
//打開管道和源文件
int fd = open("./fifo",O_WRONLY);
if(fd<0)
{
perror("open fifo err");
return -1;
}
int src = open(argv[1],O_RDONLY);
if(fd<0)
{
perror("open src err");
return -1;
}
ssize_t s;
char buf[32] = {0};
while ((s=read(src,buf,32)) != 0)
{
write(fd,buf,s);
}
close(fd);
close(src);
return 0;
}
運(yùn)行結(jié)果:
方法二:單個(gè)c文件,用父子進(jìn)程實(shí)現(xiàn),父進(jìn)程只寫有名管道,子進(jìn)程只讀有名管道
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
if (argc != 3)
{
printf("Please iniput %s <src> <dest>\n", argv[0]);
return -1;
}
if (mkfifo("./fifo", 0666) < 0) //創(chuàng)建有名管道
{
//處理文件已存在的情況
if (errno == EEXIST) //EEXTST=17
{
printf("file eexist\n");
}
else
{
perror("mkfifo err");
return -1;
}
}
//打開管道、源文件、目標(biāo)文件
int src = open(argv[1], O_RDONLY);
if (src < 0)
{
perror("open src err");
return -1;
}
int dest = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (dest < 0)
{
perror("open dest err");
return -1;
}
ssize_t s;
char buf[32] = {0};
pid_t pid = fork(); // 創(chuàng)建子進(jìn)程
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0) //子進(jìn)程從有名管道中讀出數(shù)據(jù),寫到目標(biāo)文件中
{
int fd = open("./fifo", O_RDONLY); //管道設(shè)置為只讀
if (fd < 0)
{
perror("open fifo err");
return -1;
}
while ((s = read(fd, buf, 32)) != 0)
write(dest, buf, s);
printf("child end...\n");
close(fd);
exit(0);
}
else //父進(jìn)程從源文件讀出數(shù)據(jù),寫到有名管道中
{
int fd = open("./fifo", O_WRONLY); //管道設(shè)置為只寫
if (fd < 0)
{
perror("open fifo err");
return -1;
}
while ((s = read(src, buf, 32)) != 0)
write(fd, buf, s);
printf("parent end...\n");
close(fd);
wait(NULL);
}
close(src);
close(dest);
return 0;
}
注:該程序容易被懷疑最后在子進(jìn)程的read(fd, buf, 32)會(huì)發(fā)生阻塞,其實(shí)不然,這里用到了上面的一個(gè)要點(diǎn):如果所有寫進(jìn)程都關(guān)閉命名管道,則只讀進(jìn)程的讀操作會(huì)認(rèn)為到達(dá)文件末尾,讀操作解除阻塞并返回文章來源:http://www.zghlxwxcb.cn/news/detail-645868.html
五、無名管道與有名管道對(duì)比??
文章來源地址http://www.zghlxwxcb.cn/news/detail-645868.html
到了這里,關(guān)于LinuxC編程——進(jìn)程間通信(一)(管道)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!