我们创建三个文件,一个recieve.c和pa.c,pb.c 。用recieve.c来监听pa.c和pb.c发送的消息:
recieve.c代码:
cpp
#include<t_stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <errno.h>
#define MSG_TYPE 0
#define MSG_TYPE_A 10
#define MSG_TYPE_B 11//自定义消息类型
int main(int argc,char * argv[]){
//定义自己的消息数据结构体
struct msgmbuf
{
long mtype;
char mtext[100];
int pid;
};
//创建key值
key_t key= ftok(".",1);
if(key==-1)E_MSG("ftok",-1);
//创建消息队列, 如果内核不存在队列就创建
int msg= msgget(key,IPC_CREAT|0666);
if(msg==-1)E_MSG("msgget",-1);
//定义一个消息变量用来接收消息
struct msgmbuf m_msg;
//定义一个整数用来获取监听信息的字节数
int retval;
//开始监听
while(1){
retval= msgrcv(msg,&m_msg,sizeof(m_msg),MSG_TYPE, IPC_NOWAIT);//IPC_NOWAIT,不等待,即不阻塞
if((retval == -1))
{
if (errno == ENOMSG) { // 没有消息
continue; // 跳过本次循环,继续监听
} else {
perror("msgrcv error");
return -1;
}
}
else if(retval>0)
{
printf("[PID:%d], msg type:%ld, msg:%s\n",m_msg.pid,m_msg.mtype,m_msg.mtext);
if(strncmp(m_msg.mtext, "exit", 4) == 0)//可以指定长度为4时strlen(msg.mtext)才进行比较,仅仅对exit生效
{
break;
}
}
}
//删除消息队列
msgctl(msg,IPC_RMID, NULL);
return 0;
}
pa.c
cpp
#include <t_stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#define MSG_TYPE_A 10
struct msgmbuf
{
long mtype; /* message type, must be > 0 */
char mtext[100]; /* message data */
int pid;
};
int main(void){
//创建key值
key_t key= ftok(".",1);
if(key==-1)E_MSG("ftok",-1);
//创建消息队列, 如果内核不存在队列就创建
int msg_id = msgget(key, 0);
if (msg_id == -1) {
// 如果获取失败,尝试创建消息队列
msg_id = msgget(key, IPC_CREAT | 0x0666);
if (msg_id == -1) E_MSG("msgget", -1);
}
//定义一个消息变量用来接收消息
struct msgmbuf msg;
msg.pid=getpid();//获取当前进程pid
msg.mtype=MSG_TYPE_A;
//开始循环写入
while(1){
//写入数据
fgets(msg.mtext,sizeof(msg.mtext),stdin);
//发送数据
msgsnd(msg_id,&msg,sizeof(msg),0);
if(strncmp(msg.mtext, "exit", 4) == 0)
{
break;
}
}
msgctl(msg_id, IPC_RMID, NULL);
return 0;
}
理解:
监听代码(接收者)
创建Key值:
ftok
函数用来生成一个唯一的key值,这个值基于一个文件(在这里是当前目录.
)和一个项目标识符(这里是1
)。- 这个key值用于标识消息队列,确保发送者和接收者能够访问同一个队列。
创建消息队列:
msgget
函数用于创建一个新的消息队列或获取一个已经存在的消息队列的标识符。 -IPC_CREAT标志告诉系统如果队列不存在则创建它,0666
是队列的权限。定义消息结构体:
msgmbuf
结构体定义了消息的类型(mtype
)和消息内容(mtext
)以及发送进程的PID。消息接收循环:
msgrcv
函数用于从消息队列中接收消息。IPC_NOWAIT
标志表示如果队列中没有消息,msgrcv
不会阻塞,而是立即返回。- 如果
msgrcv
返回-1
,并且错误码是ENOMSG
,表示队列中没有消息,程序继续循环监听。- 如果接收到消息,程序会打印出消息内容和类型。
- 如果消息内容是"exit",程序会跳出循环。
删除消息队列:
msgctl
函数用于控制消息队列,IPC_RMID
标志表示删除消息队列。发送消息代码(发送者)
创建Key值:
- 和接收者一样,发送者也需要创建一个key值来访问同一个消息队列。
获取或创建消息队列:
- 发送者尝试获取一个已经存在的消息队列的标识符。
- 如果获取失败,它会尝试创建一个新的消息队列。
定义消息结构体并初始化:
msgmbuf
结构体用于存储发送的消息。- 进程ID(PID)被存储在消息结构体中,以便接收者知道哪个进程发送了消息。
消息发送循环:
- 程序从标准输入读取字符串,并将其存储在消息结构体的
mtext
字段中。msgsnd
函数用于将消息发送到消息队列。- 如果输入的是"exit",程序会跳出循环。
删除消息队列:
- 和接收者一样,发送者在结束前也会删除消息队列。
为什么两个代码都要创建Key值和消息队列?
创建Key值:
- 在Unix系统中,每个IPC结构(如消息队列、共享内存、信号量)都有一个与之关联的唯一键(key)。这个键是由
ftok
生成的,它确保了不同的进程能够通过同一个键访问同一个IPC结构。- 发送者和接收者需要使用相同的key值来确保它们能够互相通信。
创建消息队列:
- 消息队列是IPC的一种形式,它允许一个或多个进程写入和读取数据。
- 在Unix中,消息队列是由操作系统管理的,因此需要使用
msgget
来创建或访问一个已经存在的消息队列。- 发送者和接收者都需要访问消息队列,因此它们都需要创建或获取队列的标识符。
这两个程序通过消息队列进行通信。发送者将消息放入队列,接收者从队列中读取消息。它们通过使用相同的key值来确保它们可以访问同一个消息队列。
扩展:我当时一直不明白两个文件为什么都要创建key值,以及为什么创建相同key值,以及如何实现利用消息队列进行通信:
在Unix-like系统中,ftok
函数用于从一个路径名和一个项目标识符(通常是整数)生成一个key值。这个key值用于进程间通信(IPC),比如消息队列、共享内存和信号量。
ftok
函数的定义如下:
复制
key_t ftok(const char *pathname, int proj_id);
pathname
是一个指向现有路径名的指针,通常是文件路径。proj_id
是一个整数,用于进一步区分不同的key值。
重要的是要理解,ftok
生成的key值不仅取决于pathname
,还取决于proj_id
。这意味着即使是在相同的路径下,只要proj_id
不同,生成的key值也会不同。相反,即使是在不同的路径下,只要pathname
和proj_id
相同,生成的key值也会相同。
因此,一个文件路径并不限制只能创建一个key值。实际上,你可以使用相同的文件路径和不同的proj_id
来生成多个不同的key值,每个key值可以用于创建或访问不同的IPC资源。
例如:
复制
key_t key1 = ftok("/path/to/file", 1);
key_t key2 = ftok("/path/to/file", 2);
在上面的例子中,key1
和key2
将会是不同的,即使它们是基于相同的文件路径生成的。
总结来说,ftok
生成的key值是唯一的,只要pathname
和proj_id
的组合是唯一的。这意味着在不同的文件路径下可以生成相同的key值,只要proj_id
相同;同时,在相同的文件路径下也可以生成不同的key值,只要proj_id
不同。