一、概念
进程间通信(Inter-Process Communication,IPC)是指在不同进程之间进行信息交换和同步的机制。
二、实现方式
1、管道
(1)无名管道
原理:它是一种半双工的通信方式,数据只能单向流动,并且只能在具有亲缘关系(如父子进程)的进程之间使用。无名管道在内核中通过一个环形缓冲区来实现,管道的两端分别是读端和写端。
特点:创建简单,使用方便,但功能相对有限,数据传输的方向性强,适用于亲缘关系进程间的简单数据传递进程间的通信方式——pipe(管道)-CSDN博客
man 2 pipe
int pipe(int pipefd[2]);
功能:
创建操作管道的两个文件描述符
参数:
pipefd: 存放文件描述符的数组
pipefd[0]:读文件描述符,用于从管道中读取数据。
pipefd[1]:写文件描述符,用于向管道中写入数据。
返回值:
成功返回0
失败返回-1
#include <stdio.h> #include "public.h" char tmpbuff[4096] = {0}; int main(int argc, const char **argv) { pid_t pid; int pipefd[2]; int ret = 0; ret = pipe(pipefd); if (-1 == ret) { ERR_MSG("fail to pipe"); return -1; } pid = fork(); if (-1 == pid) { ERR_MSG("fail to fork"); return -1; } if (0 == pid) { close(pipefd[0]); //用不到读,所以先关掉(如上图3) strcpy(tmpbuff, "hello world"); write(pipefd[1], tmpbuff, strlen(tmpbuff)); //将 tmpbuff 中的数据写入管道。 close(pipefd[1]); //写用完了,关掉 } else if (pid > 0) { close(pipefd[1]); //用不到写,所以先关掉(如上图3) read(pipefd[0], tmpbuff, sizeof(tmpbuff)); //将管道中的数据读到 tmpbuff 中。 printf("tmpbuff = %s\n", tmpbuff); close(pipefd[0]); //读完了,关掉 } return 0; }(2)有名管道
原理:它是一种特殊的文件,在文件系统中以文件名的形式存在,突破了无名管道只能在亲缘关系进程间通信的限制,不同进程可以通过该文件名来访问管道进行通信。
特点:有自己的文件路径,可以在不相关的进程间使用,实现了不同进程在文件系统层面的通信接口。
man 3 mkfifo
int mkfifo(const char *pathname, mode_t mode);
功能:
创建管道文件
参数:
pathname:管道文件名
mode:权限
返回值:
成功返回0
失败返回-1
用有名管道实现单工通信
send.c文件
#include <stdio.h> #include "public.h" int main(int argc, const char **argv) { int fd = 0; char tmpbuff[1024] = {0}; int ret = mkfifo("/tmp/myfifo", 0777); if(-1 == ret && errno != EEXIST) //EEXIST:它表示在尝试创建文件(或目录)时,目标文件(或目录)已经存在。 //errno != EEXIST表示错误原因不是因为,文件已经存在了 { perror("mkfifo error"); return -1; } fd = open("/tmp/myfifo", O_RDWR); if (-1 == fd) { ERR_MSG("fail to open"); return -1; } gets(tmpbuff); write(fd, tmpbuff, strlen(tmpbuff)); close(fd); return 0; }recv.c文件
#include <stdio.h> #include "public.h" int main(int argc, const char **argv) { int fd = 0; char tmpbuff[1024] = {0}; int ret = mkfifo("/tmp/myfifo", 0777); if(-1 == ret && errno != EEXIST) //EEXIST:它表示在尝试创建文件(或目录)时,目标文件(或目录)已经存在。 //errno != EEXIST表示错误原因不是因为,文件已经存在了 { perror("mkfifo error"); return -1; } fd = open("/tmp/myfifo", O_RDWR); if (-1 == fd) { ERR_MSG("fail to open"); return -1; } read(fd, tmpbuff, sizeof(tmpbuff)); printf("RECV:%s\n", tmpbuff); close(fd); remove("myfifo"); //读完删除管道文件 return 0; }编译send.c文件生成可执行文件send
编译recv.c文件生成可执行文件recv
执行send文件向管道中输入hello
与此同时在执行当中的recv文件会从管道中接收到hello并打印出来
(3)管道的特点
管道本质是内核中的一段缓冲区,默认64K字节,遵循队列存储方式(FFO)。
1.写阻塞:当管道的读写端都存在时,管道写满数据时,再写会发生写阻塞。
2.读阻塞:当管道的读写端都存在时,管道为空时,再读管道会发生读阻塞。
3.读返回0:当管道没有写端,只有读端时,管道中有数据则读出数据,无数据时,不再读阻塞,读管道(read),函数的返回值返回0。
4.管道破裂:当管道没有读端,向管道中写数据,发生管道破裂异常
用有名管道实现半双工通信
send.c文件
#include <stdio.h> #include "public.h" int main(int argc, const char **argv) { int fatob = 0; int fbtoa = 0; char tmpbuff[4096] = {0}; mkfifo("/tmp/ATOB", 0777); mkfifo("/tmp/BTOA", 0777); fatob = open("/tmp/ATOB", O_WRONLY); //send.c文件中打开写端,等待recv.c文件打开读端口,管道才会打开成功 if (-1 == fatob) { ERR_MSG("fail to open"); return -1; } fbtoa = open("/tmp/BTOA", O_RDONLY); //同理 if (-1 == fbtoa) { ERR_MSG("fail to open"); return -1; } printf("管道打开成功!\n"); while (1) { gets(tmpbuff); write(fatob, tmpbuff, strlen(tmpbuff)); memset(tmpbuff, 0, sizeof(tmpbuff)); read(fbtoa, tmpbuff, sizeof(tmpbuff)); printf("RECV:%s\n", tmpbuff); } close(fatob); close(fbtoa); return 0; }recv.c文件
#include <stdio.h> #include "public.h" int main(int argc, const char **argv) { int fatob = 0; int fbtoa = 0; char tmpbuff[4096] = {0}; mkfifo("/tmp/ATOB", 0777); mkfifo("/tmp/BTOA", 0777); fatob = open("/tmp/ATOB", O_RDONLY); if (-1 == fatob) { ERR_MSG("fail to open"); return -1; } fbtoa = open("/tmp/BTOA", O_WRONLY); if (-1 == fbtoa) { ERR_MSG("fail to open"); return -1; } printf("管道打开成功!\n"); while (1) { memset(tmpbuff, 0, sizeof(tmpbuff)); read(fatob, tmpbuff, sizeof(tmpbuff)); printf("RECV:%s\n", tmpbuff); gets(tmpbuff); write(fbtoa, tmpbuff, strlen(tmpbuff)); } close(fatob); close(fbtoa); return 0; }用有名管道实现全双工通信
老王.c
#include <stdio.h> #include "public.h" int fatob = 0; int fbtoa = 0; void *sendfun(void *arg) { char tmpbuff[4096] = {0}; while (1) { gets(tmpbuff); if (0 == strcmp(tmpbuff, ".quit")) { break; } write(fbtoa, tmpbuff, strlen(tmpbuff)); } close(fbtoa); return NULL; } void *recvfun(void *arg) { char tmpbuff[4096] = {0}; ssize_t nret = 0; while (1) { memset(tmpbuff, 0, sizeof(tmpbuff)); nret = read(fatob, tmpbuff, sizeof(tmpbuff)); if (0 == nret) { break; } printf("RECV:%s\n", tmpbuff); } return NULL; } int main(int argc, const char **argv) { pthread_t tid_send; pthread_t tid_recv; char tmpbuff[4096] = {0}; mkfifo("/tmp/ATOB", 0777); mkfifo("/tmp/BTOA", 0777); fatob = open("/tmp/ATOB", O_RDONLY); if (-1 == fatob) { ERR_MSG("fail to open"); return -1; } fbtoa = open("/tmp/BTOA", O_WRONLY); if (-1 == fbtoa) { ERR_MSG("fail to open"); return -1; } printf("管道打开成功!\n"); pthread_create(&tid_send, NULL, sendfun, NULL); pthread_create(&tid_recv, NULL, recvfun, NULL); pthread_join(tid_send, NULL); pthread_join(tid_recv, NULL); #if 0 while (1) { gets(tmpbuff); write(fatob, tmpbuff, strlen(tmpbuff)); memset(tmpbuff, 0, sizeof(tmpbuff)); read(fbtoa, tmpbuff, sizeof(tmpbuff)); printf("RECV:%s\n", tmpbuff); } #endif close(fatob); return 0; }老李.c
#include <stdio.h> #include "public.h" int fatob = 0; int fbtoa = 0; void *sendfun(void *arg) { char tmpbuff[4096] = {0}; while (1) { gets(tmpbuff); if (0 == strcmp(tmpbuff, ".quit")) { break; } write(fatob, tmpbuff, strlen(tmpbuff)); } close(fatob); return NULL; } void *recvfun(void *arg) { char tmpbuff[4096] = {0}; ssize_t nret = 0; while (1) { memset(tmpbuff, 0, sizeof(tmpbuff)); nret = read(fbtoa, tmpbuff, sizeof(tmpbuff)); if (0 == nret) { break; } printf("RECV:%s\n", tmpbuff); } return NULL; } int main(int argc, const char **argv) { pthread_t tid_send; pthread_t tid_recv; char tmpbuff[4096] = {0}; mkfifo("/tmp/ATOB", 0777); mkfifo("/tmp/BTOA", 0777); fatob = open("/tmp/ATOB", O_WRONLY); if (-1 == fatob) { ERR_MSG("fail to open"); return -1; } fbtoa = open("/tmp/BTOA", O_RDONLY); if (-1 == fbtoa) { ERR_MSG("fail to open"); return -1; } printf("管道打开成功!\n"); pthread_create(&tid_send, NULL, sendfun, NULL); pthread_create(&tid_recv, NULL, recvfun, NULL); pthread_join(tid_send, NULL); pthread_join(tid_recv, NULL); #if 0 while (1) { gets(tmpbuff); write(fatob, tmpbuff, strlen(tmpbuff)); memset(tmpbuff, 0, sizeof(tmpbuff)); read(fbtoa, tmpbuff, sizeof(tmpbuff)); printf("RECV:%s\n", tmpbuff); } #endif close(fbtoa); return 0; }2、信号
(1)概念
进程间通知机制(不携带数据)
信号可以看作是一种轻量级的软件中断,它能够在进程运行过程中异步地通知进程某一特定事件已经发生。当信号发送给进程时,进程会暂停当前正在执行的任务,转而去处理该信号对应的事件。处理完毕后,进程可能会继续执行之前暂停的任务,也可能根据信号的处理结果做出其他操作。例如,在 Linux 系统中,用户按下
Ctrl + C组合键,会产生一个SIGINT信号发送给当前正在运行的前台进程,通知该进程要终止运行。
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX 2) SIGINT:ctrl + c 让一个进程被打断 3) SIGQUIT:ctrl + \ 让一个进程结束 9) SIGKILL: 强制让一个进程结束 11)SIGSEGV: 让一个进程结束(段错误) 13)SIGPIPE: 让一个进程结束(管道破裂) 14)SIGALRM: 让一个进程结束(定时时间到达) 17)SIGCHLD: 子进程结束时发送给父进程 18)SIGCONT: 让停止态的进程继续执行 19)SIGSTOP: 让运行态的进程进入停止态(暂停)强制停止 20)SIGTSTP: ctrl + z 让进程进入暂停态,后台进程 来自终端的停止信号 10 SIGUSR1 12 SIGUSR2 专门预留给程序员使用的未定义信号。(2)注册信号
sighandler_t signal(int signum, sighandler_t handler);
功能:注册一个信号
参数:
signum :要注册的信号的编号
handler:对信号的处理方式
SIG_IGN : 按忽略方式处理
SIG_DFL :按照缺省方式处理
自定义的函数:按照捕获方式处理
返回值:
成功:handler
失败:SIG_ERR
信号处理方式:
1. 忽略 :不处理
2. 缺省 :按照操作系统默认方式处理
3. 捕获 :按照自定义方式处理
#include <stdio.h> #include <unistd.h> #include <signal.h> void handler(int signum) { printf("%d signal comming\n", signum); } int main(void) { // signal(SIGINT, SIG_IGN); //信号的处理方式一: 不处理,即不对SIGINT信号做处理,终端一直打印hello world,按下ctrl+c进程结束不了。 // signal(SIGINT, SIG_DFL); //方式二:按照操作系统默认方式处理,终端一直打印hello world,按下ctrl+c进程结束。 signal(SIGINT, handler); //方式三:按照自定义方式处理,自定义方式遵循handler函数执行,终端一直打印hello world,按下ctrl+c进程不结束,打印一句signal comming后继续打印hello world。 while (1) { printf("hello world\n"); sleep(1); } return 0; }