文章目錄
- 1 管道符【|】的理解
- 2 匿名管道
- 2.1 創建匿名管道:使用pipe介面
- 2.2 pipe引數fd陣列再理解
- 2.3 程式演示:用匿名管道進行讀寫演示
- 2.4 管道特征
- 2.5 驗證管道通信方式是單雙工通信方式
- 2.6 父子行程通過匿名管道進行行程間通信
- 2.7 匿名管道的最大寫入量
- 2.8 將匿名管道讀寫端檔案描述符設定為非阻塞屬性
- 2.9 匿名管道的一些極端操作及現象解釋
- 3 命名管道
- 3.1 創建命名管道的兩種方式
- 3.1.1 命令創建命名管道
- 3.1.2 函式創建命名管道
- 3.2 使用命名管道實作Client和Server通信
前言:
Q:為什么需要行程間通信呢?
A:原因是由于行程擁有自己獨立的行程虛擬地址空間,從而導致了行程的獨立性,通過行程間的通信,可以讓不同的行程之間進行協作,
1 管道符【|】的理解
ps aux | grep XXX
該命令的作用:是將當前正在運行的所有行程的行程名帶有XXX的行程過濾列印出來
- ps和grep是兩個不同的命令,同時也是兩個不同的程式
- ps程式的執行結果,通過管道,傳輸給grep程式,
- 換言之,即ps程式的輸出作為grep程式的輸入
管道就是在內核中開辟一塊記憶體,也叫緩沖區

2 匿名管道
匿名管道概念
匿名管道是程式在內核中開辟出來的一塊沒有識別符號的記憶體空間
2.1 創建匿名管道:使用pipe介面
int pipe(int pipefd[2]);
引數:
- fd是一個輸出型引數,在呼叫函式內部進行賦值并帶出函式
- fd[2]:整形陣列,有兩個元素:fd[0] 和 fd[1]
回傳值:0 代表成功,-1代表失敗
頭檔案:unistd.h
程式演示:查看輸出型引數fd從pipe函式中帶出來了什么資料?
#include <stdio.h>
#include <unistd.h>
int main()
{
int fd[2];
int ret_pipe = pipe(fd);
if(ret_pipe < 0)
perror("pipe");
else
printf("fd[0] = %d , fd[1] = %d\n",fd[0],fd[1]);
while(1)
{
sleep(1);
}
return 0;
}
輸出結果:
fd[0] = 3 , fd[1] = 4
查看該行程使用檔案描述符情況
[gongruiyang@localhost ~]$ ll /proc/3456/fd/
總用量 0
lrwx------. 1 gongruiyang gongruiyang 64 1月 1 16:16 0 -> /dev/pts/0
lrwx------. 1 gongruiyang gongruiyang 64 1月 1 16:16 1 -> /dev/pts/0
lrwx------. 1 gongruiyang gongruiyang 64 1月 1 16:16 2 -> /dev/pts/0
lr-x------. 1 gongruiyang gongruiyang 64 1月 1 16:16 3 -> pipe:[38151]
l-wx------. 1 gongruiyang gongruiyang 64 1月 1 16:16 4 -> pipe:[38151]
其中3和4這兩個檔案描述符是軟鏈接檔案,指向內核中一塊無識別符號的記憶體
2.2 pipe引數fd陣列再理解
-
fd陣列當中保存兩個檔案描述符
-
fd[0]是緩沖區的讀端,從匿名管道中讀取資料
-
fd[1]是緩沖區的寫端,向匿名管道中寫入資料
-
讀寫是相對于緩沖區中資料的

2.3 程式演示:用匿名管道進行讀寫演示
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2];
int ret_pipe = pipe(fd);
if(ret_pipe < 0)
perror("pipe");
else
{
//向 匿名管道 中寫入w_buf中的資料
char* w_buf = "Hello World!";
write(fd[1],w_buf,strlen(w_buf));
//從 匿名管道 中讀取資料到r_buf中
char r_buf[1024];
read(fd[0],r_buf,sizeof(r_buf) - 1);
//輸出讀取出來的資料
puts(r_buf);
}
return 0;
}
Hello World!
2.4 管道特征
- 管道是單雙工通信:資料只能從寫端流向讀端
- 管道提供流式服務:讀端可以決定每次讀取多少個位元組,讀走多少資料,管道中就減少多少資料
- 管道的生命周期跟隨行程
2.5 驗證管道通信方式是單雙工通信方式
輔助介面
int fcntl(int fd, int cmd, ... /* arg */ );
功能:獲取對某個檔案的操作權限屬性
引數:
- fd : 待查詢檔案描述符
- cmd
| 可選項 | 含義 |
|---|---|
| F_GETFL | 獲取檔案描述符屬性資訊 |
| F_SETFL | 設定檔案描述符屬性資訊 |
回傳值:當時F_GETFL的時候,回傳的是檔案描述符的操作屬性值
| 操作屬性值 | 對應宏 | 含義 |
|---|---|---|
| 0 | O_RDONLY | 只讀權限 |
| 1 | O_WRONLY | 只寫權限 |
| 2 | O_RDWR | 讀寫權限 |
程式演示:驗證管道是單雙工通信
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
int fd[2];
int ret_pipe = pipe(fd);
if(ret_pipe < 0)
perror("pipe");
else
{
int authority_r = fcntl(fd[1],F_GETFL);
int authority_w = fcntl(fd[0],F_GETFL);
printf("fd[1] : %d\n",authority_r);
printf("fd[0] : %d\n",authority_w);
}
return 0;
}
fd[1] : 1
fd[0] : 0
-
由于fd[1]檔案描述符的操作權限值是1,對應著O_WRONLY宏,意為只寫權限
-
由于fd[0]檔案描述符的操作權限值是0,對應著O_RDONLY宏,意為只讀權限
-
所以匿名管道的通信方式是單雙工通信
2.6 父子行程通過匿名管道進行行程間通信
匿名管道只支持具有情緣關系的行程之間進行通信
原因:沒有親緣關系的行程之間無法共享同一個管道
父子行程通信程序:
- 創建匿名管道
- 創建子行程
- 一個作為寫行程,一個作為讀行程
程式演示:子行程向匿名管道寫入資料,父行程從匿名管道中讀取資料
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
//1.創建匿名管道
int fd[2];
int ret_pipe = pipe(fd);
if(ret_pipe < 0)
{
perror("pipe");
return -1;
}
//2.創建子行程
int pid = fork();
if(pid < 0)
{
perror("fork");
return -1;
}
else if(pid == 0)
{
//子行程 向 管道 中寫入
close(fd[0]); // 關閉讀端
const char* w_buf = "Hello World!";
write(fd[1],w_buf,strlen(w_buf));
}
else
{
//父行程 從 管道 中讀取
close(fd[1]); // 關閉寫端
char r_buf[1024];
read(fd[0],r_buf,sizeof(r_buf));
puts(r_buf);
}
return 0;
}
輸出
Hello World!
提醒:由于父子行程異步執行,如果父行程先進行讀取,而子行程還未寫入,父行程就會阻塞在讀取中,等待子行程寫入后再讀取
2.7 匿名管道的最大寫入量
程式演示:測驗的出匿名管道的最大寫入量
思路:一直寫入并記錄寫入了多少
#include <stdio.h>
#include <unistd.h>
int main()
{
int fd[2];
int ret_pipe = pipe(fd);
if(ret_pipe < 0 )
{
perror("pipe");
return -1;
}
int count = 0;
while(1)
{
write(fd[1],"h",1);
count++;
printf("count:%d\n",count);
}
return 0;
}
count:65536
所以匿名管道最大寫入量為64K
- pipe_MAXSIZE = 64K ,最多寫入64K的資料
- pipe_buf = 4K ,行程向匿名管道中寫入的時候,如果寫入的資料量小于于4K時,才能保證寫入的原子性
寫入的原子性:寫入只有兩種可能:
全部寫入
一點沒寫進去
不存在只寫進去一部分,這就是寫入的原子性
2.8 將匿名管道讀寫端檔案描述符設定為非阻塞屬性
檔案描述符的非阻塞屬性:O_NONBLOCK
原始碼定義在/usr/include/bits/fcntl-linux.h中
#define O_NONBLOCK 04000
| 宏 | 8進制 | 2進制 | 10進制 |
|---|---|---|---|
| O_NONBLOCK | 04000 | 00000000 00000000 00001000 00000000 | 2048 |
我們的目的是將 讀寫端的檔案描述符 加上非阻塞屬性,即
fd[0]讀端:只讀屬性(O_RDONLY) + 非阻塞屬性(O_NONBLOCK) —> O_RDONLY | O_NONBLOCK
fd[1]寫端:只寫屬性(O_WRONLY) + 非阻塞屬性(O_NONBLOCK) —> O_WRONLY | O_NONBLOCK
這里的按位或就相當于將非阻塞屬性和原有屬性相加
程式演示:將 匿名管道讀寫端加上非阻塞屬性
#include <unistd.h>
#include <fcntl.h>
int main()
{
//創建匿名管道
int fd[2];
int ret_pipe = pipe(fd);
if(ret_pipe < 0)
{
perror("pipe");
return -1;
}
//獲取讀寫端檔案描述符原有屬性
int authority_r = fcntl(fd[0],F_GETFL);
int authority_w = fcntl(fd[1],F_GETFL);
//將讀寫端檔案描述符加上非阻塞屬性
fcntl(fd[0],F_SETFL,authority_r | O_NONBLOCK);
fcntl(fd[1],F_SETFL,authority_w | O_NONBLOCK);
//查看現在的檔案屬性值
int authority_r_m = fcntl(fd[0],F_GETFL);
int authority_w_m = fcntl(fd[1],F_GETFL);
printf("fd[0]-authority : %d\n",authority_r_m);
printf("fd[1]-authority : %d\n",authority_w_m);
return 0;
}
fd[0]-authority : 2048
fd[1]-authority : 2049
按位或之后將非阻塞屬性加到了 匿名管道讀寫端檔案描述符 上
2.9 匿名管道的一些極端操作及現象解釋
- 4中情況及其現象解釋:
操作一:將寫端檔案描述符設定為非阻塞屬性,讀端檔案描述符仍然為阻塞屬性,讀端檔案描述符不關閉,但是也不讀取資料,寫端一直寫入資料
現象:會將管道寫滿,滿之后再呼叫write函式寫入失敗回傳-1,表示資源不可用
操作二:將寫端檔案描述符設定為非阻塞屬性,讀端檔案描述符仍然為阻塞屬性,讀端檔案描述符關閉,寫端一直寫入資料
現象:呼叫write的行程會收到SIGPIPE信號,導致呼叫write的行程退出,若是子行程退出則會變成僵尸行程,若是父行程退出則子行程變成孤兒行程
操作三:將讀端檔案描述符設定為非阻塞屬性,寫端檔案描述符仍然為阻塞屬性,寫端檔案描述符不關閉,但是也不寫入資料,讀端一直讀取資料
現象:read函式會回傳-1,表示資源不可用
操作四:將讀端檔案描述符設定為非阻塞屬性,寫端檔案描述符仍然為阻塞屬性,寫端檔案描述符關閉,讀端一直讀取資料
現象:read函式回傳0,表示沒有讀取到任何內容
3 命名管道
命名管道是在內核中開辟的一段緩沖區,這段緩沖區是有識別符號的
也就意味著,不同的行程通過識別符號就可以找到這個緩沖區,不再要求行程之間具有親緣關系,任意行程間都可以通過命名管道實作通信
3.1 創建命名管道的兩種方式
3.1.1 命令創建命名管道
mkfifo命令:使用指定檔案名創建FIFO,也成為:命名管道
mkfifo 檔案名
演示實體
[gongruiyang@localhost fifoTest]$ mkfifo fifo
[gongruiyang@localhost fifoTest]$ ll
總用量 0
prw-rw-r--. 1 gongruiyang gongruiyang 0 1月 1 21:53 fifo
檔案名為fifo的命名管道檔案默認權限為0664,且該檔案不可使用vim編輯
3.1.2 函式創建命名管道
mkfifo函式:在代碼中創建命名管道
int mkfifo(const char *pathname, mode_t mode);
頭檔案:
- sys/stat.h
- sys/types.h
引數:
- pathname : 檔案名
- mode : 權限
回傳值:0 代表創建成功;-1代表創建失敗
程式演示:用mkfifo創建命名管道
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int mkfifo_ret = mkfifo("./fifo_test",0664);
if(mkfifo_ret < 0)
{
perror("mkfifo");
return -1;
}
return 0;
}
[gongruiyang@localhost fifoTest]$ ll fifo_test
prw-rw-r--. 1 gongruiyang gongruiyang 0 1月 1 22:20 fifo_test
3.2 使用命名管道實作Client和Server通信
思路:Server端創建命名管道并阻塞等待Client端打開匿名命名管道進行輸入資料,資料放入管道中后,Server端進行讀取并列印出來
Server端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
int main()
{
// 創建 命名管道
int ret_mkfifo = mkfifo("mypipe",0664);
if(ret_mkfifo < 0)
{
perror("mkpipe");
return -1;
}
// sever端以 只讀 方式打開 命名管道
int ret_fd = open("mypipe",O_RDONLY);
if(ret_fd < 0)
{
perror("open");
return -1;
}
while(1)
{
char buf[1024] = { 0 }; //用于存放讀進來的資料
printf("Please wait...\n");
// 讀取資料,若無資料 則阻塞等待
ssize_t s = read(ret_fd,buf,sizeof(buf) - 1);
if( s > 0 )
{
buf[s] = 0;
printf("client say : %s\n",buf);
}
else if(s == 0)
{
printf("Client quit,exit now!\n");
exit(EXIT_SUCCESS);
}
else
perror("read");
}
close(ret_fd);
return 0;
}
Client端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
// 客戶端以只寫方式打開 命名管道
int ret_fd = open("mypipe",O_WRONLY);
if(ret_fd < 0)
{
perror("open");
return -1;
}
while(1)
{
char buf[1024] = { 0 };
printf("Please Enter Message : ");
fflush(stdout);
// 從標準輸入中讀取資料放入buf中
ssize_t ret_read = read(0,buf,sizeof(buf) - 1);
if(ret_read > 0)
{
buf[ret_read] = 0;
// 從buf中向命名管道中寫入資料
write(ret_fd,buf,strlen(buf));
}
else
perror("read");
}
close(ret_fd);
return 0;
}
互動演示
[gongruiyang@localhost TestPipeCS]$ ./Server
Please wait...
client say : Hello Server!
Please wait...
client say : I'm Client!
Please wait...
Client quit,exit now!
[gongruiyang@localhost TestPipeCS]$ ./Client
Please Enter Message : Hello Server!
Please Enter Message : I'm Client!
Please Enter Message : ^C
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/243880.html
標籤:其他
上一篇:Java實作單鏈表
