进程间通信
多个进程需要互相配合完成一项任务,称为进程同步,例如公交车问题。
多个进程需要访问共享资源(临界资源),而资源同时只能被一个进程访问,各个进程相互竞争使用资源,称为互斥。例如买票问题。
为什么需要进程间通信:
- 数据传递
- 资源共享,多个进程使用一个资源
- 通知事件,告诉其他进程事件发生
通信方式分类:
- 管道
- System V IPC,消息队列,共享内存,信号量
- POSIX 比IPC多了互斥量,条件变量,读写锁
- socket
管道
管道将一个进程的输出流连接到另一个进程的输入流。
其原理是使用内核里的一块缓存区保存数据流。
管道是有大小限制的,内核总缓存大小65536字节,一次写入大小不超过4096B.
int pipe(int fd[2]);
利用fork共享管道时,由于子进程继承父进程文件描述符,所以管道的文件描述符fd0,fd1的引用计数为2。需要先调用close将不需要用的描述符计数-1。
//模拟shell管道
if(0 == fork()){
//子进程执行ls
//标准输出重定向到管道写端
dup2(fds[1], 1);
close(fds[0]);
execlp("ls", "ls", "-l", NULL);
perror("execlp");
exit(1);
}
else{
//父进程从管道接受输入流
dup2(fds[0], 0);
close(fds[1]);
execlp("wc", "wc", "-l", NULL);
perror("execlp");
exit(1);
}
视频解码器就类似于管道,拿走数据和接受数据以一定速度同时进行。
命名管道
匿名管道只能让子进程继承父进程文件描述符,从而得到管道描述符和内核的缓存。
命名管道是一种特殊文件,创建打开后,使两个无亲缘关系进程拿到同一块内核空间,与管道使用方法相同。
创建命名管道
int mkfifo (const char *filename, mode_t mode);
命名管道实现C/S通信:
/*server.c*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define ERR_EXIT(m) \
do{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0);
int main()
{
umask(0);
if(mkfifo("mypipe", 0644) < 0){
ERR_EXIT("mkfifo");
}
int rfd = open("mypipe", O_RDONLY);
if(rfd < 0){
ERR_EXIT("open");
}
char buf[1024];
while(1){
buf[0] = 0;
printf("please wait\n");
ssize_t s = read(rfd, buf, sizeof(buf)-1);
if(s > 0){
buf[s-1] = 0;
printf("client say# %s\n", buf);
}
else if(s == 0){
printf("client quit, exit now!\n");
exit(EXIT_SUCCESS);
}
else{
ERR_EXIT("read");
}
}
close(rfd);
return 0;
}
/*client.c*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define ERR_EXIT(m) \
do{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0);
int main()
{
int wfd = open("mypipe", O_WRONLY);
if(wfd < 0){
ERR_EXIT("open");
}
char buf[1024];
while(1){
buf[0] = 0;
printf("please Enter #");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf)-1);
if(s > 0){
buf[s] = 0;
write(wfd, buf, strlen(buf));
}
else{
ERR_EXIT("read");
}
}
close(wfd);
return 0;
}
消息队列
消息队列提供一个进程向另外一个进程发送数据的方法。
消息队列结构体存储了关于消息指针,消息个数,消息最大值,消息队列现在的大小等信息…
创建消息队列
int msgget(key_t key, int msgflag);
key:消息队列的名字
msgflag:创建 IPC_CREAT | 权限
返回值:消息队列的标识符,类似于文件描述符。
发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflag);
参数:消息队列标识符,消息类型指针,消息类型的长度,状态控制。
msgflag:IPC_NOWAIT
自定义的消息类型
struct msg{
long type; //消息类型,区分队列中不同的消息通道
char text[100];
};
接受消息
ssize_t msgrcv(int qid, void *msgp, size_t msgsz, long msgtyp, int msgflag);
//send
void send(){
struct msg m;
int qid = msgget(123, IPC_CREAT);
long type;
scanf("%ld", &type);
m.type = type;
scanf("%s", m.text);
int ret = msgsnd(qid, &m, strlen(m.text), 0);
if(ret == -1)
perror("msgsnd");
}
//recv
void recv(){
struct msg m;
int qid = msgget(123, 0);
long type;
scanf("%ld", &type);
int ret = msgrcv(qid, &m, 100, type, 0);
}
查看/删除消息队列
$ipcs -q
$ipcrm -Q key