課程作業:用 System V 消息隊列實現 3 進程 × 2 線程的群聊功能。發送進程寫入,接收進程讀取。問題:**所有進程讀取完畢後,如何清空隊列,避免舊消息干擾下一輪?**
「消息隊列中的消息不會消失」理解錯誤。實際上:
msgrcv 成功讀取後,**消息自動從隊列中移除**// 接收線程循環
while (running) {
struct msgbuf msg;
// 類型 0 表示讀取任意類型第一條消息
ssize_t ret = msgrcv(msgid, &msg, MAX_MSG_SIZE, 0, IPC_NOWAIT);
if (ret == -1) {
if (errno == ENOMSG) {
// 隊列已空,短暫休眠避免忙等
usleep(10000);
continue;
}
perror("msgrcv");
break;
}
process_message(&msg); // 處理消息
}
// 循環結束時,隊列已被讀空
// 專用清空函數
void clear_message_queue(int msgid) {
struct msgbuf msg;
// 非阻塞循環讀取直到隊列為空
while (msgrcv(msgid, &msg, MAX_MSG_SIZE, 0, IPC_NOWAIT) != -1) {
// 丟棄消息,僅為清空
}
// 此時 errno 應為 ENOMSG,表示隊列已空
if (errno != ENOMSG) perror("clear queue");
}
// 或者直接刪除隊列(慎用,影響其他進程)
msgctl(msgid, IPC_RMID, NULL); // 徹底刪除隊列
// 只清空特定類型(如上一輪聊天消息類型 = 1)
void clear_old_round(int msgid, long old_type) {
struct msgbuf msg;
while (msgrcv(msgid, &msg, MAX_MSG_SIZE, old_type, IPC_NOWAIT) != -1) {
// 丟棄舊類型消息
}
}
// 消息結構
struct chat_msg {
long mtype; // 消息類型:進程 ID 或輪次編號
int sender_pid; // 發送者 PID
int round; // 輪次號
char content[256]; // 內容
};
// 發送邏輯
void send_message(int msgid, int round, const char *content) {
struct chat_msg msg = {0};
msg.mtype = getpid(); // 或 round 號
msg.sender_pid = getpid();
msg.round = round;
strncpy(msg.content, content, sizeof(msg.content)-1);
msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0);
}
// 接收邏輯(每輪結束自動清空)
void *receiver_thread(void *arg) {
int msgid = *(int*)arg;
int current_round = 0;
while (running) {
struct chat_msg msg;
// 只讀取當前輪次消息
if (msgrcv(msgid, &msg, sizeof(msg)-sizeof(long), current_round, 0) > 0) {
printf("[PID %d] Round %d: %s\n", msg.sender_pid, msg.round, msg.content);
} else if (errno == EINTR) continue;
else break; // 隊列被刪除或出錯
}
return NULL;
}
單純消息隊列**無法原子性實現「所有人讀完再清空」**,需輔助同步:
Linux 消息隊列**讀即刪**,無需額外清空操作。群聊模型中:
msgrcv(..., IPC_NOWAIT) 讀空隊列mtype 標識輪次,僅讀取當前輪次理解「讀取即移除」這一核心語義,消息隊列編程將豁然開朗。