前言
进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。
一、管道
Ⅰ、无名管道
1、简介:
管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。
2、特点:
①它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
②它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
③它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
3、函数原型:
#include <unistd.h>
int pipe(int fd[2]);
管道存在于内核当中。fd[2]为两个文件描述符,fd[0]为只读文件描述符,fd[1]为只写文件描述符。要关闭管道只需将这两个文件描述符关闭即可。
4、返回值:
若成功返回0,失败返回-1
5、例程:
pipe.c
#include <stdio.h>
#include <unistd.h>
//int pipe(int pipefd[2]);
int main()
{
int fd[2];
pid_t pid;
char readBuf[20];
if(pipe(fd) == -1){
printf("Creat pipe error.\n");
}
if((pid = fork()) < 0){
printf("Creat process error.\n");
}
else if(pid > 0){ //父进程
sleep(3);
printf("This is father process.\n");
close(fd[0]); //关闭读
write(fd[1],"Hello world!\n",12); //往管道里写
}
else if(pid == 0){ //子进程
printf("This is child process.\n");
close(fd[1]); //关闭写
read(fd[0],readBuf,20); //读管道内容读到readBuf,如果管道内没东西,子进程会阻塞在这
printf("read from father process is %s\n",readBuf);
}
close(fd[0]);
close(fd[1]);
return 0;
}
6、运行结果:
$ ./pipe
This is child process.
This is father process.
read from father process is Hello world!
程序先执行子进程,因为设置了父进程先休眠3s,子进程阻塞在read()函数,3s过后执行父进程打印和写入内容至管道,之后子进程读取内容,并打印。
Ⅱ、命名管道 FIFO
1、简介:
FIFO,也称为命名管道,它是一种文件类型。
2、特点:
①FIFO可以在无关的进程之间的亲缘关系交换数据,与无名管道不同。
②FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
3、函数原型:
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
- 若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open要阻塞到某个其他进程为读而打开它。
- 若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该FIFO,其errno置ENXIO。
4、返回值:
返回值:成功返回0,出错返回-1。
5、例程:
//FIFO_write.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
int fd;
char *str = "message from fifo";
if(mkfifo("./file.txt",0600) == -1 && errno!=EEXIST){ //EEXIST表示文件存在的宏
printf("Creat fifo failed\n");
perror("why");
}
fd = open("./file.txt",O_WRONLY); //只写打开管道文件
write(fd,str,strlen(str));
printf("write success\n");
close(fd);
return 0;
}
//FIFO_read.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
int fd;
char readBuf[30] = {0};
if(mkfifo("./file.txt",0600) == -1 && errno!=EEXIST){ //创建命名管道
printf("Creat fifo failed\n");
perror("why");
}
fd = open("./file.txt",O_RDONLY); //只读打开管道文件
int nread = read(fd,readBuf,30); //读到readBuf
printf("read %d byte data,contex is %s\n",nread,readBuf);
close(fd);
return 0;
}
6、运行结果:
读进程:
$ ./read
//阻塞,等到执行了write进程
read 17 byte data,contex is message from fifo
写进程:
$ ./write
write success
二、消息队列
1、简介:
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
2、特点:
①消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
②消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
③消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
3、函数原型:
#include <sys/msg.h>
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数说明:
key:两个通信进程的key要相同,可通过ftok()得到。
flag:消息队列的一些宏,后面或上权限。
msqid:msgget()执行成功返回的ID,添加消息和读取消息进程的msqid要一致。
ptr:一个结构,struct msgbuf {
long mtype; /* message type, must be > 0 /
char mtext[199]; / message data */
};
size:写入队列的长度。
type:一般写0。
cmd:一些控制消息队列操作的宏。
buf:一般写NULL。
4、返回值:
①msgget():成功返回队列ID,失败返回-1
②msgsnd():成功返回0,失败返回-1
③msgrcv():成功返回消息数据的长度,失败返回-1
④msgctl():成功返回0,失败返回-1
5、例程:
//msgSend.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[199]; /* message data */
};
int main()
{
struct msgbuf buf = {999,"This is from que message"};
key_t key = ftok(".",'z');
printf("key = %x\n",key); //用ftok()生成key
int msgID = msgget(key,IPC_CREAT|0777);
if(msgID == -1){
printf("get que failuer\n");
}
msgsnd(msgID,&buf,strlen(buf.mtext),0);
printf("Send end\n");
//msgctl(msgID,IPC_RMID,NULL);
return 0;
}
//msgGet.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[30]; /* message data */
};
int main()
{
struct msgbuf readBuf;
int cnt = 0;
key_t key = ftok(".",'z'); //用ftok()生成key
printf("key = %x\n",key);
int msgID = msgget(key,IPC_CREAT|0777);
if(msgID == -1){
printf("get que failuer\n");
}
msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),999,0);
while(cnt < 3){
printf("readBuf is %s\n",readBuf.mtext);
cnt++;
}
//msgctl(msgID,IPC_RMID,NULL);
return 0;
}
6、运行结果:
Get进程:
$ gcc msgGet.c -o get
$ ./get
//阻塞在msgrcv(),执行完send进程,get往下执行
readBuf is This is from que message
readBuf is This is from que message
readBuf is This is from que message
Send进程:
$ gcc msgSend.c -o send
$ ./send
Send end
三、共享内存
1、简介:
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
2、特点
①共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
②因为多个进程可以同时操作,所以需要进行同步。
③信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
3、原型
#include <sys/shm.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr);
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
参数说明:
key:两个通信进程的key要相同,可通过ftok()得到。
size:开辟共享内存的大小,以MB为单位。
flag:一些宏,后面可或上权限。
shm_id:shmget()成功执行返回的ID。
shmat的addr:一般写NULL,随系统默认分配。
shmdt的addr:shmat()成功执行返回指向共享内存的指针。
cmd:控制共享内存操作的一些宏。
buf:一般写0。
注意事项:
- 当用shmget函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。
- 当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。
- shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。
- shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。
4、返回值:
①shmget():成功返回共享内存ID,失败返回-1
②shmat():成功返回指向共享内存的指针,失败返回-1
③shmdt():成功返回0,失败返回-1
④shmctl():成功返回0,失败返回-1
5、例程:
//shmw.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//int shmdt(const void *shmaddr);
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);
int main()
{
int shmid;
key_t key;
char *shmaddr;
key = ftok(".",1);
shmid = shmget(key,1024*4,IPC_CREAT|0666);
//新建共享内存,大小以MB为单位,开辟4MB,可读可写方式新建
if(shmid == -1){
printf("Shmget failuer\n");
exit(-1);
}
shmaddr = shmat(shmid,0,0); //将共享内存映射到本进程中
strcpy(shmaddr,"Hello World!"); //写内容到共享内存
sleep(5);
//shmdt(shmaddr); //释放映射
//shmctl(shmid,IPC_RMID,0); //释放共享内存
return 0;
}
//shmr.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//int shmdt(const void *shmaddr);
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);
int main()
{
int shmid;
key_t key;
char *shmaddr;
key = ftok(".",1);
shmid = shmget(key,1024*4,0); //打开共享内存
if(shmid == -1){
printf("Shmget failuer\n");
exit(-1);
}
shmaddr = shmat(shmid,0,0); //将共享内存映射到本进程中
printf("data is %s\n",shmaddr); //读取共享内存的内容
//shmdt(shmaddr);
printf("quit\n");
return 0;
}
6、运行结果:
read进程:
$ gcc shmr.c -o read
$ ./read
//没有内容 阻塞
data is Hello World!
write进程:
$ gcc shmw.c -o write
$ ./write