Linux进程间通信的基本思想是:让两个进程看到一份公共的资源!
管道是Unix中最古老的进程间通信的形式。 我们把从⼀个进程连接到另⼀个进程的⼀个数据流称为⼀个”管道”
接下来看看管道的使用:
功能:创建匿名管道
c#include <unistd.h>
int pipe(int pipefd[2]);
返回值: On success, zero is returned. On error, -1 is returned, and errno is set appropriately. 成功返回0,失败返回错误代码! 参数说明: pipe() creates a pipe, a unidirectional data channel that can be used for interprocess communication. The array pipefd is used to return two file descriptors referring to the ends of the pipe. pipefd[0] refers to the read end of the pipe. pipefd[1] refers to the write end of the pipe. Data written to the write end of the pipe is buffered by the kernel until it is read from the read end of the pipe. pipe() 创建一个管道,一个可用于进程间通信的单向数据通道。该文件描述符数组用于返回引用管道末端的两个文件描述符。 pipefd [0]指的是管道的读端。 pipefd [1]指的是管道的写端。写入结束的数据管道由内核缓冲,直到从管道的读取端读取。
管道简单使用示例:从键盘读取数据,写⼊管道,读取管道,写到屏幕
c#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
int fds[2];
char buf[100] = { 0 };
size_t len;
ssize_t r_len;
if (pipe(fds) == -1) {
perror("make pipe");
exit(1);
}
//read from stdin
while (fgets(buf, 100, stdin)) {
len = strlen(buf);
//write to pipe
if (write(fds[1], buf, len) != len) {
perror("write to pipe");
break;
}
memset(buf, 0, 100);
//read from pipe
if ((r_len = read(fds[0], buf, (size_t)100)) == -1) {
perror("read form pipe");
break;
}
//write to stdout
if (write(1, buf, len) != len) {
perror("write to stdout");
break;
}
}
return 0;
}
上面的示例演示了管道的基本使用方式,但是不包含进程之间的通信!接下来看看父子进程之间的通信:
从文件描述符理解管道:
从内核角度理解管道:
所以可以看到,管道其实也是文件,在Linux下一切皆文件!
c#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
int pipefd[2];
//创建一个匿名管道,失败直接退出
if(pipe(pipefd) == -1){
perror("pipe");
exit(EXIT_FAILURE);
}
pid_t pid;
pid = fork();
if(pid == -1){
perror("pipe");
exit(EXIT_FAILURE);
}
//父进程
if(pid == 0){
close(pipefd[0]);
write(pipefd[1], "hello", 5);
close(pipefd[1]);
exit(EXIT_SUCCESS);
}
close(pipefd[1]);
char buf[10] = {0};
read(pipefd[0], buf, 10);
printf("buf = %s\n", buf);
return 0;
}
PIPE_BUF
时,linux将保证写入的原子性。PIPE_BUF
时,linux将不再保证写入的原子性。管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。命名管道是一种特殊类型的文件! 接下来使用一个脚本来演示用管道进行通信:
如果此时我们关闭读端,那么写端就会退出,道理很简单,和匿名管道一样,如果读端都关闭了那么此时如果写端还在写的话其实是一种资源浪费,于是操作系统直接向写端发信号终止写端进程!
如果当前打开操作是为读而打开FIFO时:
接下来是一个模拟客户端和服务器端通信的示例:
client.c
c#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <memory.h>
#define FIFONAME "mypipe"
int main() {
//创建一个命名管道
mkfifo(FIFONAME, 0664);
//打开这个命名管道
int fd = open(FIFONAME, O_WRONLY);
if(fd < 0){
return 1;
}
char buf[1024];
while(1){
printf("Please Enter Your Message To Server# ");
fflush(stdout);
//从标准输入读取信息
ssize_t s = read(0,buf,sizeof(buf));
buf[s-1] = 0;//覆盖掉之前的回车换行
//向管道写
write(fd,buf,strlen(buf));
}
close(fd);
return 0;
}
server.c
c#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#define FIFONAME "mypipe"
int main() {
//创建一个命名管道
mkfifo(FIFONAME, 0664);
//打开这个命名管道
int fd = open(FIFONAME, O_RDONLY);
if(fd < 0){
return 1;
}
char buf[1024];
while(1){
ssize_t s = read(fd, buf, sizeof(buf)-1);
if(s > 0){
//读取成功
buf[s] = 0;
printf("client# %s\n", buf);
}else if(s == 0){
//写端把文件描述符关闭
printf("client quit!server quit too!\n");
break;
}else{
break;
}
}
return 0;
}
Bash中的管道是采用匿名管道的方式进行通信,由于匿名管道只能用于在具有亲缘关系之间通信,但是由Bash开启的进程之间是属于兄弟关系,自然就可以通过匿名管道进行通信,多个进程(多个管道)的情况下只要每个进程关闭或打开相应的读写端,形成链式数据结构便可以进行通信了(再次佩写服Bash的大佬)
本文作者:Tim
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!