本文将采用多线程的方式,实现:
A终端发送消息,B终端接收;B终端发送消息,A终端接收。
同时A、B终端都可以连续发送消息,且被另一个终端实时接收
任意一个终端输入quit(不区分大小写),可同时退出两个进程
首先,先创建消息包结构体变量
//消息包结构体
struct msgbuf
{
long mtype; //消息类型,必须大于0
char mtext[128]; //消息内容,可以自行制定类型、大小
};
因为消息队列不同于管道,它可以通过修改消息类型,做到单个消息队列互不干扰的处理两个进程通信。例如:A进程用1类型消息发送给B,用2类型接收B的消息;而B进程用1类型接收消息,用2类型发送消息。所以,我只需要创建一个消息队列即可。
//计算key值
key_t key = ftok("./", 1); //key值由路径和自己指定的非0值唯一生成
if(key < 0)
{
perror("ftok");
return -1;
}
printf("key = %#x\n", key);
//创建队列
msqid = msgget(key, IPC_CREAT | 0755); //队列1
if(msqid < 0)
{
perror("msgget");
return -1;
}
printf("msqid = %d\n", msqid);
为了不让主线程看着太过臃肿,我创建两个分支线程分别用来处理读和写(这样看着对称,个人习惯罢了)。
/*******************************************************************/
//创建线程
if(pthread_create(&tid1, NULL, my_read, NULL) != 0) //读线程
{
perror("pthread_create");
return -1;
}
if(pthread_create(&tid2, NULL, my_write, NULL) != 0) //写线程
{
perror("pthread_create");
return -1;
}
/******************************************************************/
因为题目要求读到quit就退出程序。假设A写入了quit,此时,B会读取到quit,所以我们必须要留一个read线程退出的语句,而A读取到输入了quit后,可以直接申请read线程退出,read线程退出后自己再退出。所以我在主线程只阻塞了read线程。
pthread_join(tid1, NULL);//阻塞读线程
//读线程退出后,删除消息队列
msgctl(msqid, IPC_RMID, NULL);
为了让write线程可以申请read线程退出,要把read线程号(tie1)传参给write线程,同理,消息队列的名字也要传参给read和write线程,这就要用到结构体了,我图省事,把它都定义成了全局变量。
pthread_t tid1, tid2;
int msqid;
以A进程为例。A进程的read线程用于接收B进程的write线程发送的消息,我让A进程读取1类型,让B进程读取2类型,以阻塞的方式读取消息,当它接受的B进程的write线程发送来的quit,退出循环,即退出线程,此时,主线程接收到read线程的“尸体”,结束堵塞,同时删除队列结束程序(删库跑路(雾))。
在具体运行代码时,A进程的read线程读取消息并输出后,会立刻进入循环,并阻塞在读取消息的代码处。但是与此同时,由于A进程的write线程早就运行过 printf("请输入消息内容>>>"); 代码段,所以会导致输出信息后下一行会是一片空白(输出语句后的\n)。所以,出于美观性的考虑,我们可以在read线程输出后的下一行,补充一句 printf("请输入消息内容>>>"); 语句,代码运行会相对更加美观
void *my_read(void *arg)
{ //接收消息
struct msgbuf rcv; //用相同类型接收消息
ssize_t res = 0;
while(1)
{
//阻塞的方式读取消息队列中的消息,msgtyp==0,先进先出
res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 1, 0); //读取1号类型
if(res <0)
{
perror("msgrcv");
return NULL;
}
if(strcasecmp(rcv.mtext, "quit") == 0)
{
fprintf(stderr,"程序已退出\n");
break;
}
fprintf(stderr,"\n消息包大小:%ld [%ld信道消息: %s]\n",res, rcv.mtype, rcv.mtext);
//美观性考虑,当进程A重复输入时,若无此语句,会空出一行
fprintf(stderr,"请输入消息内容>>>");
}
}
A进程的write线程,发送2类型消息,以阻塞方式发送。当输入quit时,直接用pthread_cancel(tid1); 函数申请read线程退出,同时,直接break或进入死循环,不然会再输出一句printf("请输入消息内容>>>");语句,影响美观性
void *my_write(void *arg)
{
//发送消息包
struct msgbuf snd;
snd.mtype = 2; //消息类型2号
while(1)
{
printf("请输入消息内容>>>");
fgets(snd.mtext, sizeof(snd.mtext), stdin);
snd.mtext[strlen(snd.mtext)-1] = 0;
//阻塞方式发送,消息队列满了,阻塞
if(msgsnd( msqid, &snd, sizeof(snd.mtext), 0) < 0)
{// 消息队列id, 消息包首地址, 消息包内消息内容大小 阻塞方式发送
perror("msgsnd");
return NULL;
}
if(strcasecmp(snd.mtext, "quit") == 0)
{
pthread_cancel(tid1);
printf("程序已退出\n");
while(1); //防止重复输出“请输入消息内容”
}
}
}
最后,当你使用Ubuntu的gcc编译器进行编译时,要注意,需要在最后链接pthread
gcc MAG_queue_A.c -o A -pthread
// MAG_queue_A.c 原文件名
// -o 重命名标识符
// A 重命名后的可执行文件名
// -pthread 链接库
//讲道理,这种东西我觉得不需要我赘述
以下是A进程完整代码,注释掉的部分为双消息队列的方案
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<stdlib.h>
#include<sys/msg.h>
#include<pthread.h>
#define MSG_EXCEPT 020000
int msqid/*,msqid_2*/;
pthread_t tid1, tid2;
//消息包结构体
struct msgbuf
{
long mtype; //消息类型,必须大于0
char mtext[128]; //消息内容,可以自行制定
};
void *my_read(void *arg)
{ //接收消息
struct msgbuf rcv; //用相同类型接收消息
ssize_t res = 0;
while(1)
{
//阻塞的方式读取消息队列中的消息,msgtyp==0,先进先出
res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 1, 0); //读取1号类型
if(res <0)
{
perror("msgrcv");
return NULL;
}
if(strcasecmp(rcv.mtext, "quit") == 0)
{
fprintf(stderr,"程序已退出\n");
break;
}
fprintf(stderr,"\n消息包大小:%ld [%ld信道消息: %s]\n",res, rcv.mtype, rcv.mtext);
//美观性考虑,当进程A重复输入时,若无此语句,会空出一行
fprintf(stderr,"请输入消息内容>>>");
}
}
void *my_write(void *arg)
{
//发送消息包
struct msgbuf snd;
snd.mtype = 2; //消息类型2号
while(1)
{
printf("请输入消息内容>>>");
fgets(snd.mtext, sizeof(snd.mtext), stdin);
snd.mtext[strlen(snd.mtext)-1] = 0;
//阻塞方式发送,消息队列满了,阻塞
if(msgsnd( msqid, &snd, sizeof(snd.mtext), 0) < 0)
{// 消息队列id, 消息包首地址, 消息包内消息内容大小 阻塞方式发送
perror("msgsnd");
return NULL;
}
if(strcasecmp(snd.mtext, "quit") == 0)
{
pthread_cancel(tid1);
printf("程序已退出\n");
while(1); //防止重复输出“请输入消息内容”
}
}
}
int main(int argc, const char *argv[])
{
//计算key值********************************************************
/* key_t key_2 = ftok("./", 2); //key值由路径和自己指定的非0值唯一生成
if(key_2 < 0)
{
perror("ftok");
return -1;
}
printf("key_2 = %#x\n", key_2);
*/
key_t key = ftok("./", 1); //key
if(key < 0)
{
perror("ftok");
return -1;
}
printf("%#x\n", key);
/**********************************************************************/
//创建队列
/* msqid_2 = msgget(key_2, IPC_CREAT | 0755); //二号队列
if(msqid_2 < 0)
{
perror("msgget");
return -1;
}
printf("msqid_2 = %d\n", msqid_2);
*/
msqid = msgget(key, IPC_CREAT | 0755); //一号队列
if(msqid < 0)
{
perror("msgget");
return -1;
}
printf("msqid = %d\n", msqid);
/***********************************************************************/
//创建线程
if(pthread_create(&tid1, NULL, my_read, NULL) != 0) //读
{
perror("pthread_create");
return -1;
}
if(pthread_create(&tid2, NULL, my_write, NULL) != 0) //写
{
perror("pthread_create");
return -1;
}
/************************************************************************/
pthread_join(tid1, NULL);//阻塞读
// pthread_join(tid2, NULL);
//删除消息队列
msgctl(msqid, IPC_RMID, NULL);
// msgctl(msqid_2, IPC_RMID, NULL);
// system("ipcs -q");
return 0;
}
B进程完整代码
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<stdlib.h>
#include<sys/msg.h>
#include<pthread.h>
#define MSG_EXCEPT 020000
int msqid/*,msqid_2*/;
pthread_t tid1, tid2;
//消息包结构体
struct msgbuf
{
long mtype; //消息类型,必须大于0
char mtext[128]; //消息内容,可以自行制定
};
void *my_read(void *arg)
{
//接收消息
struct msgbuf rcv; //用相同类型接收消息
ssize_t res = 0;
while(1)
{
//阻塞的方式读取消息队列中的消息,msgtyp==0,先进先出
res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 2, 0); //用2号类型读取
if(res <0)
{
perror("msgrcv");
return NULL;
}
if(strcasecmp(rcv.mtext, "quit") == 0)
{
fprintf(stderr,"程序已退出\n");
break;
}
fprintf(stderr,"\n消息包大小:%ld [%ld信道消息: %s]\n",res, rcv.mtype, rcv.mtext);
//美观性考虑,当进程A重复输入时,若无此语句,会空出一行
fprintf(stderr,"请输入消息内容>>>");
}
}
void *my_write(void *arg)
{
//发送消息包
struct msgbuf snd;
snd.mtype = 1; //消息类型1号
while(1)
{
printf("请输入消息内容>>>");
fgets(snd.mtext, sizeof(snd.mtext), stdin);
snd.mtext[strlen(snd.mtext)-1] = 0;
//阻塞方式发送,消息队列满了,阻塞
if(msgsnd( msqid, &snd, sizeof(snd.mtext), 0) < 0)
{// 消息队列id, 消息包首地址, 消息包内消息内容大小 阻塞方式发送
perror("msgsnd");
return NULL;
}
if(strcasecmp(snd.mtext, "quit") == 0)
{
pthread_cancel(tid1);
printf("程序已退出\n");
while(1); //防止重复输出“请输入消息内容”
}
}
}
int main(int argc, const char *argv[])
{
//计算key值**********************************************************
key_t key = ftok("./", 1); //key值由路径和自己指定的非0值唯一生成
if(key < 0)
{
perror("ftok");
return -1;
}
printf("key = %#x\n", key);
/* key_t key_2 = ftok("./", 2); //key_2
if(key_2 < 0)
{
perror("ftok");
return -1;
}
printf("%#x\n", key_2);
*/ /*******************************************************************/
//创建队列
msqid = msgget(key, IPC_CREAT | 0755); //一号队列
if(msqid < 0)
{
perror("msgget");
return -1;
}
printf("msqid = %d\n", msqid);
/* msqid_2 = msgget(key_2, IPC_CREAT | 0755); //二号队列
if(msqid_2 < 0)
{
perror("msgget");
return -1;
}
printf("msqid_2 = %d\n", msqid_2);
*/ /*******************************************************************/
//创建线程
if(pthread_create(&tid1, NULL, my_read, NULL) != 0) //读
{
perror("pthread_create");
return -1;
}
if(pthread_create(&tid2, NULL, my_write, NULL) != 0) //写
{
perror("pthread_create");
return -1;
}
/**********************************************************************/
pthread_join(tid1, NULL);//阻塞读
// pthread_join(tid2, NULL);
//删除消息队列
// msgctl(msqid_2, IPC_RMID, NULL);
msgctl(msqid, IPC_RMID, NULL);
// system("ipcs -q");
return 0;
}