进程间的通信(IPC)方式,总归起来主要有如下这些:
1,无名管道(PIPE)和有名管道(FIFO)。
2,信号(signal)。
3,system V-IPC 之共享内存。
4,system V-IPC 之消息队列。
5,system V-IPC 之信号量。
6,套接字。
1、无名管道:
注意:
- pipe中的参数是一个具有两个整形数据的数组,用来存放文件描述符,一个是读端、另一个是写端
Pipe的特性:
- 没有名字,因此无法用open函数打开
- 只能用于亲缘进程(如:父子进程、兄弟进程、祖孙进程…)通信
- 半双工工作方式:不能同时读写
- 写入操作不具有原子性(写操作可以被打断),因此只能用于一对一的简单通信
- 不能使用sleek来定位读写的信息
无名管道例子:
#include "stdio.h"
#include <unistd.h>
int main()
{
int pipefd[2];
if(pipe(pipefd) < 0)
{
printf("pipe error\n");
return 0;
}
//创建pipe必须在父进程创建子进程之前
pid_t pid = fork();
if(pid > 0)
{
printf("父进程\n");
write(pipefd[1], "hello world", sizeof("hello world"));//往pipe[1]管道中写入数据
}
else if(pid == 0)
{
char buf[32] = {0};
read(pipefd[0], buf, sizeof(buf));//从pipe[0]管道中写入数据
printf("子进程\n");
printf("%s", buf);
}
else
{
printf("c创建失败\n");
}
}
练习:
尝试实现一下父子进程之间互相收发数据。
#include "stdio.h"
#include <unistd.h>
#include <stdlib.h>
#include "string.h"
char *buf = NULL;
int pipefd[2];
void father()
{
while(1)
{
printf("父进程请输入数据:\n");
fgets(buf, 1024, stdin);
int ret = write(pipefd[1], buf, strlen(buf));
printf("成功写入:%d字节 \n", ret );//数据写完之后最好等待一会,避免父子进程不同步
ret = read( pipefd[0] , buf , 1024 );
printf("父进程*成功读取: %d 字节 内容:%s \n" ,ret, buf );
}
}
void sun()
{
while(1)
{
int ret_val = read( pipefd[0] , buf , 1024 );
printf("子进程*成功读取:%d字节 内容:%s \n" , ret_val , buf );
printf("子进程*请输入:\n");
fgets(buf , 1024 , stdin );
ret_val = write( pipefd[1] , buf , strlen(buf));
printf("成功写入:%d字节 \n" , ret_val );
}
}
int main()
{
if(pipe(pipefd))
{
printf("error\n");
return -1;
}
buf = calloc(1, 1024);
//需要继承的东西都要在创建子进程之前
int pid = fork();
if(pid == 0)
{
sun();
}
else if(pid > 1)
{
father();
}
return 0;
}
注意:需要在写入之后等待一会,让另外一个进程读取,不然会造成不同步现象
2、有名管道
有名管道FIFO的特性:
- 有名字,存储于普通文件系统之中
- 任何具有相应权限的进程都可以使用open()来获取FIFO的文件描述符
- 跟普通文件一样,使用统一的read()\write()来读写
- 跟普通文件不同,不能使用lseek()来定位
- 具有写入原子性,支持多写入者同时进行写操作,而不会出现数据相互践踏
- First In First Out,最先被写进去的数据,最先被读取出来
读者:
- 持有管道文件读取权限的进程
写者:
- 持有管道文件写入权限的进程
练习:
尝试使用有名管道实现两个进程互相通信。
//fifo_write.c
#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
//创建管道文件,文件存在返回0, 不存在返回-1
if(access("/tmp/my_fifo", F_OK))
{
if(mkfifo("/tmp/my_fifo", 0666))//创建
{
perror("Mkfifo\n");
return -1;
}
}
printf("管道文件创建成功!! \n") ;
//打开文件
int fd = open("/tmp/my_fifo", O_WRONLY);
if(fd == -1)
{
printf("文件打开失败\n");
return -1;
}
printf("文件打开成功\n");
//写入信息
int ret = write(fd, "Hello Even", sizeof("Hello Even"));
printf("写入成功\n");
//关闭文件
close(fd);
}
//fifo_read.c
#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
//创建管道文件,文件存在返回0, 不存在返回-1
if(access("/tmp/my_fifo", F_OK))
{
if(mkfifo("/tmp/my_fifo", 0666))//创建
{
perror("Mkfifo\n");
return -1;
}
}
printf("管道文件创建成功!! \n") ;
//打开
int fd = open("/tmp/my_fifo", O_RDONLY);
if(fd == -1)
{
printf("文件打开失败\n");
return -1;
}
printf("文件打开成功\n");
//写入信息
char buf[32] = {0};
int ret = read(fd, buf, sizeof(buf));
printf("读取成功:%s\n", buf);
//关闭文件
close(fd);
}
注意:
- 管道文件在打开的时候,如果只有一方(读者\写者)则阻塞,需要等待对方到达后同时打开文件。这是因为在有名管道中,如果打开管道的一方没有对应的对端进程,则打开操作会阻塞。这是因为有名管道是一种同步的进程间通信机制。
作业:
- 使用有名管道实现类似系统中日志文件写入的操作,使用多个进程在不同的间隔时间(随机)
对同一个文件进行写入(进程号 + 时间 + 消息),检查是否能确保各个进程写入的数据不会被互相践踏。
客户端(client)采用父子进程同时对FIFO管道发送消息,服务端(server)从FIFO管道中接收数据并存入到日志文件中。
//client.c
#include "stdio.h"
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <time.h>
#define PATH_FIFO "/tmp/Logs"
int main()
{
//如果没有文件返回-1,有文件返回0
if(access(PATH_FIFO, F_OK))
{
if(mkfifo(PATH_FIFO, 0666) == -1)//创建文件失败返回-1
{
printf("创建失败\n");
return -1;
}
}
printf("创建成功\n");
int fd = open(PATH_FIFO, O_WRONLY);//可以让父子进程同用一个文件描述父,因为有名管道有同步机制
if(fd == -1)
{
printf("error\n");
return -1;
}
pid_t pid = fork();//创建子进程
if(pid == 0)//子进程
{
time_t timep;//获取时间
while(1)
{
char buf[64] = {0};
time(&timep);
sprintf(buf, "%d:son Time:%s", getpid(), ctime(&timep));
write(fd, buf, strlen(buf));
}
close(fd);
}
else if(pid > 0)//父进程
{
time_t time_p;//获取时间
while(1)
{
char buf[64] = {0};
time(&time_p);
sprintf(buf, "%d:father Time:%s", getpid(), ctime(&time_p));
write(fd, buf, strlen(buf));
}
close(fd);
}
else
{
printf("error\n");
return -1;
}
}
//server.c
#include "stdio.h"
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#define PATH_FIFO "/tmp/Logs"
#define PATH_LOGS "./Logs_fifo.txt"
int main()
{
if(access(PATH_FIFO, F_OK))
{
if(mkfifo(PATH_FIFO, 0666) == -1)//创建失败返回-1
{
printf("error\n");
return -1;
}
}
printf("创建成功\n");
int fd_fifo = open(PATH_FIFO, O_RDONLY);//打开管道文件
int fd_logs = open(PATH_LOGS, O_WRONLY);//打开日志文件
if(fd_fifo == -1 || fd_logs == -1)
{
printf("打开失败\n");
return -1;
}
printf("打开文件成功\n");
while(1)
{
char buf[64] = {0};
int ret = read(fd_fifo, buf, sizeof(buf));//读取管道文件中的数据
write(fd_logs, buf, ret);//读到的数据写入日志文件中
}
close(fd_fifo);
close(fd_logs);
}