linux消息隊列
消息隊列是linux中進程間通信的方法之一。消息隊列是一個先進先出的隊列,對於同一個類型的隊列,消息是遵循先進先出的原則的。
消息隊列的實現很簡單,只有四個函數就解決了,下面介紹下如何實現消息隊列。
1 消息隊列的創建
函數原型如下:
int msgget (key_t __key, int __msgflg);
第一個參數是一個key,實際上就是一個長整型,通過這個Key唯一標識一個消息隊列。這個值可以是我們自己定義一個唯一的整數。也可以通過 ftok來獲取一個key。
第二個參數爲標識符,IPC_CREAT值,若沒有該隊列,則創建一個並返回新標識符;若已存在,則返回原標識符。
IPC_EXCL值,與IPC_CREAT一起使用。(用”|”連接),如果消息隊列不存在則創建之,否則產生一個錯誤並返回。
另外還要注意隊列的讀寫權限,例如要支持讀寫需要這樣寫IPC_CREAT|0666.
該函數成功返回消息隊列的id,失敗返回-1。
該函數返回的id唯一標識一個消息隊列。msgsnd 、msgrcv、msgctl都通過這個id來操作消息隊列。
ftok的函數原型爲
key_t ftok( const char * fname, int id );
fname 指定的已經存在的文件名或者目錄名(必須確保已經存在的文件名或者目錄。且不會被刪除)。該文件或者目錄不存在將返回-1。
id 可以是自己指定的一個整數。
該函數成功返回產生的key_t的值,失敗但會-1;
該函數,是將文件的索引節點號取出,前面加上子序號得到key_t的返回值。
如果文件被刪除再重新創建,那麼索引節點號就變了。用ftok創建的key就改變了,那麼就會造成進程不能訪問同一個隊列了。所以要確保該文件不會被刪除。
下面是示例代碼
int msgid;
key_t key = ftok(".", 20 );
msgid=msgget(key ,IPC_CREAT|0666);
if(msgid<0)
{
printf("create msg error");
}
2 發送消息:
函數原型如下:
int msgsnd (int __msqid, __const void *__msgp, size_t __msgsz, int __msgflg);
__msqid 通過msgget 返回的消息id
__msgp 指向消息緩衝區的指針。linux給出了一個消息的參考結構:
struct msgbuf {
long mtype; /* 消息類型,必須 > 0 */
char mtext[1]; /* 消息文本 */
};
對於消息的結構,我們可以不用上面的結構自己定義一個結構。但是第一個參數必須是消息的類型,必須要和參考的結構一樣,爲一個long型。而且值必須大於等於1 。
mtype後面的成員我們可以任意指定。例如
struct mymsg {
long mtype; /* 消息類型,必須 > 0 */
char mtext[100]; /* 消息文本 */
};
struct mymsg {
long mtype; /* 消息類型,必須 > 0 */
int item1:
char item2;
char itme3[10];
....
};
__msgsz: 發送消息的尺寸,也就是__msgp 指向的數據的尺寸。
__msgflg:爲IPC_NOWAIT表示不阻塞,也就是發送的時候如果消息隊列已滿,將直接返回-1。爲0,如果消息隊列滿將一直阻塞,直到函數可以向隊列寫數據爲止。
該函數成功返回0 失敗返回-1。
3 接收消息
函數原型如下:
int msgrcv (int __msqid, void *__msgp, size_t __msgsz, long int __msgtyp, int __msgflg);
__msqid :通過msgget 返回的消息id
__msgp :指向接收消息緩衝區的指針。
__msgsz:消息緩衝區的尺寸,也就是__msgp 指向的緩衝區的大小。
__msgtyp :當__msgtyp 爲0 時返回整個消息隊列中的第一個消息。
當__msgtyp 大於0時返回消息隊列中第一個消息類型等於__msgtyp 的消息。消息類型就是前面介紹的消息參考結構中的mtype 。
當__msgtyp 小於0時,返回消息隊列中第一個消息類型小於或者等於__msgtyp的絕對值 的第一個消息。
__msgflg:爲IPC_NOWAIT表示不阻塞,也就是如果消息隊列爲空,將直接返回-1並且將錯誤碼設置爲ENOMSG。爲0,如果消息隊列爲空,將一直阻塞直到消息隊列有數據爲止。
該函數成功返回實際讀取的數據長度。 失敗返回-1。
注意這個函數調用一次只會返回一條消息數據,即使指定__msgsz可以保存多條消息。
4 消息隊列的其他操作:
函數原型如下:
int msgctl (int __msqid, int __cmd, struct msqid_ds *__buf);
__msqid 通過msgget 返回的消息id。
__cmd :IPC_STAT 讀取消息隊列的數據結構msqid_ds,並將其存儲在__buf指定的地址中。
IPC_SET 設置消息隊列的數據結構msqid_ds中的ipc_perm元素的值。這個值取自__buf參數。
IPC_RMID 從系統內核中移走消息隊列。不論消息隊列中是否還有消息,都將溢出消息隊列。
該函數返回0 ,執行成功。
返回- 1,執行失敗:返回失敗是錯誤碼如下:
errno = EACCES (沒有讀的權限同時cmd 是IPC_STAT )
EFAULT (buf 指向的地址無效)
EIDRM (在讀取中隊列被刪除)
EINVAL (msgqid無效, 或者msgsz 小於0 )
EPERM (IPC_SET或者IPC_RMID 命令被使用,但調用程序沒有寫的權限)
注意:因爲消息隊列是多進程共享的,所以進程結束後系統並不會主動釋放消息隊列,所以我們在確保消息隊列不用的時候要調用msgctl 釋放消息隊列,以減少不必要的資源佔用。
下面我們看看示例代碼:
typedef struct
{
long mtype;
char mtext[20];
}my_msg;
int main(int argc, char** argv)
{
int isSend;
int index;
int msgid;
my_msg msg;
if(argc!=3)
{
printf("error para,exit\n");
return 0;
}
isSend = argv[1][0]-0x30;//第一個參數爲0表示進程接收消息,非0爲表示進程發送消息
index = argv[2][0]-0x30;//第二個參數表示要接收的消息的類型
if(isSend==0)
{
if(index>3||index<0)
{
printf("index error,exit\n");
return 0;
}
}
key_t key = ftok(".", 2 );
printf("key_t = %d\n",key);
msgid=msgget(key ,IPC_CREAT|0666);
if(msgid<0)
{
printf("create msg queue error\n");
return 0;
}
if(isSend==1)
{
struct timespec ts;
while(1)
{
clock_gettime(CLOCK_REALTIME, &ts);
msg.mtype=ts.tv_nsec%2+1;
sprintf(msg.mtext,"msg= %d",msg.mtype);
msgsnd(msgid, &msg, sizeof(my_msg), 0);
printf("send msg=%s\n",msg.mtext);
sleep(3);
}
}
else
{
int len ;
while(1)
{
len = msgrcv(msgid, (void *)&msg, sizeof(my_msg), index, 0);
if(len < 0)
{
printf("get msg error\n" );
}
else
{
printf("process %d get msg =[%s] type=%d\n" ,index ,msg.mtext,msg.mtype);
}
}
}
return 0;
}
例如我們編譯出來的可執行文件msgtest
我們分別在終端0運行 msgtest 1 0
在終端1運行 msgtest 0 1
在終端2運行 msgtest 0 2
在終端3運行 msgtest 0 3
我們可以看到在終端0打印 send msg n的時候 (n爲1-3)
對應的終端n 就會打印 process n get msg =[msg= n] type=n (n爲1-3)
消息隊列的實現很簡單,只有四個函數就解決了,下面介紹下如何實現消息隊列。
1 消息隊列的創建
函數原型如下:
int msgget (key_t __key, int __msgflg);
第一個參數是一個key,實際上就是一個長整型,通過這個Key唯一標識一個消息隊列。這個值可以是我們自己定義一個唯一的整數。也可以通過 ftok來獲取一個key。
第二個參數爲標識符,IPC_CREAT值,若沒有該隊列,則創建一個並返回新標識符;若已存在,則返回原標識符。
IPC_EXCL值,與IPC_CREAT一起使用。(用”|”連接),如果消息隊列不存在則創建之,否則產生一個錯誤並返回。
另外還要注意隊列的讀寫權限,例如要支持讀寫需要這樣寫IPC_CREAT|0666.
該函數成功返回消息隊列的id,失敗返回-1。
該函數返回的id唯一標識一個消息隊列。msgsnd 、msgrcv、msgctl都通過這個id來操作消息隊列。
ftok的函數原型爲
key_t ftok( const char * fname, int id );
fname 指定的已經存在的文件名或者目錄名(必須確保已經存在的文件名或者目錄。且不會被刪除)。該文件或者目錄不存在將返回-1。
id 可以是自己指定的一個整數。
該函數成功返回產生的key_t的值,失敗但會-1;
該函數,是將文件的索引節點號取出,前面加上子序號得到key_t的返回值。
如果文件被刪除再重新創建,那麼索引節點號就變了。用ftok創建的key就改變了,那麼就會造成進程不能訪問同一個隊列了。所以要確保該文件不會被刪除。
下面是示例代碼
int msgid;
key_t key = ftok(".", 20 );
msgid=msgget(key ,IPC_CREAT|0666);
if(msgid<0)
{
printf("create msg error");
}
2 發送消息:
函數原型如下:
int msgsnd (int __msqid, __const void *__msgp, size_t __msgsz, int __msgflg);
__msqid 通過msgget 返回的消息id
__msgp 指向消息緩衝區的指針。linux給出了一個消息的參考結構:
struct msgbuf {
long mtype; /* 消息類型,必須 > 0 */
char mtext[1]; /* 消息文本 */
};
對於消息的結構,我們可以不用上面的結構自己定義一個結構。但是第一個參數必須是消息的類型,必須要和參考的結構一樣,爲一個long型。而且值必須大於等於1 。
mtype後面的成員我們可以任意指定。例如
struct mymsg {
long mtype; /* 消息類型,必須 > 0 */
char mtext[100]; /* 消息文本 */
};
struct mymsg {
long mtype; /* 消息類型,必須 > 0 */
int item1:
char item2;
char itme3[10];
....
};
__msgsz: 發送消息的尺寸,也就是__msgp 指向的數據的尺寸。
__msgflg:爲IPC_NOWAIT表示不阻塞,也就是發送的時候如果消息隊列已滿,將直接返回-1。爲0,如果消息隊列滿將一直阻塞,直到函數可以向隊列寫數據爲止。
該函數成功返回0 失敗返回-1。
3 接收消息
函數原型如下:
int msgrcv (int __msqid, void *__msgp, size_t __msgsz, long int __msgtyp, int __msgflg);
__msqid :通過msgget 返回的消息id
__msgp :指向接收消息緩衝區的指針。
__msgsz:消息緩衝區的尺寸,也就是__msgp 指向的緩衝區的大小。
__msgtyp :當__msgtyp 爲0 時返回整個消息隊列中的第一個消息。
當__msgtyp 大於0時返回消息隊列中第一個消息類型等於__msgtyp 的消息。消息類型就是前面介紹的消息參考結構中的mtype 。
當__msgtyp 小於0時,返回消息隊列中第一個消息類型小於或者等於__msgtyp的絕對值 的第一個消息。
__msgflg:爲IPC_NOWAIT表示不阻塞,也就是如果消息隊列爲空,將直接返回-1並且將錯誤碼設置爲ENOMSG。爲0,如果消息隊列爲空,將一直阻塞直到消息隊列有數據爲止。
該函數成功返回實際讀取的數據長度。 失敗返回-1。
注意這個函數調用一次只會返回一條消息數據,即使指定__msgsz可以保存多條消息。
4 消息隊列的其他操作:
函數原型如下:
int msgctl (int __msqid, int __cmd, struct msqid_ds *__buf);
__msqid 通過msgget 返回的消息id。
__cmd :IPC_STAT 讀取消息隊列的數據結構msqid_ds,並將其存儲在__buf指定的地址中。
IPC_SET 設置消息隊列的數據結構msqid_ds中的ipc_perm元素的值。這個值取自__buf參數。
IPC_RMID 從系統內核中移走消息隊列。不論消息隊列中是否還有消息,都將溢出消息隊列。
該函數返回0 ,執行成功。
返回- 1,執行失敗:返回失敗是錯誤碼如下:
errno = EACCES (沒有讀的權限同時cmd 是IPC_STAT )
EFAULT (buf 指向的地址無效)
EIDRM (在讀取中隊列被刪除)
EINVAL (msgqid無效, 或者msgsz 小於0 )
EPERM (IPC_SET或者IPC_RMID 命令被使用,但調用程序沒有寫的權限)
注意:因爲消息隊列是多進程共享的,所以進程結束後系統並不會主動釋放消息隊列,所以我們在確保消息隊列不用的時候要調用msgctl 釋放消息隊列,以減少不必要的資源佔用。
下面我們看看示例代碼:
typedef struct
{
long mtype;
char mtext[20];
}my_msg;
int main(int argc, char** argv)
{
int isSend;
int index;
int msgid;
my_msg msg;
if(argc!=3)
{
printf("error para,exit\n");
return 0;
}
isSend = argv[1][0]-0x30;//第一個參數爲0表示進程接收消息,非0爲表示進程發送消息
index = argv[2][0]-0x30;//第二個參數表示要接收的消息的類型
if(isSend==0)
{
if(index>3||index<0)
{
printf("index error,exit\n");
return 0;
}
}
key_t key = ftok(".", 2 );
printf("key_t = %d\n",key);
msgid=msgget(key ,IPC_CREAT|0666);
if(msgid<0)
{
printf("create msg queue error\n");
return 0;
}
if(isSend==1)
{
struct timespec ts;
while(1)
{
clock_gettime(CLOCK_REALTIME, &ts);
msg.mtype=ts.tv_nsec%2+1;
sprintf(msg.mtext,"msg= %d",msg.mtype);
msgsnd(msgid, &msg, sizeof(my_msg), 0);
printf("send msg=%s\n",msg.mtext);
sleep(3);
}
}
else
{
int len ;
while(1)
{
len = msgrcv(msgid, (void *)&msg, sizeof(my_msg), index, 0);
if(len < 0)
{
printf("get msg error\n" );
}
else
{
printf("process %d get msg =[%s] type=%d\n" ,index ,msg.mtext,msg.mtype);
}
}
}
return 0;
}
例如我們編譯出來的可執行文件msgtest
我們分別在終端0運行 msgtest 1 0
在終端1運行 msgtest 0 1
在終端2運行 msgtest 0 2
在終端3運行 msgtest 0 3
我們可以看到在終端0打印 send msg n的時候 (n爲1-3)
對應的終端n 就會打印 process n get msg =[msg= n] type=n (n爲1-3)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.