IPC 进程间通信
IPC(Interprocess Communication,进程间通信)是指在不同进程之间传播或交换信息的一种机制。它允许多个进程在同一操作系统中协同工作,实现数据的共享和同步。IPC主要包括三大类通信方式:古老的通信方式、IPC对象通信以及socket通信。
1. 古老的通信方式
- 无名管道(PIPE):
- 是UNIX系统IPC最古老的形式,半双工,即数据只能在一个方向上流动。
- 它具有固定的读端和写端,通常用于父子进程或兄弟进程之间的通信。
- 可以看成是一种特殊的文件,对其读写使用普通的read、write函数,但它只存在于内存中,不属于任何文件系统。
- 有名管道(Named PIPE):
- 也称为FIFO(First In First Out),它克服了无名管道只能在亲缘关系进程间通信的限制。
- 有名管道以文件形式存在于文件系统中,有路径名与之关联,可以在无关的进程之间交换数据。
- 信号(Signal):
- 是Unix系统中使用的最古老的进程间通信方法之一。
- 它是异步的,用于通知进程发生了某种事件。
- 操作系统通过信号来通知进程系统中发生了某种预先规定好的事件,也是用户进程之间通信和同步的一种原始机制。
2. IPC对象通信
- 消息队列(Message Queue):
- 消息队列提供了一种从一个进程向另一个进程发送数据块的方法。
- 每个数据块都有一个类型,接收者进程可以根据类型选择性地接收数据。
- 消息队列存在于内核中,由消息队列标识符来标识。
- 共享内存(Shared Memory):
- 允许两个或多个不相关的进程访问同一块内存区域。
- 它是所有IPC方式中速度最快的一种,因为数据不需要在客户机和服务器之间复制。
- 通常需要使用某种形式的同步机制(如信号量)来避免竞态条件。
- 信号量集(Semaphore Set):
- 主要用于控制多个进程对共享资源的访问。
- 它是一种计数器,用于协调多个进程之间的同步。
- 信号量可以被设置为正数或零,表示可用资源的数量。
3. Socket通信
- 网络通信:
- Socket是一种特殊的文件描述符,用于表示两个进程间的网络连接(包括TCP和UDP协议)。
- 它不仅可以在同一台机器上的不同进程间通信,还可以跨网络进行通信。
- Socket通信包括创建socket、绑定地址和端口、监听连接、接受连接、数据收发以及关闭连接等步骤。
管道通信
管道的特性:
1、管道是 半双工的工作模式(但我们当作单工来使用)
2、所有的管道都是特殊的文件不支持定位操作。lseek->> fd fseek ->>FILE*
3、管道是特殊文件,读写使用文件IO。fgets,fread,fgetc,
open,read,write,close;
1,读端存在,一直向管道中去写,超过64k,写会阻塞。
2,写端是存在的,读管道,如果管道为空的话,读会阻塞。
3.管道破裂:读端关闭,还继续写管道。
4. read 0 :写端关闭,如果管道没有内容,读到0即:read 0 ;代表通信结束
使用框架:
创建管道 ==》读写管道 ==》关闭管道
无名管道
1、无名管道 ===》管道的特例 ===>pipe函数
特性:
1.1 亲缘关系进程使用
1.2 有固定的读写端
流程:
创建并打开管道: pipe函数
#include <unistd.h> 头文件
int pipe(int pipefd[2]);
功能:创建并打开一个无名管道
参数:pipefd[0] ==>无名管道的固定读端
pipefd[1] ==>无名管道的固定写端
返回值:成功 0
失败 -1;
注意事项:
1、无名管道的架设应该在fork之前进行。
无名管道的读写:===》文件IO的读写方式。
读: read()
写: write()
关闭管道: close();pipe
函数的参数是一个整型数组(通常是两个元素的数组),用于存储由系统分配的两个文件描述符。如果调用成功,pipe
函数会将这两个文件描述符存储在提供的数组中,并将数组的第一个元素(pipefd[0]
)设置为管道的读端,将数组的第二个元素(pipefd[1]
)设置为管道的写端。然后,pipe
函数返回0以表示成功。如果调用失败,则返回-1,并设置全局变量errno
以指示错误原因。
验证: read 0 :写端关闭,如果管道没有内容,读到0即:read 0 ;代表通信结束
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int pipefd[2]={0};
int ret = pipe(pipefd);
if(-1 == ret)
{
perror("pipe");
return 1;
}
pid_t pid = fork();
if(pid>0)
{
//需要决定读写方向
close(pipefd[0]);
sleep(3);
char buf[50]="hello,child";
write(pipefd[1],buf,strlen(buf));
}
else if(0 == pid)
{
close(pipefd[1]);
while(1)
{
char buf[50]={0};
int rd_ret = read(pipefd[0],buf,sizeof(buf)); //后台进程
if(rd_ret<=0)
{
break;
}
printf("father :%s\n",buf);
}
printf("ipc end\n");
}
else
{
perror("fork");
return 1;
}
return 0;
}
这段代码中只创建了一个管道。管道是通过pipe()
函数创建的,它返回一个包含两个文件描述符的数组:pipefd[0]
和pipefd[1]
。这两个文件描述符分别代表管道的读端和写端。
pipefd[0]
:管道的读端,用于读取数据。pipefd[1]
:管道的写端,用于向管道写入数据。
在代码中,fork()
调用创建了一个新的子进程。子进程和父进程都会继承这个管道的文件描述符。但是,根据fork()
的返回值,父进程和子进程分别关闭了它们不需要的管道端:
- 父进程(
pid > 0
):关闭了读端pipefd[0]
,保留了写端pipefd[1]
,用于向管道写入数据。 - 子进程(
pid == 0
):关闭了写端pipefd[1]
,保留了读端pipefd[0]
,用于从管道读取数据。
父进程在等待3秒后向管道写入字符串"hello,child",然后子进程不断地从管道读取数据,直到没有更多的数据可读。
因此,虽然父进程和子进程都有管道的文件描述符,但它们操作的是同一个管道的两端。整个程序中只存在一个管道,而不是两个独立的管道。
验证如下问题:
1、父子进程是否都有fd[0] fd[1],
如果在单一进程中写fd[1]能否直接从fd[0]中读到。
可以,写fd[1]可以从fd[0]读
2、管道的数据存储方式是什么样的
数据是否一直保留?
栈, 先进后出
队列形式存储 读数据会剪切取走数据不会保留
先进先出
3、管道的数据容量是多少,有没有上限值。
操作系统的建议值: 512* 8 = 4k
代码测试实际值: 65536byte= 64k
4、管道的同步效果如何验证?读写同步验证。
读端关闭能不能写? 不可以 ===>SIGPIPE 异常终止
写端关闭能不能读? 可以,取决于pipe有没有内容,===>read返回值为0 不阻塞
结论:读写端必须同时存在,才能进行
管道的读写。
5、固定的读写端是否就不能互换?
能否写fd[0] 能否读fd[1]? 不可以,是固定读写端。
练习:在父子进程间传输文件内容。父进程读取/home/linux/1.png
文件的内容,并通过管道发送给子进程。子进程则接收这些内容,并将其写入到2.png
文件中。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int pipefd[2]={0};
int ret = pipe(pipefd);
if(-1 == ret)
{
perror("pipe");
return 1;
}
pid_t pid = fork();
if(pid>0)
{
close(pipefd[0]);
int fd = open("/home/linux/1.png",O_RDONLY);
if(-1 == fd)
{
perror("father open");
exit(1);
}
while(1)
{
char buf[4096]={0};
int rd_ret = read(fd,buf,sizeof(buf));
if(rd_ret<=0)
{
break;
}
write(pipefd[1],buf,rd_ret);
}
close(fd);
close(pipefd[1]);
}
else if(0 == pid)
{
close(pipefd[1]);
int fd = open("2.png",O_WRONLY|O_TRUNC|O_CREAT,0666);
if(-1 == fd)
{
perror("child open");
exit(1);
}
while(1)
{
char buf[4096]={0};
int rd_ret = read(pipefd[0],buf,sizeof(buf));
if(rd_ret<=0)
{
break;
}
write(fd,buf,rd_ret);
}
close(fd);
close(pipefd[0]);
}
else
{
perror("fork");
return 1;
}
return 0;
}
有名管道
有名管道===》fifo ==》有文件名称的管道。
文件系统中可见
框架:
创建有名管道 ==》打开有名管道 ==》读写管道
==》关闭管道 ==》卸载有名管道
1、创建:mkfifo
#include <sys/types.h>
#include <sys/stat.h>
remove();
int mkfifo(const char *pathname, mode_t mode);
功能:在指定的pathname路径+名称下创建一个权限为
mode的有名管道文件。
参数:pathname要创建的有名管道路径+名称
mode 8进制文件权限。
返回值:成功 0
失败 -1;
2、打开有名管道 open
注意:该函数使用的时候要注意打开方式,
因为管道是半双工模式,所有打开方式直接决定
当前进程的读写方式。
一般只有如下方式:
int fd-read = open("./fifo",O_RDONLY); ==>fd 是固定读端
int fd-write = open("./fifo",O_WRONLY); ==>fd 是固定写端
不能是 O_RDWR 方式打开文件。
不能有 O_CREAT 选项,因为创建管道有指定的mkfifo函数
3、管道的读写: 文件IO
读: read(fd-read,buff,sizeof(buff));
写: write(fd-write,buff,sizeof(buff));
4、关闭管道:
close(fd);
5、卸载管道:remove();
int unlink(const char *pathname);
功能:将指定的pathname管道文件卸载,同时
从文件系统中删除。
参数: ptahtname 要卸载的有名管道
返回值:成功 0
失败 -1;
练习:1.实现fifo a,b 进程 实时聊天。可以连续收发。#quit,双方都退出。
写端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
void* th1(void* arg)
{
int fd =*(int*)arg;
while(1)
{
printf("to B:");
char buf[128]={0};
fgets(buf,sizeof(buf),stdin);//#quit
if(0 == strcmp(buf,"#quit\n"))
{
exit(0);
}
write(fd,buf,strlen(buf));
}
return NULL;
}
void* th2(void* arg)
{
int fd =*(int*)arg;
while(1)
{
char buf[128]={0};
int ret = read(fd,buf,sizeof(buf));
if(0==strcmp(buf,"#quit\n") || ret<=0)
{
exit(0);
}
printf("from B:%s",buf);
fflush(stdout);
}
return NULL;
}
int main(int argc, char *argv[])
{
int ret = mkfifo("myfifo1",0666);
if(-1 == ret)
{
if( EEXIST!= errno )
{
perror("mkfifo");
return 1;
}
}
ret = mkfifo("myfifo2",0666);
if(-1 == ret)
{
if( EEXIST!= errno )
{
perror("mkfifo");
return 1;
}
}
int fd_w = open("myfifo1",O_WRONLY);
if(-1 == fd_w)
{
perror("open");
return 1;
}
int fd_r = open("myfifo2",O_RDONLY);
if(-1 == fd_r)
{
perror("open");
return 1;
}
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,th1,&fd_w);
pthread_create(&tid2,NULL,th2,&fd_r);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}
读端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
void* th1(void* arg)
{
int fd =*(int*)arg;
while(1)
{
printf("to A:");
char buf[128]={0};
fgets(buf,sizeof(buf),stdin);//#quit
if(0 == strcmp(buf,"#quit\n"))
{
exit(0);
}
write(fd,buf,strlen(buf));
}
return NULL;
}
void* th2(void* arg)
{
int fd =*(int*)arg;
while(1)
{
char buf[128]={0};
int ret = read(fd,buf,sizeof(buf));
if(0==strcmp(buf,"#quit\n") || ret<=0)
{
exit(0);
}
printf("from A:%s",buf);
fflush(stdout);
}
return NULL;
}
int main(int argc, char *argv[])
{
int ret = mkfifo("myfifo1",0666);
if(-1 == ret)
{
if( EEXIST!= errno )
{
perror("mkfifo");
return 1;
}
}
ret = mkfifo("myfifo2",0666);
if(-1 == ret)
{
if( EEXIST!= errno )
{
perror("mkfifo");
return 1;
}
}
int fd_r = open("myfifo1",O_RDONLY);
if(-1 == fd_r)
{
perror("open");
return 1;
}
int fd_w = open("myfifo2",O_WRONLY);
if(-1 == fd_w)
{
perror("open");
return 1;
}
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,th1,&fd_w);
pthread_create(&tid2,NULL,th2,&fd_r);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}
多个有名管道,开的顺序要一致;
练习2:实现pipe实现查字典。 1.可以循环查找 2.找到显示意思,没找到显示没找到 3.#quit 都退出。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define MAX 19662
int main(int argc, char *argv[])
{
int pipefd[2]={0};
int ret = pipe(pipefd);
if(-1 == ret)
{
perror("pipe");
return 1;
}
pid_t pid = fork();
if(pid>0)
{
close(pipefd[0]);
int fd = open("/home/linux/dict.txt",O_RDONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
while(1)
{
char buf[4096]={0};
int rd_ret = read(fd,buf,sizeof(buf));
if(rd_ret==0)
{
lseek(fd,0,SEEK_SET);
continue;
}
write(pipefd[1],buf,rd_ret);
}
}
else if(0 == pid)
{
close(pipefd[1]);
FILE*fp = fdopen(pipefd[0],"r");
while(1)
{
char want_word[50]={0};
printf("want word:");
fgets(want_word,sizeof(want_word),stdin);
want_word[strlen(want_word)-1]='\0';
if(0 == strcmp(want_word,"#quit"))
{
break;
}
int num = 0;
while(1)
{
char linebuf[512]={0};
fgets(linebuf,sizeof(linebuf),fp);
char* arg[2]={NULL};
arg[0]=strtok(linebuf," ");
arg[1]=strtok(NULL,"\r");
if(0 == strcmp(want_word,arg[0]))
{
printf("%s:%s\n",arg[0],arg[1]);
break;
}
num++;
if(num>=MAX)
{
printf("cant find %s\n",want_word);
break;
}
}
}
}
else
{
perror("fork");
return 1;
}
return 0;
}
代码中用管道破裂使父进程结束
信号通信
进程间通信 ===》信号通信
应用:异步通信。 中断,,
1~64;32应用编程。
如何响应:
Term Default action is to terminate the process.
Ign Default action is to ignore the signal.
wait
Core Default action is to terminate the process and dump core (see
core(5)).
gdb a.out -c core
Stop Default action is to stop the process.
Cont Default action is to continue the process if it is currently
stopped.
kill -xx xxxx
发送进程 信号 接收进程
kill -9 1000
a.out 9 1000
1、发送端
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:通过该函数可以给pid进程发送信号为sig的系统信号。
参数:pid 要接收信号的进程pid
sig 当前程序要发送的信号编号 《=== kill -l
返回值:成功 0
失败 -1;
练习:
编写一个自己的kill程序,尽量模拟kill命令的效果。
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
char buf[10]={0};
fgets(buf,sizeof(buf),stdin);
pid_t pid = atoi(buf);
int ret= kill(pid,9);
if(-1 == ret)
{
perror("kill");
return 1;
}
return 0;
}
int raise(int sig)== kill(getpid(),int sig);
功能:给进程自己发送sig信号
unsigned int alarm(unsigned int seconds);SIGALAM
功能:定时由系统给当前进程发送信号
也称为闹钟函数
闹钟只有一个,定时只有一次有效,
但是必须根据代码逻辑是否执行判断。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
alarm(5);
while(1)
{
printf("i'm sleep\n");
sleep(1);
}
return 0;
}
alarm
设置的定时器会在5秒后中断程序,并且sleep
调用会被终止。- 程序中的
while(1)
循环是无止境的,但是实际上由于alarm
的作用,循环会在5秒后被中断。 - 程序没有处理
SIGALRM
信号,所以当定时器到期时,程序会终止。
int pause(void);
功能:进程暂停,不再继续执行,除非
收到其他信号。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int i = 0 ;
while(1)
{
printf("i'm working\n");
sleep(1);
i++;
if(i== 5)
{
pause();
}
}
return 0;
}