有名管道解决了无名管道进程只能是具有情缘关系的问题。
一、有名管道
无名管道是临时的,通信完成后自行消失。有名管道或者叫做命名管道(named pipe),可以实现没有亲缘关系的进程间通信,方法是使用FIFO文件。
1.1 shell中使用有名管道
一般而言,Linux中的fifo文件存储在/tmp/my_fifo
, shell中可以直接创建fifo
:
sudo mkfifo abc
然后进行读取和写入:
cat < /tmp/abc & #让FIFO立刻打印收到的内容
echo "Hello world" > /tmp/abc #需要root权限,注意不是sudo权限
cat < filename
将文件的内容打印至标准输出,如果此时另一进程将内容写至fifo
文件可以直接在终端观察到管道的内容。
1.2 mkfifo创建有名管道
除了在shell中直接创建外,在C/C++程序中使用mkfifo
接口创建:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char * filename, mode_t mode);
输入参数:
const char * filename 要打开的fifo
文件
mode_t mode 打开模式
mode | 含义 |
---|---|
O_RDONLY | 只读 |
O_WRONLY | 只写 |
O_NONBLOCK | 非阻塞 |
如以只读非阻塞方式打开文件,O_RDONLY|O_NONBLOCK
。
在没有设置O_NONBLOCK
时,读写都可能出现阻塞:
read
当文件为空时阻塞都关闭时,才会立刻返回0。
read是阻塞还是非阻塞读取取决于设备的设置,默认情况都是阻塞读取。
write
当文件为满阻塞
FIFO
文件大小一般为4096byte(4kb)在系统头文件limits.h
PIPE_BUF宏定义。
输出参数
返回一个实际读取的字节数n(n<PIPE_BUF)。
unlink - call the unlink function to remove the specified file
1.3 读写有名管道
有名管道需要两个进程分别完成读写打开操作,否则系统将阻塞其中一个进程直至另一个相异的进程加入。
有名管道形成回路后,读操作:
- 管道没有数据,读操作默认阻塞
- 管道有数据,但小于规定量,读操作读取所有数据立刻返回
- 管道有数据,但大于规定量,读操作读取期望数据立刻返回
有名管道形成回路后,写操作:
- 管道没有空间,写操作默认阻塞
- 管道有空间,但小于规定量,写满后阻塞
- 管道有空间,但大于规定量,写入规定量后返回
有名管道形成回路后,中间有一个退出:
- 未退出端为写操作,返回SIGPIPE信号
- 未退出端为阻塞读操作,返回0后立即返回
二、例子
对于例子1,使用time命令证明管道命令在不到0.1s的时间却读取了10MB数据,可以说速度相当快了。
2.1 C/S 的例子1
客户端:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
const char* FIFO_NAME="/tmp/my_fifo";
#define BUFFER_SIZE PIPE_BUF
#define TEN_MEG (1024*1024*10)
int main()
{
int pipe_fd;
int res;
int open_mode=O_WRONLY;
int byte_sent=0;
char buffer[BUFFER_SIZE+1];
/************************创建FIFO********************************/
if(access(FIFO_NAME,F_OK)==-1)//linux API,判断用户对于某个文件的权限
{
res=mkfifo(FIFO_NAME,0777);
if(res!=0)
{
fprintf(stderr,"Could not create fifo %s\n",FIFO_NAME);
exit(EXIT_FAILURE);
}
}
/************************打开FIFO********************************/
printf("Process %d opening FIFO O_WRONLY\n",getpid());
pipe_fd=open(FIFO_NAME,open_mode);
printf("Process %d result %d\n",getpid(),pipe_fd);
/************************读写FIFO********************************/
if(pipe_fd!=-1)
{
while(byte_sent<TEN_MEG)//没有写够足够的字节
{
res=write(pipe_fd,buffer,BUFFER_SIZE);//描述符 起始地址 大小,返回实际写的字节数
if(res==-1)
{
fprintf(stderr,"Write error on pipe\n");
exit(EXIT_FAILURE);
}
byte_sent+=res;//更新总的发送字节数
}
(void)close(pipe_fd);
}
else
{
exit(EXIT_FAILURE);
}
return 0;
}
客户端:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
const char * FIFO_NAME="/tmp/my_fifo";
#define BUFFER_SIZE PIPE_BUF
int main()
{
int pipe_fd;
int res;
int open_mode=O_RDONLY;
char buffer[BUFFER_SIZE+1];
int byte_read=0;
memset(buffer,'\0',sizeof(buffer));
printf("Process %d opening FIFO O_RDONLY\n",getpid());
pipe_fd=open(FIFO_NAME,open_mode);
printf("Process %d result %d\n",getpid(),pipe_fd);
if(pipe_fd!=-1)
{
do{
res=read(pipe_fd,buffer,BUFFER_SIZE);
byte_read+=res;
}while(res>0);
(void)close(pipe_fd);
}
else{
exit(EXIT_FAILURE);
}
printf("Process %d finished, %d byte read\n",getpid(),byte_read);
}
运行结果:
2.2 C/S 的例子2
客户端:
#include "client.h"
#include <ctype.h>
int main()
{
int server_fifo_fd,client_fifo_fd;
struct data_to_pass_st my_data;
int time_to_send;
char client_fifo[256];
server_fifo_fd=open(SERVER_FIFO_NAME,O_WRONLY);
if(server_fifo_fd==-1){
fprintf(stderr,"Sorry, no server\n");
exit(EXIT_FAILURE);
}
my_data.client_pid=getpid();
sprintf(client_fifo,CLIENT_FIFO_NAME,my_data.client_pid);
if(mkfifo(client_fifo,0777)==-1){
fprintf(stderr,"Sorry, can't make %s\n",client_fifo);
exit(EXIT_FAILURE);
}
for(time_to_send=0;time_to_send<5;time_to_send++){
sprintf(my_data.some_data,"Hello from %d",my_data.client_pid);
printf("%d sent %s, ",my_data.client_pid,my_data.some_data);
write(server_fifo_fd,&my_data,sizeof(my_data));
client_fifo_fd=open(client_fifo,O_RDONLY);
if(client_fifo_fd!=-1){
if(read(client_fifo_fd,&my_data,sizeof(my_data))>0){
printf("received: %s\n",my_data.some_data);
}
close(server_fifo_fd);
}
}
close(server_fifo_fd);
unlink(client_fifo);
exit(EXIT_SUCCESS);
}
服务端:
#include "client.h"
#include <ctype.h>
#include <iostream>
using namespace std;
int main()
{
int server_fifo_fd,client_fifo_fd;
struct data_to_pass_st my_data;
int read_res;
char client_fifo[256];
char * tmp_char_ptr;
mkfifo(SERVER_FIFO_NAME,0777);
server_fifo_fd=open(SERVER_FIFO_NAME,O_RDONLY);
if(server_fifo_fd==-1){
fprintf(stderr,"Server fifo failure\n");
exit(EXIT_FAILURE);
}
sleep(10);
do{
read_res=read(server_fifo_fd,&my_data,sizeof(my_data));
// if(read_res>0){
tmp_char_ptr=my_data.some_data;
while(*tmp_char_ptr){
*tmp_char_ptr=toupper(*tmp_char_ptr);
tmp_char_ptr++;
// }
sprintf(client_fifo,CLIENT_FIFO_NAME,my_data.client_pid);
client_fifo_fd=open(client_fifo,O_WRONLY);
if(client_fifo_fd!=-1){
int ttt=write(client_fifo_fd,&my_data,sizeof(my_data));
close(client_fifo_fd);
}
}
}while(read_res>0);
close(server_fifo_fd);
unlink(SERVER_FIFO_NAME);
exit(EXIT_SUCCESS);
}
//头文件
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define SERVER_FIFO_NAME "/tmp/server_fifo"
#define CLIENT_FIFO_NAME "/tmp/cli_%d_fifo"
#define BUFFER_SIZE 20
struct data_to_pass_st{
pid_t client_pid;
char some_data[BUFFER_SIZE-1];
};
三、小结
管道是一种抽象的概念,它充当了进程间信息交互的桥梁。常见的两种管道如下:
- 有名管道
- 无名管道
其中有名管道拥有更加丰富的访问控制规则,适用于没有亲缘关系的管道。无名管道则需要进程间有亲缘关系。
【1】《Linux程序设计(第五版)》
【2】《Linux高级程序设计(第三版)》