IPC
1.什么是IPC?Inter Process Communication
2.进程间通信常用的几种方式
1,管道通信:有名管道,无名管道
2,信号- 系统开销小
3,消息队列-内核的链表
4,信号量-计数器
5,共享内存
6,内存映射
7,套接字
无名管道
管道的概念

1.本质
内核缓冲区
伪文件-不占用磁盘空间
2特点:
两部分: 读端,写端,对应两个文件描述符
数据写端流入,读端流出
操作管理的进程被销毁之后,管道自动被释放
管道默认是阻塞的
管道的原理
1.内部实现方式:
队列-环形队列
特点:先进先出
2.缓冲区大小
默认4K,大小会根据实际情况做适当调整
管道的局限性
1.队列: 数据只能读取一次,不能重复读取
2.半双工:
单工:遥控器
半双工:对讲机
创建匿名管道
int pipe(int fd[2])
fd‐传出参数:
fd[0]‐读端
fd[1]‐写端
返回值:
0:成功
‐1:创建失败
父子进程使用管道通信
实现 ps aux | grep "bash"
数据重定向:dup2
cs
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int ret;
int fd[2];
ret = pipe(fd);
if(ret == -1)
{
printf("pipe creat failed\n");
exit(1);
}
printf("pipe creat success!\n");
printf("fd[0] is %d\n",fd[0]);
printf("fd[1] is %d\n",fd[1]);
close(fd[0]);
close(fd[1]);
return 0;
}
~

注012是标准输入输出和报错
int dup2(int oldfd, int newfd);//新段指向旧端
cs
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int ret;
int fd[2];
ret = pipe(fd);
if(ret == -1)
{
printf("pipe creat failed\n");
exit(1);
}
printf("pipe creat success!\n");
pid = fork();
if(pid == -1)
{
printf("fork failed\n");
exit(1);
}
if(pid > 0)
{
close(fd[0]);
dup2(fd[1],STDOUT_FILENO);
execlp("ps","ps","aux",NULL);
perror("excelp");
exit(1);
}
else if (pid == 0)
{
close(fd[1]);
dup2(fd[0],STDIN_FILENO);
execlp("grep","grep","bash","--color=auto",NULL);
}
return 0;
}

单个进程也可以使用管道
父子进程在使用管道的时候,父进程写的时候要关闭读,子进程读的时候要关闭写
管道的读写行为
1.读操作
1)有数据:read(fd[0]) 正常读,返回读出的字节数
2)无数据:
写端被全部关闭,read返回0,相当于读文件到了尾部
没有全部关闭,read阻塞
2.写操作
1)读端全部关闭:
管道破裂,进程被终止
内核给当前进程发送信号SIGPIPE-13,默认处理动作
2)读端没全部关闭:
缓冲区写满了,write阻塞
缓冲区没满,write继续写,直到写满,阻塞
3.如何设置非阻塞?
1)默认读写两端都阻塞
2)设置读端为非阻塞pipe(fd):
fcntl-变参函数:复制文件描述符-dup;修改文件属性-open的时候对应flag属性
设置方法
//获取原来的flags
int flags = fcntl(fd[0],F+GETFL);
//设置新的flags
flag |=O_NONBLOCK;
fcntl(fd[0],F_SETFL,flags);
fcntl(fd[0],F_SETFL,flags);
查看管道缓冲区大小
命令:
ulimit -a
fpathconf
long fpathconf(int fd, int name);

cs
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int ret;
int fd[2];
ret = pipe(fd);
if(ret == -1)
{
printf("pipe creat failed\n");
exit(1);
}
printf("pipe creat success!\n");
long size = fpathconf(fd[0],_PC_PIPE_BUF);
printf("size id %ld\n",size);
printf("fd[0] is %d\n",fd[0]);
printf("fd[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 管道名
函数:mkfifo()
int mkfifo(const char *pathname, mode_t mode);
fifo文件可以使用io函数进程操作
open/close,read/write
不能执行lseek操作
读函数
cs
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int ret;
int fd;
int nread;
char readBuff[50] = {0};
ret = mkfifo("/home/u/process/myfifo",0777);
if(ret == -1)
{
return -1;
}
printf("creat file success!\n");
fd = open("./myfifo",O_RDONLY);
if(fd < 0)
{
return -1;
}
printf("open file success!\n");
nread = read(fd,readBuff,50);
printf("read %d byte from fifo :%s\n",nread,readBuff);
close(fd);
return 0;
}
写程序
cs
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd;
char *str = "hello world!";
fd = open("./myfifo",O_WRONLY);
if(fd < 0)
{
return -1;
}
printf("open file success!\n");
write(fd,str,strlen(str));
close(fd);
return 0;
}
~

消息队列
消息队列,是消息的链表,存放在内核中,一个消息队列由一个标识符(队列ID)来标识。

特点
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级
消息队列独立于发送和接收进程,进程终止时,消息队列及其内容仍存在
消息队列可以实现消息的随机查询,消息不一定要先进先出的次序读取,也可以按消息的类型读取。
相关函数
int msgget(key_t key, int msgflg);
//创建或打开消息队列,
参数:
key:和消息队列关联的key值
msgflg:是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。
msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。
返回值:
成功返回队列ID,失败则返回‐1,
在以下两种情况下,msgget将创建一个新的消息队列:
如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志
key参数为IPC_PRIVATE
int msgsnd(int msgid, 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
ssize_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
函数msgrcv在读取消息队列时,type参数有下面几种情况
type ==0,返回队列中的第一消息
type >0,返回队列中消息队列类型为type的第一个消息
type <0,返回队列中消息类型值小于或等于type绝对值的消息,如果有多个,则取类型值最小的消息。
可以看出,type值非0时用于以非先进先出次序读取消息,也可以把type看成优先级的权值
msgctl(int msqid, int cmd, struct msqid_ds *buf);//控制消息队列,成功返回0,失败返回‐1 参数:
msgid:消息队列的队列ID
cmd:
IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID:删除消息队列
buf:是指向 msgid_ds 结构的指针,它指向消息队列模式和访问权限的结构
返回值:
成功:0
失败:‐1
ftok函数key_t ftok( char * fname, int id )
//系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
参数:
fname就是你指定的文件名(该文件必须是存在而且可以访问的)。
id是子序号, 虽然为int,但是只有8个比特被使用(0‐255)。
返回值:
当成功执行的时候,一个key_t值将会被返回,否则 ‐1 被返回。
消息队列建立和删除
cs
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
int main()
{
int msgid;
msgid = msgget(IPC_PRIVATE,0755);
if(msgid == -1)
{
printf("creat message queue failed\n");
return -1;
}
printf("creat message queue success,msgid = %d\n",msgid);
system("ipcs -q");
msgctl(msgid,IPC_RMID,NULL);
system("ipcs -q");
return 0;
}

单进程使用消息队列
cs
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mtext[128];
};
int main()
{
int msgid;
struct msgbuf sendbuf,readbuf;
int readret;
msgid = msgget(IPC_PRIVATE,0755);
if(msgid == -1)
{
printf("creat message queue failed\n");
return -1;
}
system("ipcs -q");
printf("creat message queue success,msgid = %d\n",msgid);
//init msgbuf
sendbuf.mtype = 100;
printf("plese input message:\n");
fgets(sendbuf.mtext,128,stdin);
//send message to message queue
msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtext),0);
//read message
memset(readbuf.mtext,0,128);
readret = msgrcv(msgid,(void *)&readbuf,128,100,0);
printf("receive mesagge is :%s\n",readbuf.mtext);
printf("total message is %d\n",readret);
return 0;
}

读出内容的时候,节点中内容被删除,但是节点仍然存在
消息队列进程间通信
写进程
cs
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mtext[128];
char ID[4];
};
int main()
{
struct msgbuf sendbuf;
int msgid;
int readret;
key_t key;
key = ftok("a.c",1);
msgid = msgget(key,IPC_CREAT | 0755);
if(msgid == -1)
{
printf("creat message queue failed\n");
return -1;
}
system("ipcs -q");
printf("creat message queue success,msgid = %d\n",msgid);
//init message
sendbuf.mtype = 100;
//send message to message queue
while(1)
{
memset(sendbuf.mtext,0,128);
printf("please input to message queque\n");
fgets(sendbuf.mtext,128,stdin);
msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtext),0);
}
return 0;
}
读进程
cs
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mtext[128];
char ID[4];
};
int main()
{
struct msgbuf readbuf;
int msgid;
int readret;
key_t key;
key = ftok("a.c",1);
msgid = msgget(key,IPC_CREAT | 0755);
if(msgid == -1)
{
printf("creat message queue failed\n");
return -1;
}
system("ipcs -q");
printf("creat message queue success,msgid = %d\n",msgid);
//init message
readbuf.mtype = 100;
//send message to message queue
while(1)
{
memset(readbuf.mtext,0,128);
readret = msgrcv(msgid,(void *)&readbuf,128,100,0);
printf("receive message id %s\n",readbuf.mtext);
printf("total receive %d byte\n",readret);
}
return 0;
}

消息队列进程间全双工通信
service进程
cs
int main()
{
struct msgbuf sendbuf,readbuf;
int msgid;
int readret;
int pid;
key_t key;
key = ftok("a.c",1);
msgid = msgget(key,IPC_CREAT | 0755);
if(msgid == -1)
{
printf("creat message queue failed\n");
return -1;
}
system("ipcs -q");
printf("creat message queue success,msgid = %d\n",msgid);
//init message
sendbuf.mtype = 100;
pid = fork();
if(pid > 0)//parent progress write 100
{
while(1)
{
memset(sendbuf.mtext,0,128);
printf("please input to message queque\n");
fgets(sendbuf.mtext,128,stdin);
msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtext),0);
}
}
else if(pid == 0)//child progress read 200
{
while(1)
{
memset(readbuf.mtext,0,128);
readret = msgrcv(msgid,(void *)&readbuf,128,200,0);
printf("receive progress is %s\n",readbuf.mtext);
printf("total receive %d byte\n",readret);
}
}
return 0;
}
client进程
cs
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct msgbuf
{
long mtype;
char mtext[128];
char ID[4];
};
int main()
{
struct msgbuf sendbuf,readbuf;
int msgid;
int readret;
int pid;
key_t key;
key = ftok("a.c",1);
msgid = msgget(key,IPC_CREAT | 0755);
if(msgid == -1)
{
printf("creat message queue failed\n");
return -1;
}
system("ipcs -q");
printf("creat message queue success,msgid = %d\n",msgid);
//init message
sendbuf.mtype = 200;
pid = fork();
if(pid == 0)//child progress write 200
{
while(1)
{
memset(sendbuf.mtext,0,128);
printf("please input to message queque\n");
fgets(sendbuf.mtext,128,stdin);
msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtext),0);
}
}
else if(pid > 0)//parent progress read 200
{
while(1)
{
memset(readbuf.mtext,0,128);
readret = msgrcv(msgid,(void *)&readbuf,128,100,0);
printf("receive progress is %s\n",readbuf.mtext);
printf("total receive %d byte\n",readret);
}
}
return 0;
}

注意:消息类型一一对应就可以,父进程读还是写取决于你对它业务的安排,你安排它读就让它读
共享内存
概念
共享内存(Shared Memory)就是允许多个进程访问同一个内存空间,是在多个进程之间共享和传递数据最高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其它的进程读到的数据也将会改变。
相关函数
int shmget(key_t key, size_t size, int shmflg);
//用来获取或创建共享内存
参数:
key:IPC_PRIVATE或ftok的返回值
size:共享内存区大小
shmflg:同open函数的权限位,也可以用8进制表示法
返回值:
成功:共享内存段标识符‐‐‐ID‐‐‐文件描述符
出错:‐1
void *shmat(int shm_id, const void *shm_addr, int shmflg);//把共享内存连接映射到当前进程的地址空间
参数:
shm_id:ID号
shm_addr:映射到的地址,NULL为系统自动完成的映射
shmflg:SHM_RDONLY共享内存只读。默认是0,表示共享内存可读写
返回值:
成功:映射后的地址
失败:NULL
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 shmid
buf :指定IPC_STAT/IPC_SET时用以保存/设置属性
返回值:
成功:0
出错:‐1
特点:
1.共享内存创建之后,一直存在于内核中,直到被删除或系统关闭
2.共享内存和管道不一样,读取后,内容仍然在共享内存中
共享空间创建和读取
cs
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main()
{
int shmid;
int key;
char *p;
key = ftok("a.c",0);
if(key < 0)
{
printf("ftok failure\n");
return -1;
}
printf("ftok success,key:%d\n",key);
shmid = shmget(key,128,IPC_CREAT | 0777);
if(shmid < 0)
{
printf("creat share memory failure\n");
return -2;
}
printf("creat share memory success,shmid is %d\n",shmid);
system("ipcs -m");
p = (char *)shmat(shmid,NULL,0);
if(p ==NULL)
{
printf("shmat function failure!\n");
return -3;
}
//write to share memory
fgets(p,128,stdin);
//read from share memory
printf("read share memory date is %s\n",p);
printf("read second share memory is %s\n",p);
return 0;
}
注:fgets(p, 128, stdin)
的作用是从 标准输入(stdin) 读取最多 128 个字符(包括换行符),并将其 写入共享内存(p
指向的共享内存区域)

删除共享内存映射
cs
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main()
{
int shmid;
int key;
char *p;
key = ftok("a.c",0);
if(key < 0)
{
printf("ftok failure\n");
return -1;
}
printf("ftok success,key:%d\n",key);
shmid = shmget(key,128,IPC_CREAT | 0777);
if(shmid < 0)
{
printf("creat share memory failure\n");
return -2;
}
printf("creat share memory success,shmid is %d\n",shmid);
system("ipcs -m");
p = (char *)shmat(shmid,NULL,0);
if(p ==NULL)
{
printf("shmat function failure!\n");
return -3;
}
//write to share memory
fgets(p,128,stdin);
//read from share memory
printf("read share memory date is %s\n",p);
printf("read second share memory is %s\n",p);
shmdt(p);
memcoy(p,"hello",NULL);
return 0;
}

此时删掉了映射地址空间,再往里面写东西会发生段错误。mencpy()
删除共享内存
cs
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main()
{
int shmid;
int key;
char *p;
key = ftok("a.c",0);
if(key < 0)
{
printf("ftok failure\n");
return -1;
}
printf("ftok success,key:%d\n",key);
shmid = shmget(key,128,IPC_CREAT | 0777);
if(shmid < 0)
{
printf("creat share memory failure\n");
return -2;
}
printf("creat share memory success,shmid is %d\n",shmid);
system("ipcs -m");
p = (char *)shmat(shmid,NULL,0);
if(p ==NULL)
{
printf("shmat function failure!\n");
return -3;
}
//write to share memory
fgets(p,128,stdin);
//read from share memory
printf("read share memory date is %s\n",p);
printf("read second share memory is %s\n",p);
shmdt();
shmctl(shmid,IPC_RMIC,NULL);
system("ipcs -m");
return 0;
}

封装ipcrm -m shmid
cs
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main(int argc,char *argv[])
{
int shmid;
if(argc < 3)
{
printf("input error\n");
return -1;
}
if(strcmp(argv[1],"-m") == 0)
{
printf("delete share memory\n");
}
else
{
return -2;
}
shmid = atoi(argv[2]);
printf("delete share memory id :%d\n",shmid);
shmctl(shmid,IPC_RMID,NULL);
system("ipcs -m");
return 0;
}
~
注:atoi() asc码转int类型
