本文内容:
1.进程通信的目的
2.介绍Linux下进程间的4种通信方式:管道,消息队列,共享内存,信号量
ps:套接字也可以用于进程间的通信,不过是不同物理机器上的进程通信,本章讨论是是同一台物理机器上的通信,套接字本章暂不讨论
一.进程间通信的目的
1)数据的传输
2)数据的共享
3)事件的通知
4)资源的共享
5)进程的控制
二.进程间的通信方式
1.管道
概念:管道是一种两个进程间进行单向通信的机制,因为管道传递数据的单向性,管道又称之为半双工管道
分类:匿名管道和有名管道
特点:
1)数据只能由一个进程流向另一个进程,如果要进行双工通信,则需要建立两个管道(一个读管道,一个写管道)
2)匿名管道只能用于父子进程或者兄弟进程间的通信,有名管道则不受这个限制
3)管道缓冲区的大小是有限制的
4)管道传输的是无格式的字节流,需要传输双方事先约定传输格式
5)管道也是一种文件,但这个文件只存在于内存中
管道创建:
pipe()函数
int pipe(int fd[2])
1.管道两端分别用fd[0]和fd[1]描述
2.管道两端是固定了任务的,即一端只能用来读,一端只能用来写,规定fd[0]为读端,fd[1]为写端
样例程序:建立一个子进程读,父进程写的匿名管道
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
using namespace std; #define input 0
#define output 1 int main()
{
int fd[2];
pid_t pid; pipe(fd);
pid=fork(); if(pid<0)
{
cout<<"fork error"<<endl;
return 0;
}else if(pid==0)
{
cout<<"in child process"<<endl;
close(fd[input]);
char str[]="hello,my parents";
write(fd[output],str,strlen(str));
exit(0);
}else
{
cout<<"in parent process"<<endl;
close(fd[output]);
char str[256];
int num=read(fd[input],str,sizeof(str));
str[num]='\0';
cout<<num<<" bytes,"<<str<<endl;
exit(0);
}
return 0;
}
分析:要求子进程写,父进程读,那么关闭父进程的写端,打开父进程读端,关闭子进程的读端,关闭子进程写端,这样一条子进程写父进程读的匿名管道就建立好了,然后直接用read,write函数传递进行读写就好了
值得注意的是,匿名管道是存在于内核中的,而有名管道则是以FIFO的文件形式存在于文件系统中,这样即使是不具有亲缘关系的进程,只有可以访问文件系统,都可以进行通信
有名管道的特点:
1)可以使互不相关的两个进程实现彼此通信
2)该命名管道可以通过路径名来指出,并且在文件系统中是可见的,在建立了有名管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用方便
3)数据严格遵循FIFO先进先出原则
有名管道的创建:
int mkfifo(const char* pathname,mode_t mode)
第一个参数为路径名,也就是其创建后的名字
第二个参数为文件权限掩码,表示那些用户对此文件具有读写权限
样例程序:两个毫无关系的进程通过有名管道进行通信:
进程1:读端
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
using namespace std; #define P_FIFO "/tmp/p_fifo"
#define max_v 105 int main()
{
char str[max_v]; if(access(P_FIFO,F_OK)==0)//管道文件存在
{
execlp("rm","-f",P_FIFO,NULL);//删除
cout<<"the FIFO file have existed,delete it";
} if(mkfifo(P_FIFO,0777)<0)//创建命名管道,fifo文件
{
cout<<"create fifo file error"<<endl;
return 0;
} int fd=open(P_FIFO,O_RDONLY|O_NONBLOCK);//打开fifo文件 while(1)
{
memset(str,0,sizeof(str));//清空
int num=read(fd,str,max_v);//读取数据
if(num==0)
{
cout<<"no data"<<endl;
}else
{
str[num]='\0';
cout<<str<<endl;
}
sleep(1);//sleep 1s继续读
}
close(fd);//关闭fd
return 0;
}进程二:写端
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
using namespace std; #define P_FIFO "/tmp/p_fifo"
#define max_v 105 int main()
{ int fd=open(P_FIFO,O_WRONLY|O_NONBLOCK);//打开fifo文件
char str[]="give you data";
str[strlen(str)]='\n'; write(fd,str,strlen(str));//写入数据
sleep(1); close(fd);
return 0;
}分析:先运行读端程序,然后多次运行写端程序向命名管道中写入数据,写入的数据的读端的终端中会有显示
2.消息队列
定义:用于运行于同一台机器上的进程通信,和管道很相似,是在一个系统的内核中用来保存消息的队列,它在系统内核中是以消息链表的形式出现的
相关函数:
1)创建新消息队列或者取得已经存在的消息队列
int msgget(key_t key,int msgflag)
key:键值,一个公共的标识来标识一个通讯通道,当标识和消息队列绑定之后,则内核可以通过改标识找到对应的那个队列,key值可以由ftok函数生成
【函数ftok将一个已经存在的路径和一个整数标识转化为一个key键】
msgflag:
若msgflag=IPC_CREAT,若没有消息队列,则创建一个,若存在在返回原标识符
若msgflag=IPC_EXCL,若没有该队列,则返回-1.若已存在则返回0
2)向消息队列读取/写入消息
读取消息:
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,int msgflag)
写入消息
int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflag)
字段含义:
# msqid:消息队列标识符
# msgp:指向消息缓冲区的指针,用来暂时存储和发送消息,是一个用户可定义的通用结构
struct msgstru
{
long type;//大于0
char mtext[512];
};# msgsz:消息的大小
# msgtyp:消息的形态,等于0则标识消息队列中所有的消息都会被读取
# msgflag:
1.等于0表示阻塞式接收/发送消息,没有该类型的消息/消息队列满了,那么一直阻塞等待
2.等于IPC_NOWAIT,在发送消息时若果队列满了,则不阻塞,直接返回-1,在接收消息时,如果消息队列为空,则不阻塞等待,直接返回-1
3)设置消息队列属性
int msgctl(int msgqid,int cmd,struct msqid_ds *buf)
# msqid:消息队列标识符
# cmd=IPC_STAT:获取消息队列对应的msqid_ds(消息队列属性)数据结构,并且将其存放到buf指定的看见中
# cmd=IPC_SET:设置消息队列的属性,要设置的属性放在buf中
# cmd=IPC_RMID:删除消息队列
样例程序:使用消息队列来进行两个进程间的数据传输
接收消息端:
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/msg.h>
using namespace std; #define max_v 512
struct msg_st
{
long type;//>0
char mtext[max_v];
}; int main()
{
int msgid=-1;
struct msg_st data;
long int msgtype=0; //建立消息队列
msgid=msgget((key_t)1234,0666|IPC_CREAT);
if(msgid==-1)
{
cout<<"msgget function faild with error"<<endl;
exit(EXIT_FAILURE);
} //从队列中获取消息,直到遇到end
while(1)
{
if(msgrcv(msgid,(void*)&data,max_v,msgtype,0)==-1)
{
cout<<"msgrcv failed with error"<<endl;
exit(EXIT_FAILURE);
} //打印一下
cout<<"you get message:"<<data.mtext<<endl; if(strncmp(data.mtext,"end",3)==0)
{
return 0;
}
} //删除消息队列
if(msgctl(msgid,IPC_RMID,0)==-1)
{
cout<<"msgctl IPC_RMID faild error"<<endl;
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}写入消息端:
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/msg.h>
using namespace std; #define max_v 512
struct msg_st
{
long type;//>0
char mtext[max_v];
}; int main()
{
struct msg_st data;
char buff[max_v];
int msgid=-1; //建立消息队列,若已经存在则直接获取
msgid=msgget((key_t)1234,0666|IPC_CREAT);
if(msgid==-1)
{
cout<<"msgget faild with error";
exit(EXIT_FAILURE);
} //向消息队列中写入信息,知道写入end
while(1)
{
cout<<"enter some text:"<<endl; //输入信息
fgets(buff,max_v,stdin);
data.type=1;
strcpy(data.mtext,buff); //写入信息
if(msgsnd(msgid,(void*)&data,max_v,0)==-1)
{
cout<<"msgsnd faild with error"<<endl;
exit(EXIT_FAILURE);
} //end跳出
if(strncmp(buff,"end",3)==0)
{
break;
}
sleep(1);
}
exit(EXIT_SUCCESS);
}
和命名管道相比,消息队列的优势在于:
1)消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难
2)可以同时发通过发送消息来避免命名管道的同步和阻塞问题,而不需要进程自己提供同步方法
3)接收程序可以通过消息类型有选择的接收数据,而不是像命名管道那样只能默认接收
3.共享内存
定义:允许两个不相关的进程访问同一个逻辑内存
1.共享内存是两个正在运行的进程之间传递和共享数据的一种非常有效的方式
2.不同进程之间共享的内存通常安排在同一段物理内存中
3.进程可以将同一段共享内存链接到自己的地址空间中
4.共享内存没有提供同步机制!所以通过需要其他的机制来进行同步
1)创建共享内存:shmget函数
int shmget(key_t kry,int size,int flag)
# key:共享内存段的id
# size:共享内存的容量,单位字节
# flag:权限标志
成功则返回共享内存标识符,失败则返回-1
2)链接共享内存到自身的地址空间:shmat函数
void *shmat(int shmid,void *addr,int flag)
# shmid:共享存储标识符
# *addr和flag:决定以什么方式来确定链接的地址
成功则返回进程数据段所连接的实际地址
3)将共享内存从当前进程的地址空间分离:shmdt函数
int shmdt(const void *shmaddr);
# shmaddr:为shmat函数返回的地址指针
成功返回0,失败返回-1
样例程序:使用共享内存进行进程间通信
写入端:
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/msg.h>
#include<sys/shm.h>
using namespace std; #define max_v 2048
struct shared_use_st
{
int flag;//0代表可写,1代表可读
char text[max_v];
}; int main()
{
//创建共享内存
int shmid=shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);
if(shmid==-1)
{
cout<<"shmget failed with error"<<endl;
exit(EXIT_FAILURE);
} void *share_memory=(void*)0; //将共享内存链接到本进程的地址空间
share_memory=shmat(shmid,(void *)0,0);
if(share_memory==(void*)-1)
{
cout<<"shmat failed with error"<<endl;
exit(EXIT_FAILURE);
}
//打印共享内存在进程数据段的地址
cout<<"memory attached at "<<(long)share_memory<<endl; struct shared_use_st *shared_stuff=(struct shared_use_st *)share_memory;
shared_stuff->flag=0;
char buff[max_v]; while(1)
{
//当前共享内存处于不可读写状态,等待
if(shared_stuff->flag==1)
{
sleep(1);
cout<<"wait for client..."<<endl;
} //输入
cout<<"exter some text"<<endl;
fgets(buff,max_v,stdin);
strncpy(shared_stuff->text,buff,max_v);
shared_stuff->flag=1; //遇到end跳出
if(strncmp(buff,"end",3)==0)
{
break;
}
} //将共享内存从本进程地址空间分离
if(shmdt(share_memory)==-1)
{
cout<<"shmdt failed with error"<<endl;
exit(EXIT_FAILURE);
} exit(EXIT_SUCCESS);
}读取端:
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/msg.h>
#include<sys/shm.h>
using namespace std; #define max_v 2048
struct shared_use_st
{
int flag;//0代表可写,1代表可读
char text[max_v];
}; int main()
{
//创建共享内存
int shmid=shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);
if(shmid==-1)
{
cout<<"shmget failed with error"<<endl;
exit(EXIT_FAILURE);
} void *share_memory=(void*)0; //将共享内存链接到本进程的地址空间
share_memory=shmat(shmid,(void *)0,0);
if(share_memory==(void*)-1)
{
cout<<"shmat failed with error"<<endl;
exit(EXIT_FAILURE);
} //打印共享内存在进程数据段的地址
cout<<"memory attached at "<<(long)share_memory<<endl; struct shared_use_st *shared_stuff=(struct shared_use_st *)share_memory;
shared_stuff->flag=0; while(1)
{
//可写
if(shared_stuff->flag)
{
cout<<"you wrote :"<<shared_stuff->text<<endl;
sleep(1);
shared_stuff->flag=0; //遇到end跳出
if(strncmp(shared_stuff->text,"end",3)==0)
{
break;
}
}
} //将共享内存从本进程地址空间分离
if(shmdt(share_memory)==-1)
{
cout<<"shmdt failed with error"<<endl;
exit(EXIT_FAILURE);
} //删除共享内存
if(shmctl(shmid,IPC_RMID,0))
{
cout<<"shmctl IPC_RMID faild with error";
exit(EXIT_FAILURE);
} exit(EXIT_SUCCESS);
}
共享内存优劣分析:
优点:
1.方便,函数接口简单
2.数据不用进行传送,可以之间访问内存,加快了效率
3.对通信的进程没有要求,不像匿名管道一样,要求父子/兄弟进程才能进行通信
缺点:
1.没有提供同步机制!导致我们在通信时需要通过其他机制来实现同步
四.信号量
用于多线程的信号量是POSIX信号量,而用于进程的信号量是SYSTEM_V信号量
相关函数
1)创建和打开信号量:semget函数
int semget(key_t key,int nsems,int semflag)
# key:键值
# nsems:创建信号量的个数,一旦创建了该信号量,就不能改变信号量的个数,只有不删除该信号量,就可以重新调用该函数创建该键值的信号量
# semflag:指定该信号量的读写权限
成功则返回信号量的标识符,失败则返回-1
2)改变信号量的值:semop函数
int semop(int semid,struct sembuf *sops,unsigned nsops);
# sem_id:信号量标识符
# *sops:指向存储信号操作结构的数组指针
# nsops:信号操作结构的数量,>=1
共享内存是进程间最快的通信方式,但是共享内存的同步问题共享内存无法解决,可以采用信号量解决共享内存的同步问题
在进程访问临界资源之前,需要测试信号量,如果为正数,则信号量-1并且进程可以进入临界区,若为非正数,则进程挂起放入等待队列,直至有进程退出临界区,释放资源并+1信号量,此时唤醒等待队列的进程。
样例程序:信号量+共享内存,共享内存解决进程间的通信,而信号量解决共享内存的同步问题
读取端:
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/msg.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<sys/types.h>
#include<sys/ipc.h> using namespace std; #define sem_key 4001
#define shm_key 5678 union semnu
{
int val;
}; int main()
{ //创建共享内存
int shmid=shmget(shm_key,sizeof(int),IPC_CREAT|0666);
if(shmid<0)
{
cout<<"create shm error";
return -1;
} //将共享内存链接到当前进程的地址空间
void *shmptr=shmat(shmid,NULL,0);
if(shmptr==(void*)-1)
{
cout<<"shmat error:"<<strerror(errno)<<endl;
return -1;
} int *data=(int*)shmptr;
int semid=semget(sem_key,2,0666);//创建信号量 union semnu s;
struct sembuf sbuf;
struct sembuf sb[1] = {{0, 1, 0}}; while(1)
{
//sleep(1);
cin>>*data;//往共享内存写入数据
semop(semid, sb, 1);//信号量+1
}
return 0;
}读取端:
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/msg.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<sys/types.h>
#include<sys/ipc.h> using namespace std; #define sem_key 4001
#define shm_key 5678 union semnu
{
int val;
}; int main()
{
//创建共享内存
int shmid=shmget(shm_key,sizeof(int),IPC_CREAT|0666);
if(shmid<0)
{
cout<<"create shm error";
return -1;
} //将共享内存链接到当前进程的地址空间
void *shmptr=shmat(shmid,NULL,0);
if(shmptr==(void*)-1)
{
cout<<"shmat error:"<<strerror(errno)<<endl;
return -1;
} int *data=(int*)shmptr;
int semid=semget(sem_key,2,IPC_CREAT|0666);//创建信号量 union semnu s; struct sembuf sbuf;
struct sembuf sb[1] = {{0, -1, 0}}; while(1)
{
semop(semid, sb, 1);//信号量减1,当信号量<0则会阻塞等待写入端写入数据,>0则输出数据
cout<<"the NUM="<<*data<<endl;
}
return 0;
}
分析:信号负责共享内存的同步,而共享内存负责进程间的通信!将共享内存和信号量结合的方法非常好,因为共享内存是进程间通信最快的方式,而信号量又可以很好的解决共享内存的同步问题!
写入数据后,将信号量+1
将信号量-1,然后读取数据
因为信号量<0的话当前进程会阻塞
总结:进程间的四种通信方式总结完毕:管道(匿名管道和命名管道),消息队列,共享内存,信号量