我这里要讲的并不是IPC中的消息队列,我要讲的是在进程内部实现自定义的消息队列,让各个线程的消息来推动整个进程的运动。进程间的消息队列用于进程与进程之间的通信,而我将要实现的进程内的消息队列是用于有序妥当处理来自于各个线程请求,避免一窝蜂的请求而导致消息的异常丢失。想想socket编程里的listen函数吧,里面要设置一个队列长度的参数,其实来自网络的请求已经排成一个请求队列了,只是这个队列是系统帮我们做好了,我们看不到而已。如果系统不帮我们做这个等待队列的话,那就需要我们程序员在应用层实现了。
四、构造消息生产者
五、跑起来看看

![]()
进程内的消息队列实现并不难,总的来说有以下几点:
二、构造循环队列
三、构造消息处理者
- 自定义消息结构,并构造队列
- 一个线程负责依次从消息队列中取出消息,并处理该消息
- 多个线程产生事件,并将消息放进消息队列,等待处理
长话短说,我们开始动手吧!
一、定义消息结构
先贴代码再解释:
typedef struct Msg_Hdr_s
{
uint32 msg_type;
uint32 msg_len;
uint32 msg_src;
uint32 msg_dst;
}Msg_Hdr_t;
typedef struct Msg_s
{
Msg_Hdr_t hdr;
uint8 data[100];
} Msg_t;
下面是我设计的消息格式内容的解释:
- msg_type:标记消息类型,当消息接收者看到该msg_type后就知道他要干什么事了
- msg_len:消息长度,待扩展,暂时没用到(以后会扩展为变长消息)
- msg_src:消息的源地址,即消息的发起者
- msg_dst:消息的目的地,即消息的接受者
- data[100]:消息除去消息头外可以携带的信息量,定义为100字节
由该消息数据结构可以知道,这个消息是定长的,当然也可以实现为变长消息,但现在暂不实现,今天先把定长消息实现了,以后再完善变长消息。
队列可以由链表实现,也可以由数组实现,这里就使用数组实现的循环链表作为我们消息队列的队列模型。
typedef struct Queue_s
{
int head;
int rear;
sem_t sem;
Msg_t data[QUEUE_SIZE];
}Queue_t;
int MsgQueueInit(Queue_t* Q)
{
if(!Q)
{
printf("Invalid Queue!\n");
return -1;
}
Q->rear = 0;
Q->head = 0;
sem_init(&Q->sem, 0, 1);
return 0;
}
int MsgDeQueue(Queue_t* Q, Msg_t* msg)
{
if(!Q)
{
printf("Invalid Queue!\n");
return -1;
}
if(Q->rear == Q->head) //only one consumer,no need to lock head
{
printf("Empty Queue!\n");
return -1;
}
memcpy(msg, &(Q->data[Q->head]), sizeof(Msg_t));
Q->head = (Q->head+1)%QUEUE_SIZE;
return 0;
}
int MsgEnQueue(Queue_t* Q, Msg_t* msg)
{
if(Q->head == (Q->rear+1)%QUEUE_SIZE)
{
printf("Full Queue!\n");
return -1;
}
sem_wait(&Q->sem);
memcpy(&(Q->data[Q->rear]), msg, sizeof(Msg_t));
Q->rear = (Q->rear+1)%QUEUE_SIZE;
sem_post(&Q->sem);
return 0;
}
循环队列的实现想必大家都比较熟悉,但这里需要提示的几点是:
- 队列中应加入信号量或锁来保证进队时的互斥访问,因为多个消息可能同时进队,互相覆盖其队列节点
- 这里的信号量仅用于进队而没用于出队,理由是消息处理者只有一个,不存在互斥的情形
if(pthread_create(&handler_thread_id, NULL, (void*)msg_handler, NULL))
{
printf("create handler thread fail!\n");
return -1;
}
void msg_printer(Msg_t* msg)
{
if(!msg)
{
return;
}
printf("%s: I have recieved a message!\n", __FUNCTION__);
printf("%s: msgtype:%d msg_src:%d dst:%d\n\n",__FUNCTION__,msg->hdr.msg_type,msg->hdr.msg_src,msg->hdr.msg_dst);
}
void msg_handler()
{
sleep(5); //let's wait 5s when starts
while(1)
{
Msg_t msg;
memset(&msg, 0 ,sizeof(Msg_t));
int res = MsgDeQueue((Queue_t*)&MsgQueue, &msg);
if(res != 0)
{
sleep(10);
continue;
}
msg_printer(&msg);
sleep(1);
}
}
我在进程里create了一个线程作为消息处理者(handler)来处理消息队列的消息,甘进入该线程时先等个5秒钟来让生产者往队列里丢些消息,然后再开始消息处理。当队列没消息可取时,就休息十秒,再去取消息。
这里的消息处理很简单,我只是简单地将受到的消息打印一下,证明受到的消息正是其他线程发给我的。当然,你也可以在这里扩展功能,根据受到的消息类型进一步决定该做什么事。比如:
enum MSG_TYPE
{
GO_HOME,
GO_TO_BED,
GO_TO_LUNCH,
GO_TO_CINAMA,
GO_TO_SCHOOL,
GO_DATEING,
GO_TO_WORK,//6
};
void handler()
{
switch(msgtype)
{
case GO_HOME: go_home(); break;
case GO_TO_BED: go_to_bed(); break;
.......
}
}
这里的handler就是一个简单的状态机了,根据给定的消息类型(事件)去做特定的事,推动状态机的转动。
if(pthread_create(&thread1_id, NULL, (void*)msg_sender1, NULL))
{
printf("create thread1 fail!\n");
return -1;
}
if(pthread_create(&thread2_id, NULL, (void*)msg_sender2, NULL))
{
printf("create thread2 fail!\n");
return -1;
}
if(pthread_create(&thread3_id, NULL, (void*)msg_sender3, NULL))
{
printf("create thread3 fail!\n");
return -1;
}
void msg_sender1()
{
int i = 0;
while(1)
{
if(i > 10)
{
i = 0;
}
Msg_t msg;
msg.hdr.msg_type = i++;
msg.hdr.msg_src = THREAD1;
msg.hdr.msg_dst = HANDLER;
MsgEnQueue((Queue_t*)&MsgQueue, &msg);
printf("%s: Thread1 send a message!\n",__FUNCTION__);
sleep(1);
}
}
void msg_sender2()
{
int i = 0;
while(1)
{
if(i > 10)
{
i = 0;
}
Msg_t msg;
msg.hdr.msg_type = i++;
msg.hdr.msg_src = THREAD2;
msg.hdr.msg_dst = HANDLER;
MsgEnQueue((Queue_t*)&MsgQueue, &msg);
printf("%s: Thread2 send a message!\n",__FUNCTION__);
sleep(1);
}
}
void msg_sender3()
{
int i = 0;
while(1)
{
if(i > 10)
{
i = 0;
}
Msg_t msg;
msg.hdr.msg_type = i++;
msg.hdr.msg_src = THREAD3;
msg.hdr.msg_dst = HANDLER;
MsgEnQueue((Queue_t*)&MsgQueue, &msg);
printf("%s: Thread3 send a message!\n",__FUNCTION__);
sleep(1);
}
}
这里我create了三个线程来模拟消息生产者,每个生产者每隔1秒往消息队列里写消息。
先贴完整的代码:
msg_queue.c:
#include
#include
#include
#include
#include
#include "msg_def.h" Queue_t MsgQueue; int main(int argc, char* argv[]) { int ret; pthread_t thread1_id; pthread_t thread2_id; pthread_t thread3_id; pthread_t handler_thread_id; ret = MsgQueueInit((Queue_t*)&MsgQueue); if(ret != 0) { return -1; } if(pthread_create(&handler_thread_id, NULL, (void*)msg_handler, NULL)) { printf("create handler thread fail!\n"); return -1; } if(pthread_create(&thread1_id, NULL, (void*)msg_sender1, NULL)) { printf("create thread1 fail!\n"); return -1; } if(pthread_create(&thread2_id, NULL, (void*)msg_sender2, NULL)) { printf("create thread2 fail!\n"); return -1; } if(pthread_create(&thread3_id, NULL, (void*)msg_sender3, NULL)) { printf("create thread3 fail!\n"); return -1; } while(1) { sleep(1); } return 0; } int MsgQueueInit(Queue_t* Q) { if(!Q) { printf("Invalid Queue!\n"); return -1; } Q->rear = 0; Q->head = 0; sem_init(&Q->sem, 0, 1); return 0; } int MsgDeQueue(Queue_t* Q, Msg_t* msg) { if(!Q) { printf("Invalid Queue!\n"); return -1; } if(Q->rear == Q->head) //only one cosumer,no need to lock head { printf("Empty Queue!\n"); return -1; } memcpy(msg, &(Q->data[Q->head]), sizeof(Msg_t)); Q->head = (Q->head+1)%QUEUE_SIZE; return 0; } int MsgEnQueue(Queue_t* Q, Msg_t* msg) { if(Q->head == (Q->rear+1)%QUEUE_SIZE) { printf("Full Queue!\n"); return -1; } sem_wait(&Q->sem); memcpy(&(Q->data[Q->rear]), msg, sizeof(Msg_t)); Q->rear = (Q->rear+1)%QUEUE_SIZE; sem_post(&Q->sem); return 0; } void msg_printer(Msg_t* msg) { if(!msg) { return; } printf("%s: I have recieved a message!\n", __FUNCTION__); printf("%s: msgtype:%d msg_src:%d dst:%d\n\n",__FUNCTION__,msg->hdr.msg_type,msg->hdr.msg_src,msg->hdr.msg_dst); } int msg_send() { Msg_t msg; msg.hdr.msg_type = GO_HOME; msg.hdr.msg_src = THREAD1; msg.hdr.msg_dst = HANDLER; return MsgEnQueue((Queue_t*)&MsgQueue, &msg); } void msg_handler() { sleep(5); //let's wait 5s when starts while(1) { Msg_t msg; memset(&msg, 0 ,sizeof(Msg_t)); int res = MsgDeQueue((Queue_t*)&MsgQueue, &msg); if(res != 0) { sleep(10); continue; } msg_printer(&msg); sleep(1); } } void msg_sender1() { int i = 0; while(1) { if(i > 10) { i = 0; } Msg_t msg; msg.hdr.msg_type = i++; msg.hdr.msg_src = THREAD1; msg.hdr.msg_dst = HANDLER; MsgEnQueue((Queue_t*)&MsgQueue, &msg); printf("%s: Thread1 send a message!\n",__FUNCTION__); sleep(1); } } void msg_sender2() { int i = 0; while(1) { if(i > 10) { i = 0; } Msg_t msg; msg.hdr.msg_type = i++; msg.hdr.msg_src = THREAD2; msg.hdr.msg_dst = HANDLER; MsgEnQueue((Queue_t*)&MsgQueue, &msg); printf("%s: Thread2 send a message!\n",__FUNCTION__); sleep(1); } } void msg_sender3() { int i = 0; while(1) { if(i > 10) { i = 0; } Msg_t msg; msg.hdr.msg_type = i++; msg.hdr.msg_src = THREAD3; msg.hdr.msg_dst = HANDLER; MsgEnQueue((Queue_t*)&MsgQueue, &msg); printf("%s: Thread3 send a message!\n",__FUNCTION__); sleep(1); } }
msg_def.h:
#include
#include
#include
typedef unsigned char uint8;
typedef unsigned short unit16;
typedef unsigned int uint32;
#define QUEUE_SIZE 1000
typedef struct Msg_Hdr_s
{
uint32 msg_type;
uint32 msg_len;
uint32 msg_src;
uint32 msg_dst;
}Msg_Hdr_t;
typedef struct Msg_s
{
Msg_Hdr_t hdr;
uint8 data[100];
} Msg_t;
typedef struct Queue_s
{
int head;
int rear;
sem_t sem;
Msg_t data[QUEUE_SIZE];
}Queue_t;
typedef struct Queue_s QueueNode;
enum MSG_TYPE
{
GO_HOME,
GO_TO_BED,
GO_TO_LUNCH,
GO_TO_CINAMA,
GO_TO_SCHOOL,
GO_DATEING,
GO_TO_WORK,//6
};
enum SRC_ADDR
{
THREAD1,
THREAD2,
THREAD3,
HANDLER,
};
int MsgQueueInit(Queue_t* Q);
int MsgDeQueue(Queue_t* Q, Msg_t* msg);
int MsgEnQueue(Queue_t* Q, Msg_t* msg);
void msg_handler();
void msg_sender1();
void msg_sender2();
void msg_sender3();
void msg_printer(Msg_t* msg);
int msg_send();
看看跑起来的现象:
Finish!
现在这套进程内的消息队列的架构在实际工程中非常实用,其实很多工程都需要这种基于事件推动的思想来保证每条请求都可以有条不絮地执行,所以这个框架也是有用武之地的,尤其配合状态机非常适合!