一、介绍
进程与进程间的用户空间相互独立,内核空间共享。
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; // 消息类型,必须大于0char mtext[128]; // 消息内容
};
int main(int argc, char const *argv[])
{// 创建key值key_t key = ftok("/home/ubuntu/", 1);// 第二个参数:填非0if (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; // 消息类型,必须大于0char mtext[128]; // 消息内容
};
int main(int argc, char const *argv[])
{// 创建key值key_t key = ftok("/home/ubuntu/", 1);// 第二个参数:填非0if (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;
}