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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章