IPC(InterProcess Communication)的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中Socket和Stream支持不同主机上的两个进程IPC。
-
管道(Pipes):管道是一种半双工的通信方式,用于具有亲缘关系的进程间通信。它通常用于父子进程或者兄弟进程之间。管道可以是匿名管道,也可以是命名管道。
-
消息队列(Message Queues):消息队列是一种通过消息传递进行通信的方式。发送方将消息发送到队列中,接收方从队列中接收消息。消息队列可以实现进程间的异步通信。
-
信号量(Semaphores):信号量是一种计数器,用于控制对共享资源的访问。它通常用于同步进程之间的操作,以避免竞争条件。
-
共享内存(Shared Memory):共享内存是一种允许多个进程访问同一块内存区域的方式。这种方式通常比较高效,但需要处理进程间的同步和互斥。
-
套接字(Sockets):套接字是一种网络编程接口,不仅可以用于不同主机间的进程通信,也可以用于同一主机上的进程通信。套接字可以基于网络协议(如TCP/IP)或本地协议(如UNIX域套接字)实现。
一、管道
管道(Pipes)是一种在Unix和类Unix系统中常见的进程间通信(IPC)机制,用于在具有亲缘关系的进程之间传递数据。管道是一个单向通道,允许一个进程将输出直接发送到另一个进程的输入。它是一种半双工通信方式,即数据只能单向流动,不能双向传输。
类型
-
匿名管道(Anonymous Pipes):匿名管道是最简单的管道形式,它只存在于内存中,并且通常用于父子进程之间的通信。在Unix系统中,可以使用
pipe()
系统调用创建匿名管道。 -
命名管道(Named Pipes):命名管道是一种具有持久性的管道,它以文件的形式存在于文件系统中,并允许无关进程之间进行通信。命名管道通常用于不具有亲缘关系的进程之间的通信。
特点
-
单向通信:管道是单向的,数据只能沿着管道的方向流动,不能双向传输。
-
半双工:管道是半双工的,即数据只能在一个方向上传输。如果需要双向通信,通常需要创建两个管道。
-
FIFO(先进先出):管道遵循FIFO的原则,即数据按照写入的顺序从管道中读取出来。
使用
在Unix系统中,可以使用pipe()
系统调用创建匿名管道,它返回两个文件描述符,一个用于读取,一个用于写入。然后可以使用fork()
创建一个新的进程,在父子进程之间共享管道,并使用dup2()
系统调用将管道文件描述符重定向到标准输入或标准输出。接着,一个进程可以通过写入管道的方式向另一个进程发送数据,另一个进程则可以通过读取管道来接收数据。
示例
下面是一个简单的C语言示例,演示了如何在父子进程之间使用匿名管道进行通信:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
int fd[2];
int pid;
char buf[128];
//创建管道
if(pipe(fd) == -1) {
printf("creat pipe failed\n");
}
//创建子进程
pid = fork();
if(pid < 0) {
printf("creat child faild\n");
} else if(pid > 0) { //父进程
printf("this is father\n");
close(fd[0]); //关闭读取端
//向管道写数据
write(fd[1], "hello from father", strlen("hello from father"));
wait(NULL);
} else {
printf("this is child\n");
close(fd[1]); //关闭写入端
read(fd[0], buf, sizeof(buf)); //从管道读数据
printf("child print: %s\n", buf);
exit(1);
}
return 0;
}
程序执行结果如下:
命名管道(Named Pipes)的使用:
命名管道是一种具有持久性的管道,它以文件的形式存在于文件系统中,并允许无关进程之间进行通信。相比于匿名管道,命名管道允许不具有亲缘关系的进程之间进行通信。
创建命名管道
在Unix/Linux系统中,可以使用mkfifo()
函数创建命名管道。命名管道创建后,会在文件系统中生成一个特殊类型的文件,它可以像普通文件一样被打开、读取和写入。
使用命名管道
使用命名管道和使用普通文件一样,可以使用文件I/O操作来读取和写入数据。不同的是,命名管道的数据读取和写入是以先进先出(FIFO)的方式进行的,即写入的数据按照写入的顺序从管道中读取出来。
特点
- 命名管道是持久性的,创建后会一直存在于文件系统中,直到被显式删除。
- 允许不具有亲缘关系的进程之间进行通信。
- 数据按照写入的顺序从管道中读取出来,具有先进先出(FIFO)的特性。
示例
下面是一个简单的C语言示例,演示了如何创建和使用命名管道:
先介绍一下mkfifo:mkfifo 是用于创建命名管道(FIFO)的系统调用。在 Unix 和类 Unix 系统中,命名管道以文件的形式存在,可以用于不同进程之间进行通信。
mkfifo
函数原型
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数
pathname
:要创建的命名管道的路径。mode
:管道的权限,类似于open
或chmod
中使用的权限位。常用权限包括0666
(表示管道文件可读可写)。
返回值
- 成功时返回
0
。 - 失败时返回
-1
,并设置errno
以指示错误。
注意:
命名管道的读写操作是同步的,这意味着:
- 写入进程会等待直到有读取进程打开管道进行读取。
- 读取进程会等待直到有写入进程向管道写入数据。
这导致如果你先运行写入程序而没有相应的读取程序在运行,写入程序会阻塞,等待读取程序打开管道读取数据。同样地,如果你先运行读取程序而没有写入程序在运行,读取程序会阻塞,等待写入程序向管道写入数据。
为了避免这个问题,可以按以下步骤运行程序:
- 先运行读取程序
reader
,使其准备好从管道读取数据。 - 然后运行写入程序
writer
,向管道写入数据。
writer.c (写入程序)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_FILE "/tmp/my_fifo"
int main() {
// 创建命名管道,权限模式为 0600
if (mkfifo(FIFO_FILE, 0600) == -1) {
perror("mkfifo failed");
exit(EXIT_FAILURE);
}
// 打开命名管道以写入数据
int fd = open(FIFO_FILE, O_WRONLY);
if (fd == -1) {
perror("open failed");
exit(EXIT_FAILURE);
}
// 写入数据到命名管道
const char *message = "Hello, Named Pipe!";
write(fd, message, sizeof(message));
// 关闭文件描述符
close(fd);
return 0;
}
reader.c (读取程序)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_FILE "/tmp/my_fifo"
int main() {
char buffer[BUFSIZ];
// 打开命名管道以读取数据
int fd = open(FIFO_FILE, O_RDONLY);
if (fd == -1) {
perror("open failed");
exit(EXIT_FAILURE);
}
// 从命名管道读取数据
read(fd, buffer, sizeof(buffer));
printf("Received: %s\n", buffer);
// 关闭文件描述符
close(fd);
// 删除命名管道文件
unlink(FIFO_FILE);
return 0;
}
程序运行结果: