一. 概述
1.1 单机通信
- 半双工管道 (无名管道)
- 全双工管道FIFO(命名全双工管道)
- 消息队列
- 信号量
- 共享存储
1.2多机通信:
6.套接字
7.STREAMS
管道通信
- 管道,通常指无名管道,最古老的IPC形式
2.1 特点
1.半双工(数据只能在同一方向流动),有固定的读端和写端。管道中的数据,读走就没了
2.只用于具有亲缘关系的进程之间通信
3.可看成一种特殊的文件,但只存在于内存中,但不是文件
2.2原型
## Linux 快捷指令
man 2 pipe
- 当一个管道建立时,他会创建两个文件描述符,fd[0]为读而打开,fd[1]为写而打开
- 当需要写入数据时,必须先close(fd[0]),当需要读出数据时,必须先close[1],也就是同一时间只能读或者写
2.3实践
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include<string.h>
int main(){
int fd[2] = {0};
int pid;
int fp;
int status;
char wbuf[128] = "message from child";
char rbuf[128] = {0};
fp = pipe(fd);
pid = fork();
if(fp == -1){
printf("creat pipe fail\n");
exit(-1);
}else{
if(pid > 0){
wait(&status);
close(fd[1]);
read(fd[0],rbuf,strlen(wbuf));
printf("father:strlen(rbuf) = %ld\n",strlen(rbuf));
printf("father:read message\n");
printf("father:message = %s\n",rbuf);
printf("status = %d\n",WEXITSTATUS(status));
}else if(pid == 0){
close(fd[0]);
write(fd[1],wbuf,strlen(wbuf));
printf("child:strlen(wbuf) = %ld\n",strlen(wbuf));
printf("child:send message\n");
exit(0);
}else{
printf("fork error\n");
}
}
return 0;
}
执行结果
child:strlen(wbuf) = 18
child:send message
father:strlen(rbuf) = 18
father:read message
father:message = message from child
status = 0
ztj@ubuntu:~
FIFO
FIFO,也叫命名管道,它是一种文件类型
3.1特点
- FIFO可以在无关进程之间交换数据,与无名管道不同
- FIFO有路径名与之相关,它以一种特殊设备文件存在于文件系统中
3.2原型
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
- 成功返回0,失败返回-1
- mode与open相同,一旦创建FIFO,可以以一般文件i/o函数操作它
3.3 例子
- 创建 FIFO
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<errno.h>
int main(){
if((mkfifo("./file",0600) == -1)&&(errno != EEXIST)){
printf("mkfifo failuer\n");
perror("why");
}
return 0;
}
执行结果
3.4 FIFO读写
- open一个FIFO时,默认没有设置非阻塞标志,此时open会阻塞,只写open阻塞到有进程为了读而打开FIFO,只读open则相反。
- 如果指定了非阻塞标志,只读open立即返回。只写open则出错返回-1,如果没有进程为读而打开FIFO,则其error置ENXIO。
- read.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(){
char buf[128] = {0};
int nread;
int fd = open("./file",O_RDONLY);
if(fd != 0){
printf("open success!!!\n");
}
while(1){
nread = read(fd,buf,128);
printf("read %d byte\n",nread);
sleep(1);
}
close(fd);
return 0;
}
write.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<errno.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd;
int nwrite;
char buf[128] = "1232154874554654";
if((mkfifo("./file",0600) == -1)&&(errno != EEXIST)){
printf("mkfifo failuer\n");
perror("why");
}
fd = open("./file",O_WRONLY);
if(fd != -1){
printf("open success!!!\n");
}
while(1){
nwrite = write(fd,buf,128);
// printf("write %d byte\n",nwrite);
sleep(1);
}
close(fd)
return 0;
}
消息队列
消息队列,是消息的链接表,存放在内核中,一个消息队列有一个标识符来标识
4.1特点
1.消息队列,是面向记录的,其中的消息具有特定的格式和特定的优先级
2.独立于发送和接收进程,进程终止,其中的内容和消息队列不会删除
3.可以随机查询,不一定要先进先出次序读取,也可以按消息类型读取
4.2消息队列API
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
- //创建或打开消息队列。成功返回队列ID,失败返回-1
- int msgget(key_t key, int msgflg);
- //添加消息,成功返回0,失败返回-1
- int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
- //读取消息,成功返回数据长度,失败返回-1
- ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
- //控制消息队列,成功返回0,失败返回-1
- int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 消息队列结构体:
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
1.key:键值,
2.msgsnd 和 msgrcv 中的 msgflag:标志符,写0,默认为读不到东西阻塞
3.msgget 中的 msgflag:一般为IPC_CREAT |0777,代表可读写执行,没有则创建
4.3 key键值的获取和消息队列的移除
1.ftok()获取键值
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
- 第一个参数:指定已存在的文件名,一般选择当前目录“.”
- 第二个参数ID:指子序列号,int型,但只是用8bit(0——255)
- 也可以将文件的索引节点号取出,再加上子序列号,得到键值key(Linux下:ls -i查看索引节点号(ls -ai查看所有节点))
2.利用msgctl()移除队列,cmd传参,指针设为NULL就好。
##4.4实践
1.msgSnd.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<stdio.h>
#include<string.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main(){
key_t key;
key = ftok(".",'x');
printf("key = %x\n",key);
struct msgbuf readbuf;
struct msgbuf sendbuf = {888,"msg from msgSnd"};
//1.打开/创建消息队列 :int msgget(key_t key, int msgflg);
int msgId = msgget(key, IPC_CREAT|0777);
if(msgId == -1){
printf("get que failuer\n");
}
//2.发送消息:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgsnd(msgId, &sendbuf, strlen(sendbuf.mtext), 0);
printf("msgsnd:%s\n",sendbuf.mtext);
//3.接收消息: size_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),998,0);
printf("msgget: %s\n",readbuf.mtext);
//4.移除队列 IPC_RMID int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgctl(msgId, IPC_RMID, NULL);
return 0;
}
2.msgGet.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<stdio.h>
#include<string.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main(){
key_t key;
key = ftok(".",'x');
printf("key = %x\n",key);
struct msgbuf readbuf;
struct msgbuf sendbuf = {998,"msg from msgGet"};
//1.打开/创建消息队列 :int msgget(key_t key, int msgflg);
int msgId = msgget(key, IPC_CREAT|0777);
if(msgId == -1){
printf("get que failuer\n");
}
//2.接收消息: size_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0);
printf("msgget: %s\n",readbuf.mtext);
//3.发送消息:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgsnd(msgId, &sendbuf, strlen(sendbuf.mtext), 0);
printf("msgsnd:%s\n",sendbuf.mtext);
//4.移除队列 IPC_RMID int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgctl(msgId, IPC_RMID, NULL);
return 0;
}
五.共享内存
5.1解释
- 内存中开辟的一段物理空间,这个空间,所有的进程都可以拿到地址,进行操作。
5.2共享内存通信
- ipcs -m:查看系统中有哪些共享内存
- ipcrm -m + id号:删除共享内存
5.3 API
#include <sys/ipc.h>
#include <sys/shm.h>
1.//创建或获取一个共享内存,成功返回共享内存id,失败返回-1;size大小与M对齐
2. int shmget(key_t key, size_t size, int shmflg);
3.//连接共享内存到当前地址空间,成功返回共享内存指针,失败返回-1;第二,三个参数一般写0;代表系统为共享内存自动安排系统空间和共享内存可读可写
4. void *shmat(int shmid, const void *shmaddr, int shmflg);
5.// 断开与共享内存的连接,成功返回0,失败返回-1
6. int shmdt(const void *shmaddr);
7. //控制共享内存的相关信息,成功返回0,失败返回-1
8. int shmctl(int shmid, int cmd, struct shmid_ds *buf);
##5.4实践
#include <sys/ipc.h>
#include <sys/shm.h>
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
int shmid;
char *shmaddr;
key_t key = ftok(".",1);
//创建或获取一个共享内存,成功返回共享内存id,失败返回-1
shmid = shmget(key, 1024*4, IPC_CREAT|0666);
if(shmid == -1){
printf("shmget failuer\n");
exit(-1);
}
//连接共享内存到当前地址空间,成功返回共享内存指针,失败返回-1;第二,三个参数一般写0
shmaddr = shmat(shmid, 0, 0);
strcpy(shmaddr,"曽铁江🐮🍺");
//sleep(5);//等待另一端读取,并写入
printf("%s\n",shmaddr);
// 断开与共享内存的连接,成功返回0,失败返回-1
shmdt(shmaddr);
//控制共享内存的相关信息,成功返回0,失败返回-1
shmctl(shmid, IPC_RMID, 0);
return 0;
}
信号
6.1信号概述
- 信号实际上是是软中断,为Linux提供了一种处理异步事件的一种方式。
- 信号的名字和编号,名字都以SIG开头通过kill -l 查看,信号的编号是从1开始的,没有0,信号定义在signal.h中头文件中
6.2信号处理的方式(忽略,捕捉和默认动作)
- 忽略:大多数可以用这种方式处理,除了SIGKILL和SIGSTOP,因为它们向内核与超级用户提供的进程终止的可靠方法,不能忽略。
- 捕捉信号:实际上用中断处理函数,当信号产生时,内核调用函数,实现信号处理
- 默认动作:对应于每个信号,系统都有默认的处理方式。可以使用man 7 signal 来查看
- 可以使用“kill -信号编号/信号名字 进程id ” 手动杀死一个进程,如:kill -9 4222
- 信号处理函数:
1.入门版:signal
2.高级版:sigaction
- 信号发送函数:
1.入门版:kill
2.高级版:sigqueue
6.3信号编程
6.3.1 kill 和 signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
- sigget.c
#include<stdio.h>
#include <signal.h>
void sighandler(int signum){
switch(signum){
case 9:
printf("SIGKILL\n");
break;
case 2:
printf("SIGINT\n");
break;
}
printf("never quit\n");
}
int main(){
signal(SIGINT,sighandler);
signal(SIGKILL, sighandler);
while(1);
return 0;
}
- sigsnd.c
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
int main(int argc,char **argv){
int signum;//信号的编号
int pid;//目标进程的pid
char cmd[128] = {0};
signum =atoi(argv[1]);//注意:atoi 将字符转换为int型
pid =atoi(argv[2]);
sprintf(cmd,"kill -%d %d",signum,pid);//构造system()所需的参数
// kill(pid,signum);
system(cmd);//如果不用system,也可以直接用kill发送信号
return 0;
}
- 补充:可以使用SIG_IGN,忽略某个信号(除SIGKILL和 SIGSTOP),例如:signal(SIGINT,SIG_IGNA),将信号SIGINT忽略了
6.3.2 sigqueue 和 sigaction
6.3.2.1 sigqueue()
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
- pid:发给谁
- sig:信号编码
- value:要发送的消息
6.3.2.2 sigaction()
-原型
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
- signum:信号编码
- act:结构体指针,指向如下结构体
struct sigaction {
void (*sa_handler)(int); //信号处理程序,不接受额外数据,与signal的参数相同,一般默认设为SIG_IGN忽略
void (*sa_sigaction)(int, siginfo_t *, void *);//信号处理程序(函数),接收额外数据
sigset_t sa_mask;//阻塞关键字的信号集,默认为阻塞
int sa_flags;//影响接收数据的行为,SA_SIGINFO表示能够接收数据,需要手动设置
void (*sa_restorer)(void);//不用于应用程序,不用管
};
sa_sigaction:
参数int:num,用于接收signum
参数siginfo_t*:结构体
参数void*:一个指针,当指针为空的时候表示无数据,非空表示有数据,用于判断
signfo_t:
内容1:pid,谁发的
内容2:si_int,整型数
内容3:si_value,也是一个含数据的联合体,支持int和char*数据
..........(具体man 手册查看)
- oldact:结构体指针,用来备份原来的信号操作,一般我们不关心备份的话,写NULL就可以了
6.3.2.3实战
- sigactionpro.c
#include<signal.h>
#include<stdio.h>
void handler(int signum, siginfo_t *info, void *context ){
printf("get signum = %d\n",signum);
if(context != NULL){
printf("get data = %d\n",info->si_int);
printf("get data = %d\n",info->si_value.sival_int);
}
}
int main(){
int signum = 2;
struct sigaction act;
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
sigaction(signum,&act,NULL);
while(1);
return 0;
}
- sigqueuepro.c
#include<signal.h>
#include<stdio.h>
#include <stdlib.h>
/*union sigval {
int sival_int;
void *sival_ptr;
};*/
int main(int argc,char **argv){
int signum = atoi(argv[1]);
pid_t pid = atoi(argv[2]);
union sigval value;
value.sival_int = 123456789;
sigqueue(pid,signum, value);
return 0;
}
七. 信号量
信号量和 IPC 的结构不同,它是一个计数器。用于实现进程间的同步和互斥,而不是存储进程间的通信数据
7.1信号量概述
- 特点:
- 信号量用于进程间的同步,传递数据就要结合共享内存
- 多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。
- 信号量基于系统的PV操作(拿锁和放锁)。
- 每次PV操作可以加任意正整数
- 支持信号量组
7.2信号量编程
Linux信号量函数都是在信号量组上进行操作,而不是在单一的二值(0/1)信号量操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
1.//创建和获取一个信号量组,成功返回信号量集ID,失败返回-1
2. int semget(key_t key, int nsems, int semflg);
3. //对信号量进行操作,改变信号量的值,成功返回0,失败返回-1
4. int semop(int semid, struct sembuf *sops, size_t nsops);
5. //控制信号量的相关信息,第二个参数为:第几个信号量(0开始)
6. int semctl(int semid, int semnum, int cmd, ...);
- 当semctl有四个参数时,第四个参数为联合体:
union semun {
int val; /* Value for SETVAL 信号量的值*/
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
- 实战
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include<stdio.h>
#include <unistd.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void pgetkey(int semid){
struct sembuf sops;
sops.sem_num = 0;//第几位信号量,注意,编号从零开始
sops.sem_op = -1;//操作+1/-1
sops.sem_flg =SEM_UNDO;//标志符IPC_NOWAIT/SEM_UNDO
semop(semid,&sops,1);//sops是一个结构体数组,这里只有一个结构体,所以nsops = 1(第三个参数)
printf("get key\n");
}
void vputbackkey(int semid){
struct sembuf sops;
sops.sem_num = 0;//第几位信号量
sops.sem_op = 1;//操作+1/-1
sops.sem_flg =SEM_UNDO;//标志符IPC_NOWAIT/SEM_UNDO
semop(semid,&sops,1);//sops是一个结构体数组,这里只有一个结构体,所以nsops = 1(第三个参数)
printf("put back key\n");
}
int main(){
int pid;
key_t key;
key = ftok(".",2);
int semid;
union semun initsem;//联合体,根据semctl第三个参数选择,类型要自己定义
initsem.val = 0;//开始是无锁的状态
semid = semget(key,1, IPC_CREAT|0666);//信号量的个数为1
semctl(semid,0,SETVAL,initsem);//0第几个信号量
pid = fork();
if(pid >0){
pgetkey(semid);
printf("this is father\n");
vputbackkey(semid);
semctl(semid,0,IPC_RMID);//销毁第零个信号量
}else if(pid == 0){
// pgetkey(semid);
printf("this is child\n");
vputbackkey(semid);
}else{
printf("fork error\n");
}
return 0;
}