进程中通信即IPC Inter Process Communication
进程中通信常见的几种方式 本文只讲管道、消息队列、共享内存
1、管道通信:无名管道、有名管道
2、信号--系统开销小
3、消息队列--内核的链表
4、信号量--计数器
5、共享内存
6、内存映射
7、套接字
无名管道:
内核缓冲区
伪文件-不占用磁盘空间
特点:
两部分:
读端,写端,对应两个文件描述符
数据写端流入,读端流出
操作管理的进程被销毁之后,管道自动被释放
管道默认是阻塞的
管道的原理
内部实现方式:队列
环形队列 内存中不是 环形队列假想的
特点:先进先出
缓冲区大小
默认4K
大小会根据实际情况做出适当调整
管道的局限性
队列:
数据只能读取一次,不能重复读取
单工:遥控器
半双工:对讲机
数据传输方向是单向的
双工:电话
匿名管道
适用于有血缘关系的进程
创建匿名管道
Int pipe(int fd[2])
fd-传出参数
fd[0]-读端
fd[1]-写端
返回值:
0:成功
-1:创建失败
#include
#include
#include
int main()
{
int fd[2];
int ret;
ret=pipe(fd);
if(-1==ret)
{
perror("pipe failed");
exit(1);
}
printf("read fd[0] is %d\n ",fd[0]);
printf("write fd[1] is %d\n",fd[1]);
close(fd[0]);
close(fd[1]);
return 0;
}
父子进程使用管道通信
实现ps aux |grep “bash”
数据重定向:dup2
#include
#include
#include
#include
int main()
{
int fd[2];
int ret;
ret=pipe(fd);
if(-1==ret)
{
perror("pipe failed");
exit(1);
}
pid_t pid=fork();
if(pid==-1)
{
perror("fork falied");
exit(1);
}
else if(pid==0)
{
close(fd[1]);
dup2(fd[0],STDIN_FILENO);
execlp("grep","grep","bash","--color=auto",NULL);
}
else if(pid>0)
{
close(fd[0]);
dup2(fd[1],STDOUT_FILENO);
execlp("ps","ps","aux",NULL);
perror("execlp");
exit(0);
}
printf("read fd[0] is %d\n ",fd[0]);
printf("write fd[1] is %d\n",fd[1]);
close(fd[0]);
close(fd[1]);
return 0;
}
查看管道缓冲区大小
命令
ulimit -a
fpathconf
#include
#include
#include
int main()
{
int fd[2];
int ret=pipe(fd);
if(ret==-1)
{
printf("pipe create failed\n");
exit(0);
}
long size=fpathconf(fd[0],_PC_PIPE_BUF);
printf("size is %ld\n",size);
printf("pipe[0] is %d\n",fd[0]);
printf("pipe[1] is %d\n",fd[1]);
close(fd[0]);
close(fd[1]);
return 0;
}
有名管道
函数形式:
int mkfifo(const char *filename,mode_t mode)
功能:创建管道文件
参数:管道文件文件名,权限,创建的文件权限仍然和umask有关系
返回值:创建成功返回0,创建失败返回-1
特点:
有名管道
在磁盘上有这样一个文件ls -l ->p
也是一个伪文件,在磁盘大小永久为0
数据存在内核中有一个对应的缓冲区
半双工通信方式
使用场景
没有血缘关系的进程间通信
使用mkfifo之后,只是在用户态创建了一个管道结点,内核中并没有管道,open之后才会到内核中建立一个管道,并且用户态的结点指向内核态的管道。相当于是一个缓冲区。
#include
#include
#include
#include
int main()
{
int ret;
ret=mkfifo("./myfifo",0766);
if(ret==-1)
{
printf("mkfifo create failed\n");
exit(1);
}
printf("mkfifo create success\n");
return 0;
}
有名管道:实现进程间通信
读端:
#include
#include
#include
#include
#include
#include
#include
int main()
{
int ret;
int fd;
char readBuff[128];
memset(readBuff,0,sizeof(readBuff));
ret=mkfifo("./myfifo",0766);
if(ret==-1)
{
printf("mkfifo create failed\n");
exit(1);
}
printf("mkfifo create success\n");
fd=open("./myfifo",O_RDONLY);
if(-1==fd)
{
perror("open failed");
exit(1);
}
ret=read(fd,readBuff,sizeof(readBuff));
printf("ret is %d readBuff is %s\n",ret,readBuff);
close(fd);
return 0;
}
写端:
#include
#include
#include
#include
#include
#include
#include
int main()
{
int fd;
int ret;
char *writeBuff="hello world";
fd=open("./myfifo",O_WRONLY);
if(-1==fd)
{
perror("open fifo failed");
return 1;
}
ret=write(fd,writeBuff,strlen(writeBuff));
printf("write is success %d ,%ld\n",ret,strlen(writeBuff));
close(fd);
return 0;
}
消息队列
消息队列,是消息的链表,存放在内核中,一个消息队列由一个标识符(队列ID)来标识
特点:
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级
消息队列独立于发送和接收进程,进程终止时,消息队列及其内容仍存在
消息队列可以实现消息的随机查询,消息不一定要先进先出的次序读取,也可以按消息的类型读取。
相关函数
Int msgget(key_t key,int msgfla);
创建或打开消息队列
参数:
Key:和消息队列关联的key值
Msgflg:是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。Msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而返回一个标识符。
返回值:成功返回队列ID,失败则返回-1。
#include
#include
#include
#include
#include
int main()
{
int msgid;
//msgid=msgget(IPC_PRIVATE,0755);
if(msgid==-1)
{
printf("create message queue failed\n");
return 1;
}
printf("create message queue success msgid=%d\n",msgid);
system("ipcs -q");
msgctl(0,IPC_RMID,NULL);
msgctl(1,IPC_RMID,NULL);
system("ipcs -q");
return 0;
}
Int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg)
//读取消息,成功返回消息数据的长度,失败返回-1;
参数:
Msgid:消息队列的ID
Msgp:指向消息的指针,常用结构体msgbuf如下:
Struct msgbuf
{
Long mtype;消息类型
Char mtext[N];消息正文
}
Size:发送的消息正文的字节数
Flag:
IPC_NOWAIT:消息没有发送完成函数也会立即返回
0:知道发送完成函数才返回
返回值:
成功:0
失败:-1
#include
#include
#include
#include
#include
#include
struct msgbuf
{
long mtype;
char mtest[128];
char ID[4];
};
int main()
{
struct msgbuf sendbuf;
int msgid;
msgid=msgget(IPC_PRIVATE,0755);
if(msgid==-1)
{
printf("create message queue failed\n");
return -1;
}
system("ipcs -q");
printf("create message queue success msgid = %d\n",msgid);
sendbuf.mtype=100;
printf("please input to message queue\n");
fgets(sendbuf.mtest,128,stdin);
msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
while(1);
return 0;
}
Size_t msgrcv(int msgid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
//从一个消息队列中获取消息
参数:
Msgid:消息队列的ID
Msgp:要接受消息的缓冲区
Size:要接受的消息的字节数
Msgtype:
0:接收消息队列中第一个消息
大于0:接收消息队列中第一个类型为msgtyp的消息
小于0:接收消息队列中类型值不大于msgtyp的绝对值且类型值又最小的消息
Flag:
0:若无消息函数一直阻塞
IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG
返回值:
成功:接收到的消息i长度
出错:-1
#include
#include
#include
#include
#include
#include
struct msgbuf
{
long mtype;
char mtest[128];
char ID[4];
};
int main()
{
struct msgbuf sendbuf,recvbuf;
int msgid,ret;
msgid=msgget(IPC_PRIVATE,0755);
if(msgid==-1)
{
printf("msgid fail\n");
exit(1);
}
printf("create msgid is %d\n",msgid);
sendbuf.mtype=100;
printf("please input\n");
fgets(sendbuf.mtest,128,stdin);
msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
ret=msgrcv(msgid,(void *)&recvbuf,128,100,0);
printf("recvbuf %d is %s\n ",ret,recvbuf.mtest);
return 0;
}
Int msgctl(int msqid,int cmd,strcut msqid_ds *buf);
//控制消息队列,成功返回0,失败返回-1
参数:
Msqid:消息队列的队列ID
Cmd:
IPC_STAT:把msgid_ds结构体中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值
IPC_SET:如果进程有足够的权限,就把消息队列的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID:删除消息队列
Buf:是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构
返回值:
成功:0
失败:-1
在以下两种情况下,msgget将创建一个新的消息队列:
如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志
Key参数为IPC——PRIVTE
函数msgrcv在读取消息队列时,type参数有下面几种情况
Type==0,返回队列中的第一消息
Type>0,返回队列中消息队列类型为type的第一个消息
Type<0,返回队列中消息类型值小于或等于type绝对值的消息,如果有多个,则取类型值最小的消息。
可以看出,type值非0时用于以非先进先出次序读取消息,也可以把type看成优先级的权值。
ftok函数
Key_t ftok(char * fname,int id)
//系统建立IPC通讯(如消息队列,共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
参数:
Fname就是你指定的文件名(该文件必须是存在而且可以访问的)
Id是子序号,虽然为int,但是只有8个比特被使用(0-255)
返回值:
当成功执行的时候,一个key_t值将会被返回,否则-1被返回
ipcs -q 查看消息队列
消息队列发送数据后,数据大小会增加,接收数据后,数据大小会减少。但是消息队列结点还是会存在。
两个进程之间通过消息队列实现一个进程写,另一个进程读。半双工通信
写
#include
#include
#include
#include
#include
#include
struct msgbuf
{
long mtype;
char mtest[128];
char ID[4];
};
int main()
{
struct msgbuf sendbuf;
key_t key;
int msgid;
key=ftok("a.c",1);
msgid=msgget(key,IPC_CREAT|0755);
if(msgid==-1)
{
printf("msgid fail\n");
exit(1);
}
printf("msgid is %d\n",msgid);
sendbuf.mtype=200;
printf("please input\n");
while(1)
{
memset(sendbuf.mtest,0,128);
fgets(sendbuf.mtest,128,stdin);
msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
}
return 0;
}
读
#include
#include
#include
#include
#include
#include
struct msgbuf
{
long mtype;
char mtest[128];
char ID[4];
};
int main()
{
struct msgbuf recvbuf;
key_t key;
int msgid,ret;
key=ftok("a.c",1);
msgid=msgget(key,IPC_CREAT|0755);
if(msgid==-1)
{
printf("msgid failed\n");
exit(1);
}
printf("msgid is %d\n",msgid);
recvbuf.mtype=200;
while(1)
{
memset(recvbuf.mtest,0,128);
ret=msgrcv(msgid,(void *)&recvbuf,128,200,0);
printf("recv is %s",recvbuf.mtest);
printf("total is %d\n",ret);
}
return 0;
}
进程之间消息队列全双工通信:
服务器端:
#include
#include
#include
#include
#include
#include
#include
struct msgbuf
{
long mtype;
char mtest[128];
char ID[4];
};
int main()
{
struct msgbuf sendbuf,recvbuf;
key_t key;
int msgid,ret;
pid_t pid;
key=ftok("a.c",1);
msgid=msgget(key,IPC_CREAT|0755);
if(msgid==-1)
{
printf("msgid fail\n");
exit(1);
}
printf("msgid is %d\n",msgid);
sendbuf.mtype=200;
recvbuf.mtype=300;
pid=fork();
if(pid==0)
{
while(1)
{
memset(recvbuf.mtest,0,128);
ret=msgrcv(msgid,(void *)&recvbuf,128,300,0);
printf("recvbuf is %s",recvbuf.mtest);
printf("total ret is %d\n",ret);
}
}
else if(pid>0)
{
printf("please input\n");
while(1)
{
memset(sendbuf.mtest,0,128);
fgets(sendbuf.mtest,128,stdin);
msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
}
}
return 0;
}
客服端:
#include
#include
#include
#include
#include
#include
#include
struct msgbuf
{
long mtype;
char mtest[128];
char ID[4];
};
int main()
{
struct msgbuf sendbuf,recvbuf;
key_t key;
int msgid,ret;
pid_t pid;
key=ftok("a.c",1);
msgid=msgget(key,IPC_CREAT|0755);
if(msgid==-1)
{
printf("msgid fail\n");
exit(1);
}
printf("msgid is %d\n",msgid);
sendbuf.mtype=300;
recvbuf.mtype=200;
pid=fork();
if(pid==0)
{
while(1)
{
memset(recvbuf.mtest,0,128);
ret=msgrcv(msgid,(void *)&recvbuf,128,200,0);
printf("recvbuf is %s",recvbuf.mtest);
printf("total ret is %d\n",ret);
}
}
else if(pid>0)
{
printf("please input\n");
while(1)
{
memset(sendbuf.mtest,0,128);
fgets(sendbuf.mtest,128,stdin);
msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
}
}
return 0;
}
共享内存
共享内存(Shared Memory)就是允许多个进程访问同一个内存空间,是在多个进程之间共享和传递数据最高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其它的进程读到的数据也将会改变。
Int shmget(key_t key,size_t size,int shmflg);
//用来获取或创建共享内存
参数:
Key:IPC_PRIVATE或ftok的返回值
Size:共享内存区大小
Shmflg:同open函数的权限位,也可以用8进制表示法
返回值:
成功:共享内存段标识符-----ID----文件描述符
出错:-1
#include
#include
#include
#include
int main()
{
int shmid;
shmid=shmget(IPC_PRIVATE,128,0777);
if(shmid==-1)
{
perror("shmid failed");
exit(1);
}
printf("create shmid is %d\n",shmid);
system("ipcs -m");
return 0;
}
void *shmat(int shm_id,const void *shm_addr,int shmflg);
//把共享内存连接映射到当前进程的地址空间 从内核态映射到用户态中
参数
Shm_id:ID号
Shm_addr:映射到的地址,NULL为系统自动完成的映射
Shmflg:
SHM_RDONLY共享内存只读
默认是0,表示共享内存可以读写
返回值:
成功:映射后的地址
失败:NULL
#include
#include
#include
#include
#include
int main()
{
int shmid;
key_t key;
char *q;
key=ftok("a.c",1);
if(key== -1)
{
perror("key fail");
exit(1);
}
printf("key is %d\n",key);
shmid=shmget(key,128,IPC_CREAT|0755);
if(shmid==-1)
{
perror("shmid fail");
exit(1);
}
printf("shmid is %d\n",shmid);
system("ipcs -m");
q=(char *)shmat(shmid,NULL,0);
if(q==NULL)
{
printf("shmat failed\n");
exit(1);
}
fgets(q,128,stdin);
printf("%s",q);
return 0;
}
Int shmdt(const void *shmaddr);
//将进程里的地址映射删除
参数:
Shmid:要操作的共享内存标识符
返回值:
成功:0
出错:-1
Int shmctl(int shm_id,int command,struct shmid_ds *buf)
//删除共享内存对象
参数:
Shmid:要操作的共享内存标识符
Cmd:
IPC_STAT(获取对象属性)---实现了命令ipcs -m
IPC_SET(设置对象属性)
IPC_RMID(删除对象)-----实现了命令ipcrm -m
Buf:指定IPC_STAT/IPC_SET时用以保存/设置属性
返回值:
成功:0
出错:-1
#include
#include
#include
#include
#include
#include
int main()
{
int shmid;
key_t key;
char *q;
key=ftok("a.c",1);
if(key== -1)
{
perror("key fail");
exit(1);
}
printf("key is %d\n",key);
shmid=shmget(key,128,IPC_CREAT|0755);
if(shmid==-1)
{
perror("shmid fail");
exit(1);
}
printf("shmid is %d\n",shmid);
system("ipcs -m");
q=(char *)shmat(shmid,NULL,0);
if(q==NULL)
{
printf("shmat failed\n");
exit(1);
}
fgets(q,128,stdin);
printf("%s",q);
shmdt(q);
//memcpy(q,"linux",5);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
出错:-1
特点:
共享内存创建后,一直存在于内核中,直到被删除或系统关闭。
共享内存和管道不一样,读取后,内容仍然在共享内存中。
Ipcrm -m +shmid 删除共享内存
Ipcs -m 查看共享内存
shmdt只能删除用户态地址映射,要想删除内核中的共享内存要用shmctl
实现ipcrm -m shmid
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
int shmid;
if(argc<4)
{
printf("argc fail\n");
exit(1);
}
if(strcmp("ipcrm",argv[1])==0)
{
if(strcmp("-m",argv[2])==0)
{
shmid=atoi(argv[3]);
printf("shmid is %d\n",shmid);
shmctl(shmid,IPC_RMID,NULL);
system("ipcs -m");
}
else
{
printf("argv[2] fail \n");
exit(1);
}
}
else
{
printf("argv[1] fail\n");
exit(1);
}
return 0;
}