[Linux]進(jìn)程間通信–管道
進(jìn)程間通信的目的
- 數(shù)據(jù)傳輸:一個(gè)進(jìn)程需要將它的數(shù)據(jù)發(fā)送給另一個(gè)進(jìn)程 。
- 資源共享:多個(gè)進(jìn)程之間共享同樣的資源。
- 通知事件:一個(gè)進(jìn)程需要向另一個(gè)或一組進(jìn)程發(fā)送消息,通知它(它們)發(fā)生了某種事件(如進(jìn)程終止時(shí)要通知父進(jìn)程)。
- 進(jìn)程控制:有些進(jìn)程希望完全控制另一個(gè)進(jìn)程的執(zhí)行(如Debug進(jìn)程),此時(shí)控制進(jìn)程希望能夠攔截另一個(gè)進(jìn)程的所有陷入和異常,并能夠及時(shí)知道它的狀態(tài)改變。
實(shí)現(xiàn)進(jìn)程間通信的原理
進(jìn)程是具有獨(dú)立性的,一個(gè)進(jìn)程是無(wú)法看到另一個(gè)進(jìn)程的代碼和數(shù)據(jù)的,為了讓進(jìn)程間通信,要做的工作就是讓不同的進(jìn)程看到同一份“資源”。
任何進(jìn)程通信手段需要解決的問(wèn)題如下:
- 讓不同的進(jìn)程看到同一份“資源”
- 讓一方進(jìn)行讀取,另一方進(jìn)行寫(xiě)入
不同的進(jìn)程間通信手段本質(zhì)的區(qū)別就是讓不同的進(jìn)程看到同一份“資源”的方式不同。
匿名管道
匿名管道是一種以文件為媒介的通信方式,匿名管道是一個(gè)內(nèi)存級(jí)別的文件,擁有和普通文件一樣的緩沖區(qū),但是操作系統(tǒng)不會(huì)將緩沖區(qū)刷新至外設(shè),匿名管道雖然是文件,但是由于沒(méi)有文件路徑,進(jìn)程是無(wú)法通過(guò)系統(tǒng)文件接口來(lái)操作的,因此匿名管道通常用于父子進(jìn)程之間使用。
匿名管道的通信原理
由于匿名管道沒(méi)有文件路徑,進(jìn)程是無(wú)法通過(guò)系統(tǒng)文件接口來(lái)操作的特性,匿名管道必須通過(guò)父進(jìn)程創(chuàng)建,子進(jìn)程繼承父進(jìn)程文件描述符表的方式,使得不同的進(jìn)程看到同一個(gè)文件:
由于匿名管道只支持單向通信,在使用匿名管道進(jìn)行通信時(shí),父進(jìn)程必須分別以讀方式和寫(xiě)方式打開(kāi)管道文件,子進(jìn)程繼承了文件描述符表后,一方關(guān)閉讀端,一方關(guān)閉寫(xiě)端進(jìn)行通信。
注意: 如果父進(jìn)程只以讀方式或者寫(xiě)方式打開(kāi),子進(jìn)程繼承文件描述符表后,也是同樣的方式,子進(jìn)程自身無(wú)法打開(kāi)該管道,因此導(dǎo)致無(wú)法通信。
系統(tǒng)接口
Linux系統(tǒng)提供了創(chuàng)建匿名管道的系統(tǒng)接口pipe
:
//pipe所在的頭文件和聲明
#include <unistd.h>
int pipe(int pipefd[2]);
- pipefd為輸出型參數(shù),用于接收以讀方式和寫(xiě)方式打開(kāi)管道的文件描述符。
- pipefd[0]獲取讀端文件描述符,pipefd[1]獲取寫(xiě)端文件描述符。
- 成功返回0,失敗返回-1,錯(cuò)誤碼被設(shè)置。
編寫(xiě)如下代碼測(cè)試pipe
接口:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>
using namespace std;
int main()
{
//創(chuàng)建管道
int pipefd[2] = { 0 };
int n = pipe(pipefd);
if (n < 0)//出錯(cuò)判斷
{
cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;
exit(1);
}
//創(chuàng)建子進(jìn)程
pid_t id = fork();
assert(id != -1);//出錯(cuò)判斷
//進(jìn)行通信 -- 父進(jìn)程進(jìn)行讀取,子進(jìn)程進(jìn)行寫(xiě)入
if (id == 0)
{
//子進(jìn)程
close(pipefd[0]);
const string str = "hello world";
int cnt = 1;
char buffer[1024];
while(1)
{
snprintf(buffer, sizeof(buffer), "%s, 我是子進(jìn)程, 我的pid:%d, 計(jì)數(shù)器:%d", str.c_str(), getpid(), cnt++);
write(pipefd[1], buffer, strlen(buffer));//向管道寫(xiě)入數(shù)據(jù)
sleep(1);
}
close(pipefd[1]);
exit(0);
}
//父進(jìn)程
close(pipefd[1]);
char buffer[1024];
while(1)
{
read(pipefd[0], buffer, sizeof(buffer) - 1);//從管道讀取數(shù)據(jù)
cout << "我是父進(jìn)程," << "child give me: " << buffer << endl;
}
close(pipefd[0]);
return 0;
}
編譯代碼運(yùn)行查看結(jié)果:
從運(yùn)行結(jié)果可以看出,建立管道后,父子進(jìn)程就能夠進(jìn)行數(shù)據(jù)通信。
管道特性
- 單向通信,半雙工的,數(shù)據(jù)只能向一個(gè)方向流動(dòng);需要雙方通信時(shí),需要建立起兩個(gè)管道
- 管道的本質(zhì)是文件,因此管道的生命周期隨進(jìn)程
- 管道通信,通常適用于具有“血緣關(guān)系的進(jìn)程”,諸如父子進(jìn)程、兄弟進(jìn)程等
- 管道的數(shù)據(jù)是以字節(jié)流的形式傳輸?shù)模x寫(xiě)次數(shù)的多數(shù)不是強(qiáng)相關(guān)的
- 具有一定的協(xié)同機(jī)制
管道的協(xié)同場(chǎng)景
場(chǎng)景一: 如果管道內(nèi)部的數(shù)據(jù)被讀端讀取完了,寫(xiě)端不寫(xiě)入,讀端就只能等待
編寫(xiě)如下代碼(如下代碼只是在前文測(cè)試pipe接口的代碼上做略微改動(dòng),主要改動(dòng)已用-----標(biāo)識(shí))進(jìn)行驗(yàn)證:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>
using namespace std;
int main()
{
//創(chuàng)建管道
int pipefd[2] = { 0 };
int n = pipe(pipefd);
if (n < 0)
{
cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;
exit(1);
}
//創(chuàng)建子進(jìn)程
pid_t id = fork();
assert(id != -1);
//進(jìn)行通信 -- 父進(jìn)程進(jìn)行讀取,子進(jìn)程進(jìn)行寫(xiě)入
if (id == 0)
{
//子進(jìn)程
close(pipefd[0]);
const string str = "hello world";
int cnt = 1;
char buffer[1024];
while(1)
{
snprintf(buffer, sizeof(buffer), "%s, 我是子進(jìn)程, 我的pid:%d, 計(jì)數(shù)器:%d", str.c_str(), getpid(), cnt++);
write(pipefd[1], buffer, strlen(buffer));
sleep(100); // --------- 模擬寫(xiě)入暫停 ---------
}
close(pipefd[1]);
exit(0);
}
//父進(jìn)程
close(pipefd[1]);
char buffer[1024];
while(1)
{
read(pipefd[0], buffer, sizeof(buffer) - 1);
cout << "我是父進(jìn)程," << "child give me: " << buffer << endl;
}
close(pipefd[0]);
return 0;
}
編譯代碼運(yùn)行查看結(jié)果:
場(chǎng)景二: 如果管道內(nèi)部的數(shù)據(jù)被寫(xiě)端寫(xiě)滿(mǎn)了,讀端不讀取,寫(xiě)端無(wú)法繼續(xù)寫(xiě)入
編寫(xiě)如下代碼(如下代碼只是在前文測(cè)試pipe接口的代碼上做略微改動(dòng),主要改動(dòng)已用-----標(biāo)識(shí))進(jìn)行驗(yàn)證:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>
using namespace std;
int main()
{
//創(chuàng)建管道
int pipefd[2] = { 0 };
int n = pipe(pipefd);
if (n < 0)
{
cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;
exit(1);
}
//創(chuàng)建子進(jìn)程
pid_t id = fork();
assert(id != -1);
//進(jìn)行通信 -- 父進(jìn)程進(jìn)行讀取,子進(jìn)程進(jìn)行寫(xiě)入
if (id == 0)
{
//子進(jìn)程
close(pipefd[0]);
const string str = "hello world";
int cnt = 1;
char buffer[1024];
while(1)
{
snprintf(buffer, sizeof(buffer), "%s, 我是子進(jìn)程, 我的pid:%d, 計(jì)數(shù)器:%d", str.c_str(), getpid(), cnt++);
write(pipefd[1], buffer, strlen(buffer));
printf("cnt: %d\n", cnt); // --------- 顯示寫(xiě)入過(guò)程 ---------
//sleep(100);
}
close(pipefd[1]);
exit(0);
}
//父進(jìn)程
close(pipefd[1]);
char buffer[1024];
while(1)
{
sleep(100); // --------- 模擬讀取暫停 ---------
read(pipefd[0], buffer, sizeof(buffer) - 1);
cout << "我是父進(jìn)程," << "child give me: " << buffer << endl;
}
close(pipefd[0]);
return 0;
}
編譯代碼運(yùn)行查看結(jié)果:
場(chǎng)景三: 寫(xiě)端關(guān)閉,讀端讀完了管道內(nèi)部的數(shù)據(jù)時(shí),再讀就讀到了文件的結(jié)尾。
編寫(xiě)如下代碼(如下代碼只是在前文測(cè)試pipe接口的代碼上做略微改動(dòng),主要改動(dòng)已用-----標(biāo)識(shí))進(jìn)行驗(yàn)證:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>
using namespace std;
int main()
{
//創(chuàng)建管道
int pipefd[2] = { 0 };
int n = pipe(pipefd);
if (n < 0)
{
cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;
exit(1);
}
//創(chuàng)建子進(jìn)程
pid_t id = fork();
assert(id != -1);
//進(jìn)行通信 -- 父進(jìn)程進(jìn)行讀取,子進(jìn)程進(jìn)行寫(xiě)入
if (id == 0)
{
//子進(jìn)程
close(pipefd[0]);
const string str = "hello world";
int cnt = 1;
char buffer[1024];
while(1)
{
snprintf(buffer, sizeof(buffer), "%s, 我是子進(jìn)程, 我的pid:%d, 計(jì)數(shù)器:%d", str.c_str(), getpid(), cnt++);
write(pipefd[1], buffer, strlen(buffer));
printf("cnt: %d\n", cnt);
sleep(1);
if (cnt == 5) break; // --------- 寫(xiě)端關(guān)閉 ---------
}
close(pipefd[1]);
exit(0);
}
//父進(jìn)程
close(pipefd[1]);
char buffer[1024];
while(1)
{
int n = read(pipefd[0], buffer, sizeof(buffer) - 1);
if (n > 0)
{
cout << "我是父進(jìn)程," << "child give me: " << buffer << endl;
}
else if (n == 0)// --------- 判斷讀取到文件末尾 ---------
{
cout << "讀取完畢, 讀到文件結(jié)尾" << endl;
break;
}
else
{
cout << "讀取出錯(cuò)" << endl;
break;
}
}
close(pipefd[0]);
return 0;
}
編譯代碼運(yùn)行查看結(jié)果:
**場(chǎng)景四:**寫(xiě)端一直寫(xiě),讀端關(guān)閉,操作系統(tǒng)會(huì)給寫(xiě)端發(fā)送13號(hào)信號(hào)終止進(jìn)程。
編寫(xiě)如下代碼(如下代碼只是在前文測(cè)試pipe接口的代碼上做略微改動(dòng),主要改動(dòng)已用-----標(biāo)識(shí))進(jìn)行驗(yàn)證:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
using namespace std;
int main()
{
//創(chuàng)建管道
int pipefd[2] = { 0 };
int n = pipe(pipefd);
if (n < 0)
{
cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;
exit(1);
}
//創(chuàng)建子進(jìn)程
pid_t id = fork();
assert(id != -1);
//進(jìn)行通信 -- 父進(jìn)程進(jìn)行讀取,子進(jìn)程進(jìn)行寫(xiě)入
if (id == 0)
{
//子進(jìn)程
close(pipefd[0]);
const string str = "hello world";
int cnt = 1;
char buffer[1024];
while(1)
{
snprintf(buffer, sizeof(buffer), "%s, 我是子進(jìn)程, 我的pid:%d, 計(jì)數(shù)器:%d", str.c_str(), getpid(), cnt++);
write(pipefd[1], buffer, strlen(buffer));
printf("cnt: %d\n", cnt);
sleep(1);
}
close(pipefd[1]);
exit(0);
}
//父進(jìn)程
close(pipefd[1]);
char buffer[1024];
while(1)
{
int cnt = 0;
//sleep(100);
int n = read(pipefd[0], buffer, sizeof(buffer) - 1);
if (n > 0)
{
cout << "我是父進(jìn)程," << "child give me: " << buffer << endl;
}
else if (n == 0)
{
cout << "讀取完畢, 讀到文件結(jié)尾" << endl;
break;
}
else
{
cout << "讀取出錯(cuò)" << endl;
break;
}
//sleep(100);
sleep(5);
break;// --------- 讀端關(guān)閉 ---------
}
close(pipefd[0]);
int status = 0;
waitpid(id, &status, 0);
cout << "signal: " << (status & 0x7F) << endl;// --------- 回收子進(jìn)程獲取退出信號(hào) ---------
sleep(3);
return 0;
}
編譯代碼運(yùn)行查看結(jié)果:
管道的大小
在Linux下,管道(Pipe)的大小受到操作系統(tǒng)的限制。具體來(lái)說(shuō),管道的大小由內(nèi)核參數(shù)PIPE_BUF
定義,通常是4096個(gè)字節(jié)。
- 當(dāng)要寫(xiě)入的數(shù)據(jù)量不大于
PIPE_BUF
時(shí),linux將保證寫(xiě)入的原子性。 - 當(dāng)要寫(xiě)入的數(shù)據(jù)量大于
PIPE_BUF
時(shí),linux將不再保證寫(xiě)入的原子性。
命名管道
命名管道同樣是內(nèi)存級(jí)的文件,和匿名管道的區(qū)別就是命名管道可以在指定路徑下創(chuàng)建,并且命名可以指定,因此命名管道可以給任何兩個(gè)不同的進(jìn)程用于通信。
使用指令創(chuàng)建命名管道
Linux下使用mkfifo
指令就可以在指定路徑下創(chuàng)建命名管道。
命名管道同樣和匿名管道一樣滿(mǎn)足管道的協(xié)同場(chǎng)景:
寫(xiě)端嘗試打開(kāi)管道文件,沒(méi)有讀端,寫(xiě)端就會(huì)卡在打開(kāi)文件這一步驟。
右側(cè)讀端開(kāi)始會(huì)等待寫(xiě)端寫(xiě)入,后續(xù)關(guān)閉右側(cè)讀端,左側(cè)寫(xiě)端進(jìn)程直接被終止。
使用系統(tǒng)調(diào)用創(chuàng)建命名管道
//mkfifo所在的頭文件和聲明
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
- pathname參數(shù) – 創(chuàng)建命名管道的路徑
- mode參數(shù) – 創(chuàng)建命名管道的文件權(quán)限
- 成功返回0,失敗返回-1,錯(cuò)誤碼被設(shè)置。
為了測(cè)試mkfifo
接口編寫(xiě)代碼進(jìn)行測(cè)試,首先設(shè)置文件結(jié)構(gòu)如下:
makefile
文件內(nèi)容如下:
.PHONY:all
all:client server
client:client.cc
g++ -o $@ $^ -std=c++11
server:server.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -rf client server
common.hpp
主要用于讓兩個(gè)進(jìn)程獲取管道路徑,具體內(nèi)容如下:
#include <iostream>
#include <string>
#define NUM 1024
const std::string pipename = "./namepipe"; //管道的路徑和管道名
mode_t mode = 0666; //創(chuàng)建管道的文件權(quán)限
client.cc
作為寫(xiě)端輸入數(shù)據(jù),具體內(nèi)容如下:
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <cassert>
#include "commn.hpp"
int main()
{
// 打開(kāi)管道文件
int wfd = open(pipename.c_str(), O_WRONLY);
if (wfd < 0)
{
std::cerr << "errno : " << errno << "strerror : " << strerror(errno) << std::endl;
exit(1);
}
//進(jìn)行通信
while(true)
{
char buffer[NUM];
std::cout << "請(qǐng)輸入內(nèi)容:";
fgets(buffer, sizeof(buffer), stdin);//獲取用戶(hù)輸入
buffer[strlen(buffer) - 1] = 0;
if (strcasecmp(buffer, "quit") == 0) break;//用戶(hù)輸入quit退出進(jìn)程
ssize_t size = write(wfd, buffer, strlen(buffer));
assert(size >= 0);
(void)size;
}
close(wfd);
return 0;
}
server.cc
作為讀端用于接收寫(xiě)端的輸入并打印,具體內(nèi)容如下:
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include "commn.hpp"
int main()
{
umask(0);
// 創(chuàng)建管道文件
int n = mkfifo(pipename.c_str(), mode);
if (n < 0)
{
std::cerr << "errno : " << errno << "strerror : " << strerror(errno) << std::endl;
exit(1);
}
std::cout << "create fifo file success" << std::endl;
// 以讀方式打開(kāi)管道文件
int rfd = open(pipename.c_str(), O_RDONLY);
if (rfd < 0)
{
std::cerr << "errno : " << errno << "strerror : " << strerror(errno) << std::endl;
exit(2);
}
// 進(jìn)行通信
while (true)
{
char buffer[NUM];
ssize_t size = read(rfd, buffer, sizeof(buffer) - 1);
buffer[size] = 0;
if (size > 0)
{
std::cout << "client send me :" << buffer << std::endl;//輸出接收的信息
}
else if (size == 0)
{
std::cout << "client quit, me too!" << std::endl;
break;
}
else
{
std::cerr << "errno : " << errno << "strerror : " << strerror(errno) << std::endl;
break;
}
}
close(rfd);
unlink(pipename.c_str()); // 刪除文件
return 0;
}
編譯代碼運(yùn)行查看結(jié)果:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-708894.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-708894.html
到了這里,關(guān)于[Linux]進(jìn)程間通信--管道的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!