进程间通信
进程间通信的目的
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程间通信的发展
管道 | system V IPC | POSIX IPC |
---|---|---|
匿名管道 | System V 消息队列 | 消息队列 |
命名管道 | System V 共享内存 | 共享内存 |
System V 信号量 | 信号量、互斥量、条件变量 | |
读写锁 |
管道
1) 相关概念
- 是什么? 从一个进程连接到另一个进程的一个数据流。
- 有多大? 从Linux2.6版本之后,其大小为65536bytes。
2) 匿名管道
特点
- 只能用于具有共同祖先的进程之间进行通信,通常是父子进程进行通信的一种方式,通过创建子进程实现通信。
- 管道提供流式服务,是半双工的,只能进行单项通信。
- 管道是基于文件实现的,文件随进程,管道的生命周期也随进程;进程退出,管道释放。
- 一般而言,内核会对管道提供同步与互斥。
读写规则
- 写端慢,写端若不关闭文件描述符且不写入,读端在一端时间内会阻塞。
- 读端慢,在实际写入时,若写入条件不满足,写端在一端时间内会阻塞。
- 如果写端关闭文件描述符,读端读到结尾时,会关闭读端的文件描述符,即,read会返回0。
- 如果读端关闭文件描述符,写端进程后续可能会被直接杀掉,变成僵尸进程:write操作会产生SIGPIPE信号,进而导致write进程退出。
实现从键盘读入数据写入管道(子进程),(父进程)读取数据写到屏幕的测试代码
int main(){
int fd[2] = {0}; //定义一个文件描述符数组:fd[0]--read,fd[1]--write
int pi = pipe(fd); //创建匿名管道
if(pi != 0){
printf("pipe error!\n");
exit(1);
}
pid_t id = fork(); //创建子进程
if(id<0){
printf("fork error!\n");
exit(2);
}
else if(id==0){ //child:write
close(fd[0]); //关闭读端
char* msg;
while(1){
//从键盘读入数据,写入管道
fgets(msg, sizeof(msg), stdin);
write(fd[1], msg, strlen(msg));
sleep(1);
}
}
else{ //father:read
close(fd[1]); //关闭写端
int count = 0;
char buf[64];
while(1){
ssize_t s = read(fd[0], buf ,sizeof(buf)-1); //s是读取的长度
if(s > 0){
buf[s] = 0;
printf("father get message: %s\n", buf);
}
}
return 0;
}
}
3) 命名管道
特点
可以用于不相关进程之间的通信
创建方式
- 使用命令行:mkfifo pipe(创建了pipe命名管道)
- 从程序中创建(调用mkfifo函数):mkfifo(“pipe”, 0644);
使用方式
通过open函数打开管道进行使用
使用命名管道,实现文件拷贝的测试代码
//使用命名管道,对文件进行拷贝
//test_1.c文件中:
int main(int argc, char *argv[]){
mkfifo("pipe", 0644); //建立命名管道
int fd = open("pipe", O_WRONLY);
int infd = open("abc.txt", O_RDONLY);
char buf[64];
int n;
while ((n=read(infd, buf, 64))>0){
write(fd, buf, n); //从infd中读入读入数据到管道pipe
}
close(infd);
close(fd);
return 0;
}
//test_2.c文件中:
int main(){
int infd;
infd = open("pipe", O_RDONLY); //打开同一个命名管道
int outfd = open("def.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
char buf[64];
int n;
while ((n=read(infd, buf, 64))>0){
write(outfd, buf, n); //从管道pipe中读出数据写入到outfd文件中,实现文件内容从infd到outfd之间的拷贝
}
close(infd);
close(outfd);
return 0;
}
利用命名管道,实现简单的Server&Client之间的通信
- Server端读取Client端发送的信息
int main(){
mkfifo("./myfifo", 0644); //创建myfifo命名管道,并以只读的方式打开
int fd = open("./myfifo", O_RDONLY);
if(fd < 0){
printf("open error...\n");
exit(1);
}
char buf[64];
while(1){
ssize_t s = read(fd, buf, sizeof(buf)-1);
if(s > 0){
buf[s] = 0;
printf("Client say# %s", buf);
}
else if(s == 0){
printf("Client stopping...\n");
}
else{
printf("read error...\n");
break; //当前一次读取失败
}
}
close(fd);
return 0;
}
- Client端发送信息给Server端
int main(){
int fd = open("./myfifo", O_WRONLY);
if(fd < 0){
printf("open error...\n");
exit(1);
}
char msg[64];
while(1){
printf("Please Enter Message # ");
fflush(stdout); //使用printf,要注意刷新标准输出
ssize_t s = read(0, msg, sizeof(msg)-1); //从键盘读数据
if(s > 0){
msg[s] = 0;
write(fd, msg, strlen(msg));
}
else if(s == 0){
}
else{
printf("write error...\n");
break;
}
}
close(fd);
return 0;
}