天天看點

POSIX和SYSTEM的消息隊列應該注意的問題

首先看看POSIX的代碼:

1.posix_mq_server.c

#include <mqueue.h>

#include <sys/stat.h>

#include <string.h>

#include <stdio.h>

#define MQ_FILE "/mq_test"

#define BUF_LEN 128

int main()

{

     mqd_t mqd;

    char buf[BUF_LEN];

    int  por = 0;

    int ret = 0;

    struct mq_attr attr;

    attr.mq_flags = 0;

    attr.mq_maxmsg = 3;

    attr.mq_msgsize = 50;

    attr.mq_curmsgs= 0;

    mqd = mq_open(MQ_FILE, O_WRONLY,0666,&attr);

    if (-1 == mqd)

    {

        printf("mq_open error.\n");

        return -1;

    }

    do{

        buf[BUF_LEN-1]='\0';

        printf("MQ_MSG : ");

        scanf("%s", buf);

        if(buf[BUF_LEN-1]!= '\0')

        {

            continue;

        }

        printf("strlen:%d\nMQ_POR : ",strlen(buf));

        scanf("%d", &por);

        ret== mq_send(mqd, buf, strlen(buf)+1, por);

        if (ret != 0)

            perror("mq_send error.\n");

        memset(buf,'\0',BUF_LEN);

    }while(strcmp(buf, "quit"));

    mq_close(mqd);

    mq_unlink(MQ_FILE);

    return 0;

}

2.posix_mq_client.c

    mqd_t mqd;

    char buf[BUF_LEN + 1] = "quit";

    int cnt;

    attr.mq_maxmsg = 128;

    attr.mq_msgsize = 128;

    attr.mq_curmsgs = 0;

    //mqd = mq_open(MQ_FILE, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR, NULL);

    mqd = mq_open(MQ_FILE, O_RDONLY | O_CREAT, 0644, &attr);

        cnt = mq_receive(mqd, buf, BUF_LEN, &por);

        if (0 < cnt)

            printf("mq receive : ");

            fflush(stdout);

            buf[cnt] = '\0';

            printf("%s  por:%d\n", buf,por);

    }while(strcmp(buf, "quit")==0);

    printf("\n");

3.makefile

target:client  server

client: posix_mq_client.c

    gcc posix_mq_client.c -o client -lrt

server:posix_mq_server.c

    gcc posix_mq_server.c -o server -lrt

clean:

    rm -f client server

    rm -f *.o

運作make:

==[]==root@gaoke:~/code$./server 

MQ_MSG : fgsdfgsdfgsdfg

MQ_POR : 9

MQ_MSG : dfgsdfgsdfg

MQ_POR : 3

MQ_MSG : dfghsdfhgjghdj

MQ_POR : 6

MQ_MSG : sdfgdgfhgjh

MQ_POR : 2

MQ_MSG : dsfghgjghjkh

MQ_POR : 8

MQ_MSG : sdfgsdfgsdfgsd

MQ_POR : 5

MQ_MSG : 

==[]==root@gaoke:~/code$./client 

mq receive : fgsdfgsdfgsdfg  por:9

mq receive : dsfghgjghjkh  por:8

mq receive : dfghsdfhgjghdj  por:6

mq receive : sdfgsdfgsdfgsd  por:5

mq receive : dfgsdfgsdfg  por:3

mq receive : sdfgdgfhgjh  por:2

我們發現POSIX是嚴格按照優先級排序讀出的,而且先讀出作業優先級最高的作業。

好了再看看我們的SYSTEM是如何進行的,先寫個簡單的代碼調試運作看看結果如何:

首先我先說明一下System V系統的消息對列對象結構:

01

02

03

04

05

06

07

08

09

10

11

12

13

<code>struct msqid_ds {</code>

<code>    </code><code>struct ipc_perm     msg_perm; </code><code>// 權限,跟共享記憶體一樣</code>

<code>    </code><code>struct msg      *msg_first;</code><code>// 指向隊列的第一條消息</code>

<code>    </code><code>struct msg      *msg_last; </code><code>// 指向隊列的最後一條消息</code>

<code>    </code><code>msglen_t        msg_cbytes;</code><code>// 目前隊列所占位元組數</code>

<code>    </code><code>msgnum_t        msg_qnum;  </code><code>// 目前隊列的消息數</code>

<code>    </code><code>msglen_t        msg_qbytes;</code><code>// 隊列允許的最大位元組數</code>

<code>    </code><code>pid_t           msg_lspid; </code><code>// 最後調用msgsnd的PID</code>

<code>    </code><code>pid_t           msg_lrpid; </code><code>// 最後調用msgrcv的PID</code>

<code>    </code><code>time_t          msg_stime; </code><code>// 最後調用msgsnd的時間</code>

<code>    </code><code>time_t          msg_rtime; </code><code>// 最後調用msgrcv的時間</code>

<code>    </code><code>time_t          msg_ctime; </code><code>// 最後調用msgctl的時間</code>

<code>}</code>

使用其中一個IPC機制時,系統核心會維護一個ipc權限對象,用于設定讀寫權限

<code>struct ipc_perm {</code>

<code>    </code><code>uid_t   uid;   </code><code>// owner’s user id</code>

<code>    </code><code>gid_t   gid;       </code><code>// owner’s group id</code>

<code>    </code><code>uid_t   cuid;      </code><code>// creator’s user id</code>

<code>    </code><code>gid_t   cgid;      </code><code>// creator’s group id</code>

<code>    </code><code>mode_t  mode;  </code><code>// 讀寫權限</code>

<code>    </code><code>ulong_t seq;       </code><code>// 序列号</code>

<code>    </code><code>key_t   key;       </code><code>// IPC key</code>

<code>}</code>

其次我們還知道在Linux下,消息隊列被建立在虛拟檔案系統中。(其它實作可能也提供這樣的特性,但細節可能不一樣)此檔案系統可以使用以下指令挂載(由超級使用者):

1.sys_msq_server.c

#include &lt;limits.h&gt;

#include &lt;fcntl.h&gt;

#include &lt;sys/types.h&gt;

#include &lt;sys/ipc.h&gt;

#include &lt;sys/msg.h&gt;

#define MQ_FILE "./mq_test"

struct msgbuf {

   long mtype;     /*  message type, must be &gt; 0 */

   char mtext[256];  /*  message data */

};

int  main()

    struct msqid_ds info={0};

    struct msgbuf MSG={0};

    key_t key = ftok(MQ_FILE,10);

    int cnt =  msgget(key,IPC_CREAT|0666);//0:取消息隊列辨別符,若不存在則函數會報錯IPC_CREAT:當msgflg&amp;IPC_CREAT為真時,如果核心中不存在鍵值與key相等的消息隊列,則建立一個消息隊列;如果存在這樣的消息隊列,傳回此消息隊列的辨別符IPC_CREAT|IPC_EXCL:如果核心中不存在鍵值與key相等的消息隊列,則建立一個消息隊列;如果存在這樣的消息隊列則報錯

    if(cnt == -1)

        perror("error!");

    while(1){

        printf("enter the MSG:\n");

        scanf("%s",MSG.mtext);

        MSG.mtype = 1;

        //

        msgsnd(cnt,&amp;MSG,strlen(MSG.mtext)+1,IPC_NOWAIT);// 最後一個參數:0:當消息隊列滿時,msgsnd将會阻塞,直到消息能寫進消息隊列IPC_NOWAIT:當消息隊列已滿的時候,msgsnd函數不等待立即傳回IPC_NOERROR:若發送的消息大于size位元組,則把該消息截斷,截斷部分将被丢棄,且不通知發送程序

        msgctl(cnt,IPC_STAT,&amp;info);//IPC_STAT:獲得msgid的消息隊列頭資料到buf中IPC_SET:設定消息隊列的屬性,要設定的屬性需先存儲在buf中,可設定的屬性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes

        printf("uid:%d, gid = %d, cuid = %d, cgid= %d\n" , info.msg_perm.uid,  info.msg_perm.gid,  info.msg_perm.cuid,  info.msg_perm.cgid  ) ;

        printf("read-write:%03o, cbytes = %lu, qnum = %lu, qbytes= %lu\n" , info.msg_perm.mode&amp;0777, info.msg_cbytes, info.msg_qnum, info.msg_qbytes ) ;

        system("ipcs -q");

2.sys_msq_client.c

    int cnt =  msgget(key,IPC_CREAT|0666);

    int size =0;

        size = msgrcv(cnt,&amp;MSG,256,1,IPC_NOWAIT);

        if(size &gt; 0)

               puts(MSG.mtext);

        msgctl(cnt,IPC_STAT,&amp;info);

        printf("uid:%d, gid = %d, cuid = %d, cgid= %d\n" ,info.msg_perm.uid,  info.msg_perm.gid,  info.msg_perm.cuid,  info.msg_perm.cgid  ) ;

client: sys_msq_client.c

    gcc sys_msq_client.c -o client -lrt

server:sys_msq_server.c

    gcc sys_msq_server.c -o server -lrt

然後make運作

enter the MSG:

aaaa

uid:0, gid = 0, cuid = 0, cgid= 0

read-write:666, cbytes = 5, qnum = 1, qbytes= 65536

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages    

0xffffffff 0          root       666        5            1          

dddd

read-write:666, cbytes = 10, qnum = 2, qbytes= 65536

0xffffffff 0          root       666        10           2          

fffff

read-write:666, cbytes = 16, qnum = 3, qbytes= 65536

0xffffffff 0          root       666        16           3          

ggggg

read-write:666, cbytes = 22, qnum = 4, qbytes= 65536

0xffffffff 0          root       666        22           4           

 ==[]==root@gaoke:~/code$./client 

asdfasdfas

read-write:666, cbytes = 0, qnum = 0, qbytes= 65536

asdfasdfasdf

ssss

read-write:666, cbytes = 17, qnum = 3, qbytes= 65536

read-write:666, cbytes = 12, qnum = 2, qbytes= 65536

read-write:666, cbytes = 6, qnum = 1, qbytes= 65536

由此我們還發現了什麼呢?有沒有發現SYSTEM的消息輸入長度是固定的,然而POSIX的是可變長的。

SYSTEM的消息隊列函數由msgget、msgctl、msgsnd、msgrcv四個函數組成。下面的表格列出了這四個函數的函數原型及其具體說明。

msgget(得到消息隊列辨別符或建立一個消息隊列對象)

所需頭檔案

函數說明

得到消息隊列辨別符或建立一個消息隊列對象并傳回消息隊列辨別符

函數原型

int msgget(key_t key, int msgflg)

函數傳入值

key

0(IPC_PRIVATE):會建立新的消息隊列

大于0的32位整數:視參數msgflg來确定操作。通常要求此值來源于ftok傳回的IPC鍵值

msgflg

0:取消息隊列辨別符,若不存在則函數會報錯

IPC_CREAT:當msgflg&amp;IPC_CREAT為真時,如果核心中不存在鍵值與key相等的消息隊列,則建立一個消息隊列;如果存在這樣的消息隊列,傳回此消息隊列的辨別符

IPC_CREAT|IPC_EXCL:如果核心中不存在鍵值與key相等的消息隊列,則建立一個消息隊列;如果存在這樣的消息隊列則報錯

函數傳回值

成功:傳回消息隊列的辨別符

出錯:-1,錯誤原因存于error中

附加說明

上述msgflg參數為模式标志參數,使用時需要與IPC對象存取權限(如0600)進行|運算來确定消息隊列的存取權限

錯誤代碼

EACCES:指定的消息隊列已存在,但調用程序沒有權限通路它

EEXIST:key指定的消息隊列已存在,而msgflg中同時指定IPC_CREAT和IPC_EXCL标志

ENOENT:key指定的消息隊列不存在同時msgflg中沒有指定IPC_CREAT标志

ENOMEM:需要建立消息隊列,但記憶體不足

ENOSPC:需要建立消息隊列,但已達到系統的限制

如果用msgget建立了一個新的消息隊列對象時,則msqid_ds結構成員變量的值設定如下:

Ÿ        msg_qnum、msg_lspid、msg_lrpid、 msg_stime、msg_rtime設定為0。

Ÿ        msg_ctime設定為目前時間。

Ÿ        msg_qbytes設成系統的限制值。

Ÿ        msgflg的讀寫權限寫入msg_perm.mode中。

Ÿ        msg_perm結構的uid和cuid成員被設定成目前程序的有效使用者ID,gid和cuid成員被設定成目前程序的有效組ID。

msgctl (擷取和設定消息隊列的屬性)

擷取和設定消息隊列的屬性

int msgctl(int msqid, int cmd, struct msqid_ds *buf)

msqid

消息隊列辨別符

cmd

IPC_STAT:獲得msgid的消息隊列頭資料到buf中

IPC_SET:設定消息隊列的屬性,要設定的屬性需先存儲在buf中,可設定的屬性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes

buf:消息隊列管理結構體,請參見消息隊列核心結構說明部分

成功:0

EACCESS:參數cmd為IPC_STAT,确無權限讀取該消息隊列

EFAULT:參數buf指向無效的記憶體位址

EIDRM:辨別符為msqid的消息隊列已被删除

EINVAL:無效的參數cmd或msqid

EPERM:參數cmd為IPC_SET或IPC_RMID,卻無足夠的權限執行

msgsnd (将消息寫入到消息隊列)

将msgp消息寫入到辨別符為msqid的消息隊列

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)

msgp

發送給隊列的消息。msgp可以是任何類型的結構體,但第一個字段必須為long類型,即表明此發送消息的類型,msgrcv根據此接收消息。msgp定義的參照格式如下:

    struct s_msg{ /*msgp定義的參照格式*/

     long type; /* 必須大于0,消息類型 */

           char mtext[256]; /*消息正文,可以是其他任何類型*/

    } msgp;

msgsz

要發送消息的大小,不含消息類型占用的4個位元組,即mtext的長度

0:當消息隊列滿時,msgsnd将會阻塞,直到消息能寫進消息隊列

IPC_NOWAIT:當消息隊列已滿的時候,msgsnd函數不等待立即傳回

IPC_NOERROR:若發送的消息大于size位元組,則把該消息截斷,截斷部分将被丢棄,且不通知發送程序。

EAGAIN:參數msgflg設為IPC_NOWAIT,而消息隊列已滿

EACCESS:無權限寫入消息隊列

EFAULT:參數msgp指向無效的記憶體位址

EINTR:隊列已滿而處于等待情況下被信号中斷

EINVAL:無效的參數msqid、msgsz或參數消息類型type小于0

   msgsnd()為阻塞函數,當消息隊列容量滿或消息個數滿會阻塞。消息隊列已被删除,則傳回EIDRM錯誤;被信号中斷傳回E_INTR錯誤。

 如果設定IPC_NOWAIT消息隊列滿或個數滿時會傳回-1,并且置EAGAIN錯誤。

msgsnd()解除阻塞的條件有以下三個條件:

①    不滿足消息隊列滿或個數滿兩個條件,即消息隊列中有容納該消息的空間。

②    msqid代表的消息隊列被删除。

③    調用msgsnd函數的程序被信号中斷。

msgrcv (從消息隊列讀取消息)

從辨別符為msqid的消息隊列讀取消息并存于msgp中,讀取後把此消息從消息隊列中删除

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,

                      int msgflg);

存放消息的結構體,結構體類型要與msgsnd函數發送的類型相同

要接收消息的大小,不含消息類型占用的4個位元組

msgtyp

0:接收第一個消息

&gt;0:接收類型等于msgtyp的第一個消息

&lt;0:接收類型等于或者小于msgtyp絕對值的第一個消息

0: 阻塞式接收消息,沒有該類型的消息msgrcv函數一直阻塞等待

IPC_NOWAIT:如果沒有傳回條件的消息調用立即傳回,此時錯誤碼為ENOMSG

IPC_EXCEPT:與msgtype配合使用傳回隊列中第一個類型不為msgtype的消息

IPC_NOERROR:如果隊列中滿足條件的消息内容大于所請求的size位元組,則把該消息截斷,截斷部分将被丢棄

成功:實際讀取到的消息資料長度

E2BIG:消息資料長度大于msgsz而msgflag沒有設定IPC_NOERROR

EACCESS:無權限讀取該消息隊列

ENOMSG:參數msgflg設為IPC_NOWAIT,而消息隊列中無消息可讀

EINTR:等待讀取隊列内的消息情況下被信号中斷

msgrcv()解除阻塞的條件有以下三個:

①    消息隊列中有了滿足條件的消息。

③    調用msgrcv()的程序被信号中斷。

消息隊列使用程式範例

繼續閱讀