本篇文章将介绍无名管道,有名管道,信号,共享内存和信号灯集,消息队列。
传统通信:无名管道,有名管道,信号
system v ipc 对象:共享内存,信号灯集,消息队列
话不多说。
目录
一.无名管道
1.1特点:
1:管道是进程间通信的常用方式,而无名管道(有的地方也叫匿名管道)仅适用于父子进程之间的通信。
2:半双工通信:可读可写,有专门的读端和写端,但同一时刻只能读或者只能写。
3:可以看成一种特殊的文件,但其实不是文件,可以通过文件IO进行操作。(read) 4: 只可以操作普通文件(-)。
5:打开管道,默认打开两个文件描述符 fd[0]读管道,fd[1]写管道。
1.2注意事项:
1:管道是有存储容量的,最大能容纳64k的数据,关于这一点的验证,大家可以向管道中写入64K个字符,看会不会读堵塞。也就是说,如果管道写满了,再向管道中写数据会写堵塞。当然,也不是拿出一个数据后就可以继续向内输入1个数据,只要管道被写满,则需拿出4k(4096)个数据后才能继续向内输入。
2:当管道中没有数据时,会读堵塞。如果管道的写端被关闭,再读管道时会立即返回(也就是程序结束)。
3:只有读端存在的时候,向管道内写数据才有意义,如果光写不读,拿这些数据存在管道里又有什么意义呢?如果你真的把读端关闭了,还向管道内写入数据,那么会导致管道破裂,同时你的程序会向系统发送SIGPIPE的信号。
1.3相关操作(函数):
1: int pipe(int fd [2])
参数:文件描述符数组,fd[0]读管道,fd[1]写管道。
功能:创建无名管道。
返回值:成功:返回0;
失败返回-1;
2:文件IO的相关函数
ssize_t read(in fd ,void * buf,size_t count);
功能:读取文件中的数据
参数 :fd 文件描述符
把读取的数据存放在buf指向的数组里
读取的数据的个数count
返回值:成功读取的数据的个数
ssize_t write(int fd ,void *buf size_t count)
功能:将数组里的数据写入文件
参数:同上,略微不同的就是将读取二字换成写入
返回值:成功写入的数据的个数
1.4 无名管道实现通信
我这里的实现方法:无名管道实现父进程向管道内写入,子进程从管道内拿数据并打印。
#include <stdio.h>
#include <unistd.h> //pipe fork
#include <string.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
int fd[2] = {0}; // 创建文件描述符数组,fd[0]读管道,fd[1]写管道,初始化为0
char buf[32] = ""; // 用于存放数据
int s;
if (pipe(fd) < 0) // 要保证父子进程通过同一个管道通信,所以先创建管道,在创建子进程
{
perror("create pipe err"); // 创建管道失败会返回错误码,打印错误码
return -1;
}
pid_t pid = frok();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
while (1)
{
s = read(fd[0], buf, 32); // 管道有自己特殊的同步机制,所以子进程和父进程输入输出会同步
if (strcmp(buf, "quit") == 0)
break;
printf("%s\n", buf); // 打印时要注意/n换行刷新缓存区,将内容及时打印出来
}
}
else
{
while (1)
{
scanf("%s", buf); // 循环输入这里会堵塞等待输入
write(fd[1], buf, 32);//先写入管道再判断的原因是如果管道道中没有数据子进程读会堵塞
if (strcmp(buf, "quit") == 0)
break;
}
wait(NULL);//等在子进程结束 回收子进程
}
close(fd[0]); // 关闭管道读端
close(fd[1]); // 关闭管道写端
return 0;
}
1.5思考管道命令是有名管道还是无名管道
答案是无名管道,验证方法:在终端上使用命令alarm(5)|alarm(10)|alarm(3)
alarm函数呢,定时程序结束,第一次使用会返回0,第二次使用会返回上一次alarm调用剩余的秒数。我们使用这个命令之后然后在终端输入ps aux命令可以查看当前终端正在运行的进程,可以产看pid和ppid,通过查看可以看到他们之间的父子关系。
其实呢,不利用命令通过推理也可知道是无名管道:
个人认为是无名管道。
第一:命令中用到管道提示符是把前一个命令或者程序的正确的输出作为管道提示符后的命令或者程序的输入,也就是管道符只能处理前一个命令的标准输出,而不能处理标准错误,并且管道符右边命令能够接收标准输入。这就要求进程之间能传递正确的信息,而无名管道在父子进程之间完成通信,父子进程的结果又给到父子进程中运行下一个结果,不会出现错误输入的问题。
第二:管道符的用法格式:命令|命令与父进程先执行出结果创建管道再创建子进程(fork),又把结果给到子进程的过程类似。子进程拷贝父进程地址空间,获取管道描述符,已达到和父进程一起操作同一个管道的目的。
第三:如果所有的命令或者进程都能拿到管道里的内容也不利于数据的安全,只有父子进程能够操作同一个管道时,就保证了数据的安全。
第四:有名管道需要创建并打开一个特殊的文件FIFO来进行文件IO的读写操作,而管道符命令不需要创建并打开这样一个文件。
二.有名管道
2.1特点:
1:不再局限于父子进程,可以是任一进程之间通信
2:半双工通信:可读可写,有专门的读端和写端,但同一时刻只能读或者只能写。
3:作为特殊的文件,可以在文件中显示,并且通过文件IO进行操作,但是内容不是存放在这个管道中,内存中,所以这个文件里是没有数据的。
4:遵循先进先出,不支持lseek操作
2.2注意事项:
1.只读模式,读堵塞,直到另一个进程打开写操作;
2.只写模式,写操作,直到另一个进程打开读操作。
3.可读可写,读在最后读堵塞。
2.3相关操作(函数)
int mkfifo(const char *pathname,mode_t mode)
功能:创建有名管道
参数:路径名/权限 文件的(umask码:八进制,例如0666,就是有读写权限)
返回值:成功0/失败-1并设置errno
注意:错误的处理方式,有一种错误是已存在,如果存在那么就直接打开就可以了。因为两个进程有一个先创建,另一个进程只需要打开这个管道就可以了。
2.4有名管道实现通信
这里的举例还是发送和打印数据:这里有两个进程,一个循环向管道内输入,另一个循环输出,这里再强调一次,管道通信有自己的特有的同步机制。
#include<stdio.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main(int argc, char const *argv[])
{ /*两个进程循环输入/打印 完成cp命令 */
//这里是读取源文件放入管道中的进程
if(mkfifo("./fifo",0666)<0)//创建有名管道,并判断是否成功
{ if(errno==EEXIST)
{
//管道存在不做处理,直接打开
}
else
{
perror("create fifo err");
return -1;
}
}
char buf[32]="";//用于存放数据
int fd=open("./fifo",O_WRONLY);//打开管道,这里设置管道写端只写
//如果可读可写,那么读端到最后会堵塞
int src=open(argv[1],O_RDONLY);//命令行传参打开想复制的文件,设置只读权限
int s;
while(1)
{
s=read(src,buf,32);//实际读到的字符个数,如果是0,那么表示读完
if(s==0)
break;
write(fd,buf,32);
}
close(fd);//关闭管道
close(src);//关闭源文件
return 0;
}
#include<stdio.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(int argc, char const *argv[])
{ /*两个进程循环输入/打印 完成cp命令 */
//这里是从管道中拿数据放进新文件的进程
if(mkfifo("./fifo",0666)<0)//创建有名管道,并判断是否成功
{ if(errno==EEXIST)
{
//管道存在不做处理,直接打开
}
else
{
perror("create fifo err");
return -1;
}
}
char buf[32]="";//用于存放数据
int fd=open("./fifo",O_RDONLY);//打开和输入相同的管道,这里设置管道写端只读
//如果可读可写,那么读端到最后会堵塞
int dist=open(argv[1],O_RDONLY|O_CREAT|O_TRUNC,0666);
//命令行传参打开想复制成的目标文件,设置只读权限,有则清空,无则创建并设置权限为0666
int s;
while(1)
{
s=read(fd,buf,32);//实际读到的字符个数,如果是0,那么表示读完管道内的内容
if(s==0)
break;
write(dist,buf,32);//向目标文件中写入
}
close(fd);
close(dist);
return 0;
}
2.5 有名管道和无名管道的对比(以上内容的整理)
三. 信号
3.1信号的概念
信号是在软件层次上对中断机制的模拟,是一种异步通信机制。同步就是按一定的顺序先后执行,异步就是无顺序的同时执行。
3.2信号的特点
当进程处于堵塞状态,信号就会被延迟传递,直到进程不再堵塞才会传递给进程
当进程未处于执行态,信号会被内核保存起来,直到进程恢复执行再传递给它。
信号可以直接进行用户空间进程和内核进程的交互,内核进程也可以通过信号来告诉用户空间进程系统发生了什么系统事件。
3.3常见信号种类
2)SIGINT:结束进程,对应快捷方式ctrl+c
3)SIGQUIT:退出信号,对应快捷方式ctrl+\
9)SIGKILL:结束进程,不能被忽略不能被捕捉
15)SIGTERM:结束终端进程,kill 使用时不加数字默认是此信号
17)SIGCHLD:子进程状态改变时给父进程发的信号
19)SIGSTOP:结束进程,不能被忽略不能被捕捉
20)SIGTSTP:暂停信号,对应快捷方式ctrl+z
26)SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程。
3.4相关操作(函数)
int kill(pid_t pid,int sig)
功能:向指定进程号的进程发送信号
参数:指定的进程号/信号
返回值:0/-1
int raise(int sig)
功能:给自己发送信号
参数:信号
返回值:0/-1
int pause()
功能:将进程挂起,如果想要进程收到信号后才继续下去的话可以挂起等待信号
sighandler_t signal(int signum,sighandler_t handler)
功能:信号处理函数
参数:signum:要处理的信号
handler:信号处理方式
SIG_IGN:忽略信号
SIG_DFL:执行默认操作
handler:捕捉信号 void handler(int sig){} //函数名可以自定义
返回值:成功:设置之前的信号处理方式
失败:-1
3.5信号通信的实现
用信号的知识实现司机和售票员问题。
1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)
2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
4)司机等待售票员下车,之后司机再下车。
先来分析一下题目:售票员要捕捉 SIGINT/SIGQUIT/SIGUSR1
忽略SIGTSTP
司机要捕捉SIGUSR1/SIGUSR2/SIGTSTP
忽略SIGINT/SIGQUIT
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include<sys/wait.h>
void seller(int sig)
{
if (sig == SIGINT) // 判断信号为SIGINT
kill(getppid(), SIGUSR1); // 向父进程发送SIGUSR1
if (sig == SIGQUIT)
kill(getppid(), SIGUSR2);
if (sig == SIGUSR1)
printf("please getoff the bus\n");
}
void driver(int sig)
{
if (sig == SIGUSR1) // 判断信号为SIGUSR1
printf("let's gogogo\n"); // 打印
if (sig == SIGUSR2)
printf("stop the bus\n");
if (sig == SIGQUIT)
kill(pid, SIGUSR1);
}
pid_t pid ;
int main(int argc, char const *argv[])
{
pid = fork();
if (pid < 0)
{
perror("create fork err");
return -1;
}
else if (pid == 0)
{
signal(SIGTSTP, SIG_IGN); // 收到sigtstp信号忽略
signal(SIGINT, seller); // 收到sigint信号,调用seller函数
pause();
signal(SIGQUIT, seller);
pause();
signal(SIGUSR1, seller);
pause(); // 为了避免未等到信号直接结束
}
else
{
signal(SIGINT, SIG_IGN); // 收到sigint信号忽略
signal(SIGQUIT, SIG_IGN);
signal(SIGUSR1,driver); // 收到sigint信号,调用seller函数
pause();
signal(SIGUSR2, driver);
pause();
signal(SIGTSTP, driver);
pause(); // 为了避免未等到信号直接结束
wait(NULL);//回收子进程
}
return 0;
}
四.共享内存
4.1特点
共享内存是最为高效的进程间通信机制。为了给多个进程之间交换信息,内核专门留出一片内存区,可以由需要访问的进程映射到自己的私有地址空间。进程可以直接读写这篇内存区,而不需要任何数据的拷贝,因而大大提高了效率。但是由于多个进程访问同一片内存区,所以需要设置相应的同步机制,也就是说共享内存没有同步机制。比如说设置标志位,互斥锁。
4.2共享内存的使用步骤
创建key值,key值是通过ftok函数创建的相对于系统来说的唯一值,通过key值创建的共享内存因为key值的缘故,可以确定多个进程访问的是同一个共享内存。当然key值也可指自己输入不使用函数。
创建/打开共享内存
int shmget(key_t key, size_t size, int shmflg);//参数key/想要创建的共享内存的大小单位字节/权限
//因为共享内存是ipc函数,所以权限有常用的IPC_CREAT创建/IPC_EXCL查错
//成功 shmid 共享内存号
//失败-1 并设置errno,这里注意如果共享内存已有,那么只需shmid=shmget(key,size,0666)就可以了
创建映射
void * p=(void *)shmat(shmid,NULL,0);//参数shmid/设置为null则系统自动分配映射关系/0可读可写
进程间对共享内存进行读写
关闭映射
shmdt(p)
删除共享内存
shmctl(shmid,IPC_RMID,NULL)//删除共享内存 参数shmid/删除操作/共享内存属性,null就是默认
4.3共享内存操作
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<errno.h>
#include<string.h>
typedef struct memory{
char data[256];
int flag;//设置标志位,模拟同步,其实不是同步
}*Memory;
int main(int argc, char const *argv[])
{ //key
/*两个进程一个进程向共享内存中输入数据,另一个进程从共享内存中读取数据,读到quit结束*/
/*这是一个向共享内存中输入数据的进程*/
__key_t key = ftok(".", 1);
//创建共享内存
int shmid =shmget(key,256,IPC_CREAT|IPC_EXCL|0666);
if(shmid<=0)
{ if(errno==EEXIST)//已存在,那么进程直接使用,以达到两个进程共用同一片共享内存的作用
shmid=shmget(key,256,0666);
else
{
perror("create err");
return -1;
}
}
Memory p=(Memory)shmat(shmid,NULL,0);//系统自动分配,可读可写
if(p==(Memory)-1)
{ perror("err");
return -1;
}
p->flag=0;//给标志位初始化为0
while(1)
{ if(p->flag==0)//标志位为0,向共享内存中输入
{ printf("please input someting into shared memory:");
fgets(p->data,100,stdin);
p->flag=1;//输入完后设置标志位为1,让另一个进程从共享内存中拿取数据
if(p->data[0]=='q'&&p->data[1]=='u'&&p->data[2]=='i'&&p->data[3]=='t')//strcmp(p->data,"quit\n")
break;
}
}
shmdt(p);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<errno.h>
#include<string.h>
typedef struct memory{
char data[256];
int flag;
}*Memory;`
int main(int argc, char const *argv[])
{ //key
/*这是一个从共享内存中读取数据的进程*/
__key_t key = ftok(".", 1);
//创建共享内存
int shmid =shmget(key,256,IPC_CREAT|IPC_EXCL|0666);//没有创建
if(shmid<=0)
{ if(errno==EEXIST)
shmid=shmget(key,256,0666);
else
{
perror("create err");
return -1;
}
}
Memory p=(Memory)shmat(shmid,NULL,0);
if(p==(Memory)-1)
{ perror("err");
return -1;
}
printf("******************\n");
while(1)
{ if(p->flag==1)//标志位1,读取数据
{ if(p->data[0]=='q'&&p->data[1]=='u'&&p->data[2]=='i'&&p->data[3]=='t')//strcmp(p->data,"quit\n")
break;
printf("message from shared memory is :");
printf("%s\n",p->data);
p->flag=0;//读取完之后置0
}
}
shmdt(p);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
4.4删除共享内存的命令
很多朋友在编写代码的时候经常出错,导致程序还没运行完,没有执行删除共享内存的操作,下次执行可能会报错,这里把命令行删除共享内存的方法告诉大家
1.ipcs -m 查看系统中存在的共享内存,找到权限为0666的共享内存号
2. ipcrm -m shmid 删除共享内存号为shmid的共享内存
五.信号灯集
5.1信号灯集相关信息
信号灯集也是信号量,它是不同进程间或一个给定进程内部不同线程间同步的机制;System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。而Posix信号灯指的是单个计数信号灯。
通过信号灯集实现共享内存的同步操作。
5.2信号灯集的操作(函数)
int semget(key_t key, int nsems, int semflg);
功能:创建/打开信号灯
参数:key:ftok产生的key值
nsems:信号灯集中包含的信号灯数目
semflg:信号灯集的访问权限,通常为IPC_CREAT |0666
返回值:成功:信号灯集ID
失败:-1
int semctl ( int semid, int semnum, int cmd…/*union semun arg*/);
功能:信号灯集合的控制(初始化/删除)
参数:semid:信号灯集ID
semnum: 要操作的集合中的信号灯编号
cmd:
GETVAL:获取信号灯的值,返回值是获得值
SETVAL:设置信号灯的值,需要用到第四个参数:共用体
IPC_RMID:从系统中删除信号灯集合
返回值:成功 0
失败 -1
int semop ( int semid, struct sembuf *opsptr, size_t nops);
功能:对信号灯集合中的信号量进行PV操作
参数:semid:信号灯集ID
opsptr:操作方式
nops: 要操作的信号灯的个数 1个
返回值:成功 :0
失败:-1
struct sembuf {
short sem_num; // 要操作的信号灯的编号
short sem_op; // 0 : 等待,直到信号灯的值变成0
// 1 : 释放资源,V操作
// -1 : 申请资源,P操作
short sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO
};
5.3信号灯集实现功能
还是老朋友:一个进程向共享内存中输入数据,另一个进程从共享内存中读取数据,直到quit结束。使用信号灯集实现,这里我有两种方法,
第一种方法:信号灯集中有一个信号,初始化为0。输入数据的进程在scanf处堵塞,直到用户输入数据,然后释放信号,另一个进程申请到信号,不再堵塞,判断/打印,循环往复。
第二种方法:一个信号灯集中两个信号,一个初始化为1,另一个初始化为0。输入进程先申请到初始化为1的信号,然后输入到共享内存,释放第二个信号,并判断。第二个进程申请到第二个信号,判断/打印,释放第一个信号。循环往复。直到判断到quit结束。
因为用的是信号灯集嘛 ,突出的就是一个信号的集合,所以这里展示第二种思路的代码
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/sem.h>
#include <sys/types.h>
#include<string.h>
typedef struct DATA
{
char data[100];
//struct sembuf buf[2];
} * sharedMemory;
union semlight {
int inivalue;
};
/*这里是信号灯集的用法举例演示*/
/*这是向共享内存中输入数据的进程*/
int main(int argc, char const *argv[])
{
__key_t key = ftok(".", '1');//创建key
if (key < 0)
{
perror("create key errr");
return -1;
}
int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);//创建共享内存
if (shmid <= 0)
{
if (errno == EEXIST)
shmid = shmget(key, 128, 0666);
else
{
perror("create shared memory err");
return -2;
}
}
sharedMemory p = (sharedMemory)shmat(shmid, NULL, 0);//char * 创建映射
/*这里也可以直接用char *p 直接fgets(p,32,stdin)也可写入共享内存*/
/*创建信号灯集 */
int semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if (semid <= 0)
{
if (errno == EEXIST)
semid = semget(key, 2, 0666);
else
{
perror("create semlight errr");
return -3;
}
}
else /*初始化信号灯集*/
{
union semlight sem;
sem.inivalue = 1;
semctl(semid, 0, SETVAL, sem);
sem.inivalue = 0;
semctl(semid, 1, SETVAL, sem);
}
printf("%d\n",semctl(semid,0,GETVAL));
printf("%d\n",semctl(semid,1,GETVAL));
/*pv操作需要借助sembuf结构体 */
/*填充结构体*/
struct sembuf buf[2];
buf[0].sem_num = 0;//0号信号 创建buf【0】就是第一个信号一一对应关系
buf[0].sem_flg = 0;//设置为0 意思是信号量大于零可申请,小于等于0会堵塞
buf[1].sem_num = 1;//1号信号,创建buf【1】就是第二个信号的一一对应关系
buf[1].sem_flg = 0;
//semop(semid, p->buf, 2);会造成阻塞
while (1)
{
buf[0].sem_op = -1;//输入进程先申请到第一个信号
semop(semid, &buf[0], 1);//执行pv操作
printf("please input:");
fgets(p->data, 32, stdin);
buf[1].sem_op = 1;//释放第二个信号
semop(semid, &buf[1], 1);//执行pv操作
if (!strcmp(p->data, "quit\n"))
break;
}
semctl(semid,0,IPC_RMID);
shmdt(p);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/sem.h>
#include <sys/types.h>
#include<string.h>
typedef struct DATA
{
char data[100];
} * sharedMemory;
union semlight {//设置共用体用来给信号灯集中的信号初始化
int inivalue;
};
/*这是信号灯集通信过程的输出进程*/
int main(int argc, char const *argv[])
{
__key_t key = ftok(".", '1');
if (key < 0)
{
perror("create key errr");
return -1;
}
int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid <= 0)
{
if (errno == EEXIST)
shmid = shmget(key, 128, 0666);
else
{
perror("create shared memory err");
return -2;
}
}
sharedMemory p = (sharedMemory)shmat(shmid, NULL, 0);
/*创建信号灯集 */
int semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if (semid <= 0)
{
if (errno == EEXIST)
semid = semget(key, 2, 0666);
else
{
perror("create semlight errr");
return -3;
}
}
else /*初始化信号灯集*/
{
union semlight sem;
sem.inivalue = 1;
semctl(semid, 0, SETVAL, sem);
sem.inivalue = 0;
semctl(semid, 1, SETVAL, sem);
}
printf("%d\n",semctl(semid,0,GETVAL));
printf("%d\n",semctl(semid,1,GETVAL));
/*pv操作需要借助sembuf结构体 */
struct sembuf buf[2];
buf[0].sem_num = 0;
//buf[0].sem_op=0;
buf[0].sem_flg = 0;
buf[1].sem_num = 1;
//buf[1].sem_op=0;
buf[1].sem_flg = 0;
//semop(semid, buf, 2);
while (1)
{
buf[1].sem_op = -1;//申请第二个信号
semop(semid, &buf[1], 1);//执行pv操作
if (!strcmp(p->data, "quit\n"))
break;
printf("message from shared Memory :%s",p->data);
buf[0].sem_op = 1;//释放第二个信号
semop(semid, &buf[0], 1);
}
semctl(semid,0,IPC_RMID);
shmdt(p);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
六.消息队列
6.1特点
消息队列是IPC对象的一种
消息队列由消息队列ID来唯一标识
消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
消息队列可以按照类型来发送(添加)/接收(读取)消息
6.2步骤
- 创建或打开消息队列msgget。
- 添加消息:按照类型把消息添加到已打开的消息队列末尾msgsnd。
- 读取消息:可以按照消息类型将消息从消息队列中读走msgrcv。
- 删除消息队列 msgctl。
6.3函数操作
int msgget(key_t key, int flag);
功能:创建或打开一个消息队列
参数: key值
flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666
返回值:成功:msgid
失败:-1
int msgsnd(int msqid, const void *msgp, size_t size, int flag);
功能:添加消息
参数:msqid:消息队列的ID
msgp:指向消息的指针。常用消息结构msgbuf如下:
struct msgbuf{
long mtype; //消息类型
char mtext[N]}; //消息正文
size:发送的消息正文的字节数
flag:IPC_NOWAIT消息没有发送完成函数也会立即返回
0:直到发送完成函数才返回
返回值:成功:0
失败:-1
使用:msgsnd(msgid, &msg,sizeof(msg)-sizeof(long), 0)
注意:消息结构除了第一个成员必须为long类型外,其他成员可以根据应用的需求自行定义。
The msgp argument is a pointer to a caller-defined structure
msgp 参数是指向调用方定义的结构的指针
The mtext field is an array (or other structure) whose size is specified by msgsz,a
nonnegative integer value.mtext 字段是一个数组(或其他结构),其大小由 msgsz(一个非负整数值)指定。
int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag);
功能:读取消息
参数:msgid:消息队列的ID
msgp:存放读取消息的空间
size:接受的消息正文的字节数
msgtype:0:接收消息队列中第一个消息。
大于0:接收消息队列中第一个类型为msgtyp的消息.
小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
flag:0:若无消息函数会一直阻塞
IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG
返回值:成功:接收到的消息的长度
失败:-1
6.4消息队列的进程间通信的实现
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<errno.h>
#include<stdio.h>
#include<string.h>
typedef struct mymsg{
long mytype;
char data[32];
}messageQueue;
int main(int argc, char const *argv[])
{
key_t key = ftok(".", '1');
if (key < 0)
{
perror("create key err");
return -1;
}
int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if (msgid <= 0)
{
if (errno == EEXIST)
msgid = msgget(key, 0666);
else
{
perror("create msgid err");
return -2;
}
}
printf("msgid=%d\n",msgid);
messageQueue msg;
msg.mytype=1;//设置消息队列的一种为类型1
strcpy(msg.data,"nice!");
msgsnd(msgid,&msg,sizeof(msg)-sizeof(long),0);//添加消息,直到发送完才结束
strcpy(msg.data,"book");
msgrcv(msgid,&msg,sizeof(msg)-sizeof(long),1,0);//读取消息队列中类型为1的消息,没有消息堵塞
printf("%s\n",msg.data);
return 0;
}