一、介绍
进程与进程间的用户空间相互独立,内核空间共享。
1.传统的进程间通信机制
a.无名管道 pipe
b.有名管道 fifo
c.信号 signal
2.system V中的IPC对象
a.消息队列 message queue
b.共享内存 shared memory
c.信号灯集 semaphoare
3.可用于跨主机传输的通信机制
a.套接字 socket
二、管道
1.管道可以看成是一个特殊文件,一般文件存储在外存中,而管道内容存储在内存中
2.管道遵循先进先出原则
3.管道的读操作是一次性的,内容被读出后就会从管道中删除
4.管道是一种半双工的通信方式
5.管道只能使用文件IO函数,因为需要直接操作内核空间,如open,close,read,write,但不能使用lseek
6.管道的大小为64k
2.1无名管道
无名管道即在文件系统(用户系统)不可见的管道文件
无名管道不可以用open打开,因为不知道路径以及名字
无名管道只能用于具有亲缘关系的进程间通信。由于无名管道在文件系统中不可见,两个无关的进程,无法拿到同一根管道的读写段,只有具有亲缘关系的进程,在父进程中创建一根管道,拿到读写端后,调用fork函数,创建出来的子进程也会有该管道的读写端。
从管道中读取数据:
1.读写端均存在时,当管道中没有数据时,read会阻塞2.当管道的写端不存在时,若管道中有数据,会先将数据读取完毕,没有数据时,read函数不会阻塞,直接返回0
向管道中写入数据:
1.读写段均存在时,write会阻塞
2.当管道的读端不存在时,调用write函数,尝试向管道中写入数据会导致管道破裂。
#include <head.h>
int main(int argc, char const *argv[])
{
// 管道的创建必须放在fork前
// 若放在fork后,会导致父子进程各自创建一个内管道,无法通信
int pfd[2] = {0};
if (pipe(pfd) < 0) // pf[0]为读的文件描述符,pf[1]为写的文件描述符
{
perror("pipe");
return -1;
}
printf("管道创建成功\n");
pid_t pid = fork();
if (pid > 0)
{
// 父进程发数据给子进程
char buf[128] = "";
while (1)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = 0; // 从终端获取数据,将最后获取到的\n变成\0
// 将数据写入到管道中
if (write(pfd[1], buf, sizeof(buf)) < 0)
{
perror("write");
return -1;
}
printf("写入成功\n");
}
}
else if (pid == 0)
{
// 子进程接收父进程发送过来的数据
char buf[128] = "";
int res = 0;
while (1)
{
// 管道中没有数据时,read会阻塞
res = read(pfd[0], buf, sizeof(buf));
printf("读取成功\n");
printf("%s\n", buf);
}
}
else
{
perror("fork");
return -1;
}
return 0;
}
2.2有名管道
写端
#include <head.h>
int main(int argc, char const *argv[])
{
umask(0);
// 创建有名管道
if (mkfifo("./myfifo", 0664) < 0)
{
if (errno != 17)
{ // 文件已存在的错误是一个合法的错误,需要排除,代码允许正常运行
perror("mkfifo");
return -1;
}
}
printf("有名管道创建成功\n");
// 以只写的方式打开有名管道
int fd = open("./myfifo", O_WRONLY); // 当只有一个写端时,open函数会阻塞
if (fd < 0)
{
perror("open");
return -1;
}
printf("open succcess\n");
char buf[128] = "";
while (1)
{
printf("请输入>>>");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = 0;
if (write(fd, buf, sizeof(buf)) < 0)
{
perror("write");
return -1;
}
if (strcmp(buf, "quit") == 0)
break;
printf("写入成功\n");
}
close(fd);
return 0;
}
读端
#include <head.h>
int main(int argc, char const *argv[])
{
umask(0);
// 创建有名管道
if (mkfifo("./myfifo", 0664) < 0)
{
if (errno != 17)
{ // 文件已存在的错误是一个合法的错误,需要排除,代码允许正常运行
perror("mkfifo");
return -1;
}
}
printf("有名管道创建成功\n");
// 以只读的方式打开有名管道
int fd = open("./myfifo", O_RDONLY); // 当只有一个读端时,open函数会阻塞
if (fd < 0)
{
perror("open");
return -1;
}
printf("open succcess\n");
char buf[128] = "";
int res = 0;
while (1)
{
bzero(buf, sizeof(buf));
res = read(fd, buf, sizeof(buf));
if (res < 0)
{
perror("read");
return -1;
}
else if ((res == 0) || strcmp(buf, "quit") == 0)
{
printf("写端退出\n");
break;
}
printf("buf=%s\n", buf);
}
close(fd);
return 0;
}
三、信号
原理
信号是一种异步通信的方式
异步:任务与任务之间无关系,根据CPU轮询机制来运行多个任务
同步:任务与任务之间有先后关系,必须要等任务A结束2后才能执行任务B
1.signal
#include <head.h>
void handler(int sig)
{
printf("sig=%d\n", sig);
return;
}
int main(int argc, char const *argv[])
{
// 捕获2)SIGINT信号
if (signal(2, handler) == SIG_ERR)
//第一个参数:指定要捕获的信号,天对应的编号或宏
//第二个参数:可以填SIG_IGN:忽略信号
//SIG_DFL:执行默认操作
//捕获信号信号:填写函数指针变量
{
perror("signal");
return -1;
}
printf("捕获信号成功\n");
while (1)
{
printf("主函数\n");
sleep(1);
}
return 0;
}
练习:用信号的方式回收僵尸进程
#include <head.h>
int count = 0;
void handler(int sig)
{
while (waitpid(-1, NULL, WNOHANG) > 0)
;
}
int main(int argc, char const *argv[])
{
// 捕获17)SIGCHLD
__sighandler_t s = signal(17, handler);
if (SIG_ERR == s)
{
perror("signal");
return -1;
}
int i = 0;
while (i < 100)
{
int res = fork();
if (res == 0)
exit(0);
i++;
}
while (1)
sleep(1);
return 0;
}
2.kill
当收到quit时,父子进程全部退出
#include <head.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if (pid > 0)
{
while (1)
{
printf("父进程 %d %d\n", getpid(), pid);
sleep(1);
}
}
else if (pid == 0)
{
char buf[128] = "";
bzero(buf, sizeof(buf));
while (1)
{
printf("请输入>>>");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = 0;
printf("buf=%s", buf);
if (strcmp(buf, "quit") == 0)
{
break;
}
}
// 子进程给父进程发信号,请求父进程退出
kill(getppid(), 9); // 9号是杀死信号
}
else
{
perror("fork");
return -1;
}
return 0;
}
3.alarm
设置3秒的定时器
#include <head.h>
void callback(int sig)
{
printf("超时了\n");
alarm(3);
return;
}
int main(int argc, char const *argv[])
{
// 捕获14号信号
if (signal(14, callback) == SIG_ERR)
{
perror("signal");
return -1;
}
alarm(3);
while (1)
{
printf("signal....\n");
sleep(1);
}
return 0;
}
四、消息队列
消息队列按照先进先出的原则,但也可以限制消息类型读取
消息队列独立于进程,等进程结束后,消息队列以及其中的内容不会删除,除非重启操作系统或手动删除
发送数据
#include <head.h>
struct msgbuf
{
long mtype; // 消息类型,必须大于0
char mtext[128]; // 消息内容
};
int main(int argc, char const *argv[])
{
// 创建key值
key_t key = ftok("/home/ubuntu/", 1);
// 第二个参数:填非0
if (key < 0)
{
perror("ftok");
return -1;
}
// 创建消息队列
int msqid = msgget(key, IPC_CREAT | 0664);
if (msqid < 0)
{
perror("msgget");
return -1;
}
struct msgbuf sndbuf;
while (1)
{
printf("请输入人消息类型>>>");
scanf("%ld", &sndbuf.mtype);
getchar();
if (sndbuf.mtype == 0)
{ // 若输入类型为0,退出循环
break;
}
printf("请输入消息内容>>>");
fgets(sndbuf.mtext, sizeof(sndbuf.mtext), stdin);
sndbuf.mtext[strlen(sndbuf.mtext) - 1] = 0;
// 向消息队列中发送数据
if (msgsnd(msqid, &sndbuf, sizeof(sndbuf.mtext), 0) < 0)
{
perror("msgsnd");
return -1;
}
printf("发送成功\n");
}
// 删除消息队列
if (msgctl(msqid, IPC_RMID, NULL) < 0)
{
perror("msgctl");
return -1;
}
printf("删除消息队列成功\n");
return 0;
}
接收数据
#include <head.h>
struct msgbuf
{
long mtype; // 消息类型,必须大于0
char mtext[128]; // 消息内容
};
int main(int argc, char const *argv[])
{
// 创建key值
key_t key = ftok("/home/ubuntu/", 1);
// 第二个参数:填非0
if (key < 0)
{
perror("ftok");
return -1;
}
// 创建消息队列
int msqid = msgget(key, IPC_CREAT | 0664);
if (msqid < 0)
{
perror("msgget");
return -1;
}
struct msgbuf recvbuf;
int res = 0;
while (1)
{
// 从指定的消息队列中读取数据
// 读取消息队列中的第一条消息,先进先出
res = msgrcv(msqid, &recvbuf, sizeof(recvbuf.mtext), 0, IPC_NOWAIT);
if (res < 0)
{
perror("msgrcv");
return -1;
}
printf("接收到的消息为%s\n", recvbuf.mtext);
}
// 删除消息队列
if (msgctl(msqid, IPC_RMID, NULL) < 0)
{
perror("msgctl");
return -1;
}
printf("删除消息队列成功\n");
return 0;
}
五、共享内存
共享内存是最高效的进程间通信方式
多个进程可以同时访问共享内存,修改其中的内容,所以共享内存其实是临界资源,需要注意进程间的同步互斥
共享内存独立于进程,等进程结束后,共享内存以及其中的内容不会删除,除非重启操作系统或者手动删除
发送数据
#include <head.h>
int main(int argc, char const *argv[])
{
// 创建key值
key_t key = ftok("./", 10);
if (key < 0)
{
perror("ftok");
return -1;
}
printf("key=%#x\n", key);
// 创建共享内存,获得shmid号
int shmid = shmget(key, 32, IPC_CREAT | 0664);
if (shmid < 0)
{
perror("shmget");
return -1;
}
printf("shmid=%d\n", shmid);
// 映射共享内存到用户空间
void *addr = shmat(shmid, NULL, 0);
if (addr == (void *)-1)
{
perror("shmat");
return -1;
}
// 先往共享内存中存储一个int类型的数据
*(int *)addr = 10;
// 再往int类型数据后面存储一个字符串
char *ptr = (char *)addr + 4;
strcpy(ptr, "hello world");
return 0;
}
接收数据
#include <head.h>
int main(int argc, char const *argv[])
{
// 创建key值
key_t key = ftok("./", 10);
if (key < 0)
{
perror("ftok");
return -1;
}
// 创建共享内存,获得shmid号
int shmid = shmget(key, 32, IPC_CREAT | 0664);
if (shmid < 0)
{
perror("shmget");
return -1;
}
// 映射共享内存到用户空间
void *addr = shmat(shmid, NULL, 0);
if (addr == (void *)-1)
{
perror("shmat");
return -1;
}
printf("%d\n", *(int *)addr);
printf("%s\n", (char *)((int *)addr + 1));
return 0;
}