消息隊列就是一個消息的鏈表。可以把消息看作一個記錄,具有特定的格式以及特定的優先級。對消息隊列有寫權限的進程可以向消息隊列中按照一定的規則添加新消息;對消息隊列有讀權限的進程則可以從消息隊列中讀走消息。消息隊列是隨內核持續的,只有內核重啓或人工刪除時,該消息隊列纔會被刪除。
對於系統中的每個消息隊列,內核維護一個定義在 sys/msg.h 頭文件中的信息結構。
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* first message on queue,unused */
struct msg *msg_last; /* last message in queue,unused */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
我們可以將內核中的某個特定的消息隊列畫爲一個消息鏈表,如圖假設有一個具有三個消息的隊列,消息長度分別爲1字節,2字節和3字節,而且這些消息就是以這樣的順序寫入該隊列的。再假設這三個消息的類型分別100,200,300.
system V 消息隊列運用的一系列函數有如下:
msgget 函數
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int msgget(key_t key,int flag)
返回值:若成功,返回消息隊列 ID;若出錯,返回-1
參數說明:
- Key:是基於指定的key產生的,而key即可以是ftok的返回值,也可以是常值IPC_PRIVATE
- Flag:oflag是讀寫權限值的組合
msgsnd函數
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int msgsnd(int msqid,const void *prt,size_t size,int flag)
返回值:若成功,返回0;若出錯,返回-1
參數說明:
- msqid:消息隊列的隊列 ID
- prt:指向消息結構的指針。
- size:消息的字節數,不要以 null 結尾
- flag:
- IPC_NOWAIT: 若消息並沒有立即發送而調用進程會立即返回
- 0:msgsnd 調用阻塞直到條件滿足爲止
ptr是一個結構指針,該結構具有如下模板,它定義在 sys/msg.h 中,如下所示:
struct msgbuf{
long mtype;//消息類型
char mtext[1];//消息正文
}
msgrcv函數
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int msgrcv(int msgid,struct msgbuf *msgp,int size,long msgtype,int flag)
返回值:若成功,返回0;若出錯,返回-1
參數說明:
- msqid:消息隊列的隊列 ID
- msgp:消息緩衝區
- size:消息的字節數,不要以 null 結尾
Msgtype:
- 0:接收消息隊列中第一個消息
- 大於 0:接收消息隊列中第一個類型爲 msgtyp 的消息
- 小於 0:接收消息隊列中第一個類型值不小於 msgtyp 絕對值且類型值又最小的消息
flag:
- MSG_NOERROR:若返回的消息比 size 字節多,則消息就會截短到size 字節,且不通知消息發送進程
- IPC_NOWAIT: 若消息並沒有立即發送而調用進程會立即返回
- 0:msgsnd 調用阻塞直到條件滿足爲止
msgctl 函數
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf )
返回值:若成功,返回0;若出錯,返回-1
參數說明:
- msqid:消息隊列的隊列 ID
cmd:
- IPC_STAT:讀取消息隊列的數據結構 msqid_ds,並將其存儲在buf 指定的地址中
- IPC_SET:設置消息隊列的數據結構 msqid_ds 中的 ipc_perm 元素的值。這個值取自 buf 參數
- IPC_RMID:從系統內核中移走消息隊列
Buf:消息隊列緩衝區
示例程序:
/* message_queues.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define BUFF 1024
//msg_type說明:
struct msg_st
{
long int msg_type;
char text[BUFF];
};
//子進程發送消息,父進程接收消息
int main ()
{
pid_t pid;
struct msg_st data;
char buffer[BUFF];
key_t key;
int qid,len;
//系統建立IPC通訊 (消息隊列、信號量和共享內存) 時必須指定一個ID值。通常情況下,該id值通過ftok函數得到。
//根據不同的路徑和關鍵表示產生標準的 key
if((key=ftok(".",1))== -1)
{
perror("ftok");
exit(-1);
}
/*創建消息隊列*/
if((qid = msgget(key,IPC_CREAT|0666))== -1)
{
perror("msgget");
exit(-1);
}
printf("opened queue %d\n",qid);
if ((pid = fork()) < 0)
{
perror("fork");
exit(-1);
}
else if (pid == 0) //子進程
{
//輸入數據
printf("Enter some text: ");
fgets(buffer, BUFF, stdin);
data.msg_type = 2; //注意 !發送的信息的類型爲2
strcpy(data.text, buffer);
len = strlen(data.text);
//向隊列發送數據
if(msgsnd(qid, (void*)&data, len, 0) == -1)
{
perror("msgsnd");
exit(-1);
}
sleep(2);
return 0;
}
else //父進程
{
//讀取消息隊列,沒讀取到數據的時候會阻塞
//第四個參數 0:接收消息隊列中第一個消息;大於 0:接收消息隊列中第一個類型爲 msgtyp 的消息
if(msgrcv(qid,&data,BUFF,2,0)<0)
{
perror("msgrcv");
exit(-1);
}
printf("recv message from child:%s\n",(&data)->text);
printf("Waiting for the child process to exit\n");
//等待子進程退出
waitpid(pid,NULL,0);
printf("remove the Message Queues\n");
/*從系統內核中移除該消息隊列。*/
if((msgctl(qid,IPC_RMID,NULL))<0)
{
perror("msgctl");
exit(1);
}
printf("The child process has exited,now exit the father process\n");
exit(0);
}
return 0;
}
實驗結果:
ubuntu:~/test/process_test$ gcc message_queues.c -o message_queues
ubuntu:~/test/process_test$ ./message_queues
opened queue 163840
Enter some text: 1354
recv message from child:1354
Waiting for the child process to exit
remove the Message Queues
The child process has exited,now exit the father process
以上程序,先是調用了msgget函數創建消息隊列,key是基於ftok函數產生的。然後fork出子進程,在子進程中調用msgsnd函數往消息隊列中添加數據,父進程中調用msgrcv函數從消息隊列中獲取數據後,就調用msgctl函數,把該消息隊列從系統內核中移除。這裏需要注意消息類型,msgrcv函數的第四個參數,爲0表示接收消息隊列中第一個消息,大於0表示接收消息隊列中第一個類型爲 msgtyp 的消息。所以,接收消息和發送消息的類型一定要對應。
我們這裏把msgsnd函數消息類型改成data.msg_type = 1,其他不變,實驗一下父進程能不能接收到消息。
實驗結果:
ubuntu:~/test/process_test$ gcc message_queues.c -o message_queues
ubuntu:~/test/process_test$ ./message_queues
opened queue 196608
Enter some text: 123456
實驗結果是,父進程一直阻塞在msgrcv函數,父進程讀不到消息類型爲1的消息,因爲父進程把消息類型指定爲2。這個時候再用ipcs -q 查看,結果如下:
ubuntu:~/test/process_test$ ipcs -q
------ Message Queues --------
key msqid perms used-bytes messages
0x011f24c7 196608 666 7 1
因爲是手動結束的進程,父進程並沒有調用msgctl函數把該消息隊列從系統內核中移除。所以,我們創建的消息隊列並沒有銷燬。這也證實了我們前面所說的“消息隊列是隨內核持續的,只有內核重啓或人工刪除時,該消息隊列纔會被刪除”。
把msgsnd函數消息類型改成data.msg_type = 2,把程序編譯後執行一遍。執行ipcs命令,結果如下,說明消息隊列已被移除。
ubuntu:~/test/process_test$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
接下來,我們把發送消息和接收消息各寫成一個程序,然後在不同的終端試驗一下。
/* msgsend.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define BUFF 1024
struct msg_st
{
long int msg_type;
char text[BUFF];
};
int main ()
{
struct msg_st data;
char buffer[BUFF];
key_t key;
int qid,len;
//系統建立IPC通訊 (消息隊列、信號量和共享內存) 時必須指定一個ID值。通常情況下,該id值通過ftok函數得到。
//根據不同的路徑和關鍵表示產生標準的 key
if((key=ftok(".",1))== -1)
{
perror("ftok");
exit(-1);
}
printf("key = 0x%x\n",key);
/*創建消息隊列*/
if((qid = msgget(key,IPC_CREAT|0666))== -1)
{
perror("msgget");
exit(-1);
}
printf("opened queue %d\n",qid);
while(1)
{
//輸入數據
printf("Enter some text: ");
fgets(buffer, BUFF, stdin);
data.msg_type = 2; //注意 !發送的信息的類型爲2
strcpy(data.text, buffer);
len = strlen(data.text);
//向隊列發送數據
if(msgsnd(qid, (void*)&data, len, 0) == -1)
{
perror("msgsnd");
exit(-1);
}
}
return 0;
}
/* msgrcv.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define BUFF 1024
//msg_type說明:
struct msg_st
{
long int msg_type;
char text[BUFF];
};
int main ()
{
struct msg_st data;
char buffer[BUFF];
key_t key;
int qid,len,i;
//系統建立IPC通訊 (消息隊列、信號量和共享內存) 時必須指定一個ID值。通常情況下,該id值通過ftok函數得到。
//根據不同的路徑和關鍵表示產生標準的 key
if((key=ftok(".",1))== -1)
{
perror("ftok");
exit(-1);
}
printf("key = 0x%x\n",key);
/*創建消息隊列*/
if((qid = msgget(key,IPC_CREAT|0666))== -1)
{
perror("msgget");
exit(-1);
}
printf("opened queue %d\n",qid);
for(i = 0;i < 3;i++)
{
//讀取消息隊列,沒讀取到數據的時候會阻塞
//第四個參數 0:接收消息隊列中第一個消息;大於 0:接收消息隊列中第一個類型爲 msgtyp 的消息
if(msgrcv(qid,&data,BUFF,2,0)<0)
{
perror("msgrcv");
exit(-1);
}
printf("recv message from child:%s\n",(&data)->text);
}
/*從系統內核中移除該消息隊列。*/
if((msgctl(qid,IPC_RMID,NULL))<0)
{
perror("msgctl");
exit(1);
}
return 0;
}
A終端,執行程序,根據提示依次輸入”123”,”456”,”789”。
ubuntu:~/test/process_test$ ./msgsend
key = 0x11f24c7
opened queue 229376
Enter some text: 123
Enter some text: 456
Enter some text: 789
Enter some text: 9
msgsnd: Invalid argument
B終端,每當A終端輸入字符串,B終端就顯示相應的字符串。
ubuntu:~/test/process_test$ ./msgrcv
key = 0x11f24c7
opened queue 229376
recv message from child:123
recv message from child:456
recv message from child:789
C終端
A終端輸入123後執行ipcs -q 命令
ubuntu:~$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x011f24c7 229376 chenting 666 0 0
程序結束後執行ipcs -q 命令
ubuntu:~$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
實驗表明,在非親緣關係進程中,消息隊列也同樣可以操作。情況和示例程序 message_queues.c 分析的差不多。