1. 簡(jiǎn)介
分類
2 匿名管道
2.1 單工管道
程序進(jìn)程與Shell命令行進(jìn)程單項(xiàng)通信。
① 打開管道FILE* popen (const char *command, const char *open_mode)
No. |
參數(shù) |
含義 |
1 |
command |
命令行字符串 |
2 |
open_mode |
"r" 只讀"w" 只寫 |
No. |
返回值 |
含義 |
1 |
NULL |
文件描述符 |
2 |
非NULL
|
打開失敗 |
② 讀取size_t fread ( void *buffer, size_t size, size_t count, FILE *stream)
No. |
參數(shù) |
含義 |
1 |
buffer |
用于接收數(shù)據(jù)的內(nèi)存地址 |
2 |
size |
讀取每個(gè)數(shù)據(jù)項(xiàng)的字節(jié)數(shù) |
3 |
count |
數(shù)據(jù)項(xiàng)個(gè)數(shù) |
4 |
stream |
輸入流 |
No. |
返回值 |
含義 |
1 |
>count |
出錯(cuò) |
2 |
正數(shù) |
真實(shí)讀取的數(shù)據(jù)項(xiàng)個(gè)數(shù) |
③ 寫入size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream)
No. |
參數(shù) |
含義 |
1 |
buffer |
寫入數(shù)據(jù)的內(nèi)存地址 |
2 |
size |
寫入數(shù)據(jù)項(xiàng)的字節(jié)數(shù) |
3 |
count |
寫入數(shù)據(jù)項(xiàng)的個(gè)數(shù) |
4 |
stream |
目標(biāo)文件指針 |
No. |
返回值 |
含義 |
1 |
>count |
出錯(cuò) |
2 |
正數(shù) |
真實(shí)寫入的數(shù)據(jù)項(xiàng)個(gè)數(shù) |
④ 關(guān)閉管道int pclose(FILE *stream);
No. |
參數(shù) |
含義 |
1 |
stream |
文件描述符 |
No. |
返回值 |
含義 |
1 |
-1 |
失敗 |
2 |
0 |
成功 |
示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
FILE* fd = popen("wc","w");
//FILE* fd = popen("ls -l","r");
//char str[] = "123 456";
char str[] = "123 456\n";
size_t n = fwrite(str,sizeof(char),sizeof(str),fd);
if(n > sizeof(str)){
fprintf(stderr,"FILE:%d,LINE:%d-fwrite error",__FILE__,__LINE__);
exit(EXIT_FAILURE);
}
pclose(fd);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
FILE* fd = popen("ps -ef","r");
//FILE* fd = popen("ls -l","r");
char buf[BUFSIZ];
size_t count = 0;
printf("read data:\n");
do{
memset(buf,'\0',BUFSIZ);
size_t n = fread(buf,sizeof(char),BUFSIZ-1,fd);
if( n > BUFSIZ - 1 ){
perror("fread error");
exit(EXIT_FAILURE);
}
count += n;
printf("\n%d:\n%s",n,buf);
}while(!feof(fd));
printf("total size:%ld\n",count);
pclose(fd);
}
本質(zhì)
- 啟動(dòng)shell和命令兩個(gè)進(jìn)程句柠,從命令進(jìn)程中讀/寫文件流袁勺。
- 解決exec和system無法返回輸出數(shù)據(jù)問題
特點(diǎn)
- 方便使用系統(tǒng)自帶功能,并且可以執(zhí)行比較復(fù)雜Shell
- 默認(rèn)啟動(dòng)兩個(gè)進(jìn)程,效率較低。
操作 |
管道 |
文件 |
打開 |
popen() |
fopen() |
關(guān)閉 |
pclose() |
fclose() |
2.2 半雙工管道
① 創(chuàng)建管道int pipe(int filedes[2])
No. |
參數(shù) |
含義 |
1 |
filedes[0] |
讀 |
2 |
filedes[1] |
寫 |
No. |
返回值 |
含義 |
1 |
-1 |
失敗 |
2 |
0 |
成功 |
② 讀取ssize_t write(int fd, const void *buf, size_t nbyte)
No. |
參數(shù) |
含義 |
1 |
fd |
文件描述符 |
2 |
buf |
寫入數(shù)據(jù)的內(nèi)存單元 |
3 |
nbyte |
寫入文件指定的字節(jié)數(shù) |
No. |
返回值 |
含義 |
1 |
-1 |
出錯(cuò) |
2 |
正數(shù) |
寫入的字節(jié)數(shù) |
③ 寫入ssize_t read(int fd, void *buf, size_t count)
No. |
參數(shù) |
含義 |
1 |
fd |
文件描述符 |
2 |
buf |
讀取數(shù)據(jù)的內(nèi)存單元 |
No. |
返回值 |
含義 |
1 |
-1 |
出錯(cuò) |
2 |
0 |
無數(shù)據(jù) |
3 |
正數(shù) |
讀取的字節(jié)數(shù) |
④ 控制int fcntl(int fd, int cmd, long arg)
如果管道是空的,read()
默認(rèn)是阻塞
No. |
參數(shù) |
含義 |
1 |
fd |
文件描述符 |
2 |
cmd |
F_GETFL :獲取文件描述符狀態(tài);F_SETFL :設(shè)置文件描述符狀態(tài); |
3 |
arg |
O_NONBLOCK :非阻塞;O_BLOCK :阻塞 |
把文件描述符改為非阻塞的fcntl(filedes,F_SETFL,O_NONBLOCK);
⑤ 關(guān)閉管道close(filedes)
示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
int fd[2];
pipe(fd);
char in[] = "Hello pipe";
write(fd[1],in,sizeof(in));
printf("write:%s\n",in);
char out[sizeof(in)]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("read:%s\n",out);
close(fd[0]);
close(fd[1]);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
int fd[2];
pipe(fd);
if(!fork()){// child
char in[] = "Hello pipe";
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
}else{// parent
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
}
close(fd[0]);
close(fd[1]);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
int fd[2];
pipe(fd);
if(!fork()){// child
char in[] = "Hello pipe";
sleep(3);
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
}else{// parent
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
}
close(fd[0]);
close(fd[1]);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
int main(){
int fd[2];
pipe(fd);
if(!fork()){// child
char in[] = "Hello pipe";
sleep(3);
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
}else{// parent
fcntl(fd[0],F_SETFL,O_NONBLOCK);
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
}
close(fd[0]);
close(fd[1]);
}
- 更加規(guī)范的寫法(關(guān)閉不需要的管道)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
int main(){
int fd[2];
pipe(fd);
if(!fork()){// child
close(fd[0]);
char in[] = "Hello pipe";
sleep(3);
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
close(fd[1]);
}else{// parent
close(fd[1]);
fcntl(fd[0],F_SETFL,O_NONBLOCK);
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
close(fd[0]);
}
}
2.3 FIFO管道/命名管道
① 創(chuàng)建命名管道int mkfifo(pathname,mode)
No. |
參數(shù) |
含義 |
1 |
pathname |
文件路徑妹卿,文件必須不存在 |
2 |
mode |
模式 |
No. |
返回值 |
含義 |
1 |
0 |
成功 |
2 |
非零 |
失敗 |
#include <stdio.h>
#include <unistd.h>
int main(){
if(-1 == mkfifo("/tmp/test",0644)){
perror("mkfifo error");
return 1;
}
}
注意:
- 管道文件通常在
/tmp
目錄下創(chuàng)建。
- 管道文件大小通常是0
② 打開FIFO文件int open(const char *path, int mode)
No. |
參數(shù) |
含義 |
1 |
pathname |
文件路徑 |
2 |
mode |
模式 |
No. |
模式 |
含義 |
1 |
O_RDONLY |
阻塞只讀 |
2 |
O_RDONLY | O_NONBLOCK |
非阻塞只讀 |
3 |
O_WRONLY |
阻塞只寫 |
4 |
O_WRONLY | O_NONBLOCK |
非阻塞只寫 |
No. |
返回值 |
含義 |
1 |
-1 |
失敗 |
2 |
其他 |
文件描述符 |
示例
- 阻塞讀
#include <iostream>
#include <unistd.h>
#include <fcntl.h> // open() O_RDONLY
using namespace std;
#define BUFSIZE 258
int main(){
// 打開命名管道
int fd = open("/tmp/fifo",O_RDONLY);
if(-1 == fd){
perror("open error");
return EXIT_FAILURE;
}else{
cout << "filo open ok" << endl;
}
// 讀取字符串
char buffer[BUFSIZE] = {0};
read(fd,buffer,BUFSIZE);
// 打印讀取的字符串
cout << buffer << endl;
// 關(guān)閉管道
close(fd);
return EXIT_SUCCESS;
}
#include <iostream>
#include <fcntl.h> // open() O_WRONLY
#include <unistd.h>
using namespace std;
int main(){
// 打開命名管道
int fd = open("/tmp/fifo",O_WRONLY);
if(-1 == fd){
perror("open error");
return EXIT_FAILURE;
}else{
cout << "filo open ok" << endl;
}
// 讀取字符串
string str;
getline(cin,str);
// 寫入管道
write(fd,str.c_str(),str.size()+1);
// 關(guān)閉管道
close(fd);
return EXIT_SUCCESS;
}
- 寫非阻塞
說明:
- 只需要在
open()
添加O_NONBLOCK
。
- 寫
open()
非阻塞纽帖,讀open()
阻塞的情況宠漩。讀open()
需要先執(zhí)行举反,否則懊直,寫open()
會(huì)出現(xiàn)No such device or address
。
#include <iostream>
#include <fcntl.h> // open() O_WRONLY O_NONBLOCK
#include <unistd.h>
using namespace std;
int main(){
// 打開命名管道
int fd = open("/tmp/fifo",O_WRONLY|O_NONBLOCK);
if(-1 == fd){
perror("open error");
return EXIT_FAILURE;
}else{
cout << "filo open ok" << endl;
}
// 讀取字符串
string str;
getline(cin,str);
// 寫入管道
write(fd,str.c_str(),str.size()+1);
// 關(guān)閉管道
close(fd);
return EXIT_SUCCESS;
}
- 讀非阻塞
說明:
- 只需要在
open()
添加O_NONBLOCK
火鼻。
- 寫
open()
阻塞室囊,讀open()
非阻塞的情況。讀read()
需要處理寫open()
未執(zhí)行(read()
返回0
)和讀不到數(shù)據(jù)(寫open()
打開但是沒有寫數(shù)據(jù)魁索,read()
返回-1
)的情況融撞。
#include <iostream>
#include <unistd.h>
#include <fcntl.h> // open() O_RDONLY O_NONBLOCK
using namespace std;
#define BUFSIZE 258
int main(){
// 打開命名管道
int fd = open("/tmp/fifo",O_RDONLY|O_NONBLOCK);
if(-1 == fd){
perror("open error");
return EXIT_FAILURE;
}else{
cout << "filo open ok" << endl;
}
// 讀取字符串
char buffer[BUFSIZE] = {0};
int n = -1;
while(n<=0){
n=read(fd,buffer,BUFSIZE);
}
// 打印讀取的字符串
cout << buffer << endl;
// 關(guān)閉管道
close(fd);
return EXIT_SUCCESS;
}
特點(diǎn)
- 可以是非親緣進(jìn)程之間
- FIFO首先會(huì)阻塞在
open()
,等待讀寫文件的文件描述符都打開粗蔚。接著阻塞在read()
/write()
操作尝偎,讀寫操作需要同時(shí)執(zhí)行。
案例
3. 通信分類
No. |
類型 |
創(chuàng)建/打開 |
關(guān)閉 |
讀 |
寫 |
1 |
單工 |
popen() |
pclose |
fread() |
fwrite() |
2 |
半雙工 |
pipe() /open()
|
close() |
read() |
write() |
3 |
FIFO半雙工 |
mkfifo() /open
|
close() /unlink()
|
read() |
write() |
4 |
全雙工 |
socketpair() |
close() |
read() |
write() |
3.1 單進(jìn)程管道
管道通常用于進(jìn)程間通信
3.2 父子進(jìn)程單向管道
3.3.1 概念圖解
父進(jìn)程關(guān)閉fd[0] 子進(jìn)程關(guān)閉fd[1]
3.3.2 原理圖解
3.3 父子進(jìn)程雙向管道
4. 文件描述符
4.1 Linux文件讀寫與標(biāo)準(zhǔn)C的文件讀寫
文件描述符
|
文件描述符 |
文件流 |
數(shù)據(jù) |
int 整數(shù) |
FILE 指針 |
標(biāo)準(zhǔn) |
POSIX |
ANSI C |
打開 |
open |
fopen |
關(guān)閉 |
close |
fclose |
讀 |
read |
fread |
寫 |
write |
fwrite |
定位 |
lseek |
fseek |
- 文件流是文件描述符之上的封裝鹏控。文件流通過增加緩沖區(qū)減少讀寫系統(tǒng)調(diào)用次數(shù)來提高讀寫效率致扯。在進(jìn)程的用戶空間封裝的
FILE
結(jié)構(gòu),以提高可移植性和效率当辐。
4.2 文件描述符原理
Linux內(nèi)核使用三個(gè)關(guān)聯(lián)的數(shù)據(jù)結(jié)構(gòu)抖僵,表示打開的文件。
4.3 命令lsof
lsof
(list open files):列出當(dāng)前系統(tǒng)打開文件
No. |
列名 |
含義 |
1 |
COMMAND |
進(jìn)程的名稱 |
2 |
PID |
進(jìn)程標(biāo)識(shí)符 |
3 |
USER |
進(jìn)程所有者 |
4 |
FD |
文件描述符缘揪,應(yīng)用程序通過文件描述符識(shí)別該文件耍群。如cwd、txt等 |
5 |
TYPE |
文件類型找筝,如PIPE 蹈垢、DIR 、REG 等 |
6 |
DEVICE |
指定磁盤的名稱 |
7 |
SIZE |
文件的大小 |
8 |
NODE |
索引節(jié)點(diǎn)(文件在磁盤上的標(biāo)識(shí)) |
9 |
NAME |
打開文件的確切名稱 |
No. |
命令 |
作用 |
1 |
lsof 文件名 |
查看文件打開信息 |
2 |
lsof -d 文件描述符 |
查看文件描述符信息 |
3 |
lsof -p PID |
查看進(jìn)程PID打開的文件信息 |
4.3 文件描述符復(fù)制
分類 |
文件描述符 |
文件號(hào) |
標(biāo)準(zhǔn)輸入 |
STDIN_FILENO |
0 |
標(biāo)準(zhǔn)輸出 |
STDOUT_FILENO |
1 |
標(biāo)準(zhǔn)出錯(cuò)信息 |
STDERR_FILENO |
2 |
內(nèi)核為每個(gè)進(jìn)程創(chuàng)建的文件描述符袖裕。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(){
char str[1024];
scanf("%s",str);
printf("%s\n",str);
read(STDIN_FILENO,str,sizeof(str));
write(STDOUT_FILENO,str,strlen(str));
}
① 函數(shù)int dup(int oldfd)
No. |
參數(shù) |
含義 |
1 |
oldfd |
舊文件描述符 |
No. |
返回值 |
含義 |
1 |
-1 |
失敗 |
2 |
其他 |
新文件描述符 |
② 函數(shù)int dup2(int oldfd, int newfd)
No. |
參數(shù) |
含義 |
1 |
oldfd |
舊文件描述符 |
2 |
newfd |
新文件描述符 |
No. |
返回值 |
含義 |
1 |
-1 |
失敗 |
2 |
其他 |
最小及尚未使用的文件描述符 |
示例
- 復(fù)制標(biāo)準(zhǔn)輸出
新文件描述符與舊文件描述符不同耘婚,但是具備舊文件描述符功能
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(){
int fd = dup(STDOUT_FILENO);
fprintf(fdopen(fd,"w"),"%d printf:Hello dup\n",fd);
}
- 復(fù)制文件描述符
新文件描述符與舊文件描述符不同,但是具備舊文件描述符功能
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
int main(){
int fd = open("./test",O_CREAT|O_RDWR,FILE_MODE);
char str[]="Hello dup\n";
write(fd,str,sizeof(str));
int cp_fd = dup(fd);
printf("copy %d to %d",fd,cp_fd);
write(cp_fd,str,sizeof(str));
//fprintf(fdopen(fd,"w"),"%d printf:Hello dup\n",fd);
close(fd);
}
- 把文件描述符重定向(復(fù)制)到標(biāo)準(zhǔn)輸出
printf()
直接輸出到文件中陆赋,不再輸出到終端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
int main(){
int fd = open("./test",O_CREAT|O_RDWR,FILE_MODE);
char str[]="Hello dup\n";
dup2(fd,STDOUT_FILENO);
printf("%d printf:Hello dup\n",fd);
}
- 把文件描述符重定向(復(fù)制)到標(biāo)準(zhǔn)輸出沐祷,并且輸出后還原
注意把標(biāo)準(zhǔn)輸出流從文件重定向(復(fù)制)回終端,需要清除緩沖區(qū)攒岛。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
int main(){
int save_fd = dup(STDOUT_FILENO);
int fd = open("./test",O_CREAT|O_RDWR,FILE_MODE);
if(-1 == dup2(fd,STDOUT_FILENO)){
perror("dup2 error0");
return 1;
}
close(fd);
printf("%d printf:Hello dup\n",fd);
fflush(stdout);// 一定要清除緩沖區(qū)赖临,否則會(huì)輸出到終端
if(-1 == dup2(save_fd,STDOUT_FILENO)){
perror("dup2 error");
return 1;
}
close(save_fd);
printf("%d printf:this is save\n",save_fd);
}
特點(diǎn)
必須是親緣進(jìn)程之間
dup()/dup2()原理圖