1. 传统的通信方式
无名管道:父子进程,数据大小限制,半双工通信(不推荐)
有名管道:半双工通信
无名管道和有名管道是内核的一块缓冲区,内核对管道进行了同步与互斥
2. IPC通信
消息队列:全双工
共享内存:适用于大量数据共享
信号量
3. BSD
socket:适用本地IP(127.0.0.1),不同的进程用不同的端口号
数据传输:一个进程需要将它的数据发送给另一个进程;
资源共享:多个进程间共享同样的资源;
通知事件:一个进程需要向另一个或一组进程发消息,通知它们发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
1. 无名管道是一种文件,在内核空间中对应一段内存空间,内核以循环对列的方式存储(由操作系统管理和维护,自动释放)
2. 子进程复制父进程的描述符表(指向的是同一个管道,即同一片内存,所有是半双工),所以只能是父子进程,兄弟进程之间通信
3. fd[0]作为读端(从管道读数据),fd[1]作为写端,一端只读,一端只写,所有需要关闭不必要的描述符
4. 阻塞
无数据可读时读阻塞
管道被写满时写阻塞,直到数据被读走(无名管道的大小为64K)
5. 管道关闭
写端关闭时,读端读完管道中的数据,然后read结束
读端关闭时,写端写入失败,内核发出SIGPIPE信号
6. 打开管道的所有进程退出时管道释放
缺陷:
1. 半双工
2. 数据量大小限制
步骤:
1. 在创建子进程前创建无名管道
2. 线程使用读端则关闭写端,使用写端则关闭读端
3. 读端或写端处理完数据后关闭
//从父进程写入数据,子进程读取数据
#include
#include
#include
#include
int main()
{
int fd[2]; // fd[0]:读端,fd[1]:写端
// 管道需要创建在创建子进程前,这样才能复制,子进程和父进程都有一份独立的fd[2]数据
if(pipe(fd)<0) {
perror(“pipe errno”);
return -1;
}
int pid=-1;
pid=fork();//创建子进程,对于父进程会返回子进程id,子进程会返回0,创建失败会返回0
if(pid<0) {
perror("fork errno");
return -1;
}
else if(pid==0) {
//子进程 读取数据-> fd[0]
close(fd[1]);//fd[1]是向管道写入数据,子进程不用写入数据,需要关闭管道写入端
char buff[1024]={0};
read(fd[0],buff,1024);//如果管道没数据会等待,然后读取数据,默认阻塞等待直至有数据
printf("buff:%s\n",buff);
close(fd[0]);
} else {
//父进程 :写入数据->fd[1]
close(fd[0]); //fd[0]是读取数据,父进程不用读取数据,需要关闭管道读取端
write(fd[1],"happy day",10);
close(fd[1]);
}
return 0;
}
1. 命名管道可以应用于同一主机上任意进程间通信
2. 有名管道可以通过路径名来指出,并且在文件系统中可见
3. 打开方式:可读可写
4. 创建出管道后还需要用open打开。当以只读的方式打开管道,如果此时没有其它进程以写的方式打开管道,则阻塞在open,直到其它进程以写的方式打开。同理,以只写的方式打开也是阻塞。
步骤:
1. 在创建进程前创建有名管道(即创建出文件)
2. 打开管道(阻塞)
3. 读写数据
4. 关闭管道
#include
#include
#include
#include
#include
int main()
{
//用mkfifo创建管道(创建并没有打开)
umask(0); //将掩码设置为0
if(mkfifo("./test.fifo",0664)<0) // 管道文件的路径, 管道文件的权限
{
if(errnoEEXIST);//由于mkdir只能创建不存在命名管道,如果存在会报错,在这里如果存在继续走下面代码
else
{
perror(“mkfifo error”);
return -1;
}
}
//用open打开管道
int fd=open("./test.fifo",O_RDONLY);//用只读打开命名管道,open函数将阻塞等到直至有其他进程以写的方式打开这个命名管道,如果没有进程以写的方式打开这个命名管道,程序将停在此处
if(fd<0)
{
perror(“open error”);
return -1;
}
printf(“open fifo sucess and start read\n”);
//从管道中读取数据
while(1)
{
char buff[1024]={0};
int ret= read(fd,buff,1024); //管道是缓冲区,所以读取数据从缓冲区开始读;并且是单工通信(读或写一种)
if(ret>0)
printf("[%s]\n",buff);
// 管道特性:如果所有写端(echo 写完退出test.fifo,就是关闭了写端)关闭,那么读取时返回0
else if(ret0)
{
printf(“all write closed\n”);
sleep(3);
}
}
//关闭管道
close(fd);
return 0;
}
1. 消息队列是内核为创建的一个链式队列
2. 通过队列的标识符key,每一个进程都可以打开这个队列,每个进程都可以向这个队列中插入一个结点或者获取一个结点来完成不同进程间的通信。
3. 消息队列即满足的队列的先进先出的原则,又可以按照指定的消息类型去存取
4. 全双工通信
5. 如果没有释放队列则一直存在
6. 默认的情况下:整个系统最多允许有16个消息队列,每个消息对列最大为16384字节,消息对列中每个消息最大为8192字节。
#include
#include
步骤:
1. 根据文件的inode结点号和一个proj_id计算得出一个key值
key_t ftok(const char *pathname, int proj_id);
2. 创建消息队列
int msgget(key_t key, int msgflg);
3. 发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
4. 接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
5. 删除消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
ipc相关命令:
ipcs : 查看ipc信息
ipcs -q :查看消息队列
ipcs -m :查看共享内存
ipcs -s :查看信号量
ipcrm : 删除ipc
ipcrm -q msqid 删除指定的消息队列
ipcrm -m msqid 删除指定的共享内存
ipcrm -s msqid 删除指定的信号量
#include
#include
#include
#include
#include
#define IPC_KEY 0X12345678 // 消息队列标识符
#define TYPE_C 1 // 消息类型
#define TYPE_S 2
struct msgbuf {
long mtype; /* message type, must be > 0 /
char mtext[1024]; / message data */
};
int main()
{
int msqid=-1; // 消息队列句柄
//创建消息队列,不存在消息队列则创建,存在则打开
msqid=msgget(IPC_KEY,IPC_CREAT |0664);
if(msqid<0)
{
perror(“msgget error”);
return -1;
}
while(1)
{
//接受数据
struct msgbuf buff;
msgrcv(msqid,&buff,1024,TYPE_C,0);// 最多读取1024字节
printf("[%s]\n",buff.mtext); //将接受的数据打印
//发送数据
memset(&buff,0,sizeof(struct msgbuf));//先将数据初始化为0
buff.mtype=TYPE_S;
scanf("%s",buff.mtext);
msgsnd(msqid,&buff,1024,0);//并不是全部发送1024字节,会发生strlen(buff)
}
//删除消息队列
msgctl(msqid,IPC_RMID,NULL);
return 0;
}
共享内存直接申请一块物理内存通过页表映射到虚拟地址空间中,操作虚拟地址空间,其实是操作同一块物理内存区域,因此进行数据传输时相较于其他通信方式少了两步用户态与内核态数据拷贝的过程,因此共享内存是最快的进程间通信方式。
#include
#include
步骤:
1. 创建共享内存
int shmget(key_t key, size_t size, int shmflg);
2. 共享内存映射到进程各自的虚拟地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
3. 数据读写
4. 解除内存映射(如果有进程依然与共享内存保持映射连接关系,那么共享内存将不会被立即删除,而是等最后一个映射断开后删除,在这期间,将拒绝其他进程映射)
int shmdt(const void shmddr);
注意:需要互斥变量来限制多个进程对共享内存的读写
#include
#include
#include
#include
#include
#define SHM_KEY 0x12345678
int main()
{
int shmid=-1;
//创建共享内存
shmid=shmget(SHM_KEY,32,IPC_CREAT | 0664);
if(shmid<0)
if(shmid<0)
{
perror(“shmget errnor”);
return -1;
}
//建立映射关系
void *shm_start=shmat(shmid,NULL,0); // 0是读写 返回的是虚拟地址空间首地址 ,失败返回-1
if(shm_start<0)
{
perror("shmat error");
return -1;
}
//向内存写内容
while(1)
{
printf("please input:");
fflush(stdout);
memset(shm_start,0,32);
scanf("%s",(char *)shm_start);
sleep(3);
}
//解除映射
//int shmdt(const void shmddr);//由shmat返回的指针
shmdt(shm_start);
//删除(共享内存映射连接数为0)
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
#include
#include
1. 在创建子进程之前,创建信号量(一次创建多个)
int semget(key_t key, int nsems, int semflg);
2. 在创建子进程之前,设置信号量初值(一次初始化多个)
int semctl(int semid, int semnum, int cmd, ...);
3. 访问资源前先获取信号量 -1
int semop(int semid, struct sembuf *sops, unsigned nsops);
4. 数据
5. 访问资源后释放信号量+1