Linux进程通信之消息队列-项目实践

本文深入讲解了消息队列的基本概念及其实现原理,包括消息队列的创建、消息的发送与接收过程,以及如何利用系统调用进行权限控制。此外还介绍了与消息队列相关的系统调用及其使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

   

      消息队列的基本概念:消息队列 (也叫做报文队列)是Unix系统V版本中3种进程间通信机制之一。另外两种是信号灯和共享内存。这些IPC机制使用共同的授权方法。只有通过系统调用将标志符传递给核心之后,进程才能存取这些资源。这种系统IPC对象使用的控制方法和文件系统非常类似。使用对象的引用标志符作为资源表中的索引。

       上面是到别的地方copy的一些蛋疼的基本概念,看一百遍都是那么回事,说穿了,想要操作一个消息队列首先得获得他的msqid,啥?你不知道msqid,看图~~~


        看见msqid木有,是吧。(ipcs -q,是为了查看当前系统中的消息队列,ipcs -m 查看共享内存),里面的几个字段,如key,msqid,owner,perms used-bytes,messages都是很有用的,请听我娓娓道来。。。。

    key:msqid是IPC对象的内部名,为了使多个进程能访问同一个IPC对象,所以必须提供一个外部的key,那你是不是很疑惑,“我都知道了msqid,还要key干叼”,问得好,么急,这又的从消息队里的创建开始说了,我们来看,消息队里的创建/获取消息队里的操作权限,要用到msqid=msgget(ket_t key, int flag), 看到key木有,所有的消息队列创建之前或者获取一个消息队列的操作权限都要先给一个key,所以不同的进程通过相同的key可以得到相同的消息队列(操作权限),明白了吗,亲? 

 

 那我们再来说key的创建:key_t ftok( const char * path, int id )

      path:服务器和客户进程都认可的同一文件路径,

        id:子序号,一般设置0-255的数

 

      概念性的东西,咱也解释不大清楚,想多了解就去百度上面google下。

注:msgget(ket_t key, int flag)中key的值,可以直接给一个IPC_PRIVATE的宏, 但是每次创建的进程都是一个崭新的消息队列,对于需要拿来给客户服务器通信的消息队列来说,这有叼用。(水平有限,可能还真有叼用也未可而知)

     有了key之后,下一步就是创建消息队列了,就如前面说到的msgget函数,flag标志其实就是权限位,如同咱创建一个文件时(open)后面的权限位。flag取值有IPC_CREAT、IPC_EXCL同时可以再或上一个具体的权限如“0600”(就是咱执行ipcs-q时看到的perm),如msgget(key,IPC_CREAT|0600),由于要做成接口时要封装msgget,所以msgget的flag这个给就行了,至于IPC_EXCl与IPC_CREAT一起使用,如果该消息队列已创建,则返回错。IPC_EXCl也只能用到也别的需求的地方这里就不扯了。

下面来说说下一步的消息的收发:

int     msgsnd( int msqid , struct msgbuf *msgp , size_t msgsiz , int msgflag );
向消息队列送消息
返回值:        成功时:0
                失败时:-1

(1) msqid : 已打开的消息队列返回值id

(2) msgp : 存放消息的结构

(3) msgsz : 消息数据长度

(4) msgflg :发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够的空间容纳要发送的消息时,msgsnd是否等待

Struct msgbuf

{

     long   mtype;   //消息类型>0

      char  mtext[256];  //消息数据的首地址

}


int  msgrcv(int msqid,struct msgbuf *msgp, int msgsz,long  msgtyp, int  msgflg)

功能:从msqid 代表的消息队列中读取一个msgtyp类型的消息,并把消息存储在msgp指向的msgbuf结构中。在成功地读取了一条消息以后,队列中的这条消息将被删除。

返回值:失败返回-1,成功返回接收到的字节数

(1) msqid : 已打开的消息队列返回值id

(2) msgp : 存放消息的结构

(3) msgsz : 消息数据长度

(4)msgtyp: 要接受消息数据类型(啥,你不懂接收啥类型,好的,看下面的说明

(5) msgflg :发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够的空间容纳要发送的消息时,msgsnd是否等待

msgtyp==0;返回消息队列第一条消息

msgtyp>0:返回消息队列中类型为msgtyp的第一条消息

msgtyp<0:返回消息队列中小于等于msgtyp绝对值的第一条消息。

 啥你还是不懂msgtyp要怎么填,这个简单,看前面的Struct msgbuf,也就是发送的时候要先填充下这个结构体。

Struct msgbuf

{

     long   mtype;   //消息类型>0

      char  mtext[256];  //消息数据的首地址

}

其中的mtype即你要发送的类型,如果你填1,后面msgrcv的时候,msgtyp就的填1,具体你想怎么给,看需求

char  mtext[256];  就是你要发送的消息,填充大小随意但是不能超过内核限制的值,咋看?ipcs -l

------ Messages: Limits --------
max queues system wide = 1024
max size of message (bytes) = 8192//
看这里,别瞎看,这个就是内核限制的最大消息数,可以改具体请百度。
default max size of queue (bytes) = 16384


还有啥说的,我想想。。。

对了注意下,收发函数中的msgflg,这个标志位一般设置为0或者IPC_NOWAIT,二者选其一。你肯定想问,为啥不能一起用咧,我擦,你问题太多了,那我说一下吧。

0:阻塞

IPC_NOWAIT:阻塞

当使用msgsnd函数时,msgflg设置为0,如果消息队列已满则一直阻塞等待有空间的时候在吧消息插进去,如msgflg设置为IPC_NOWAIT,当消息队列木有足够的空间时则直接返回-1.

msgrcv也差不多,msgflg设置为0,如果消息队列为空则一直阻塞等待有队列中有消息的时候再接收消息,如msgflg设置为IPC_NOWAIT,当消息队列木有消息则直接返回-1.


最后还有个函数msgctl(int msqid,int cmd, struct msqid_ds *buf)

 cmd:IPC_STAT 取msqid指定的队列信息(不是消息就像咱们执行ipcs -q一样)即取出它在系统中的状态。存放在buf中,buf后面说

            IPC_SET 将buf中的数据传递给msqid指定的消息队列,以达到设置其属性的z作用.

            IPC_RMID 删除msqid指定的消息队列,buf给NULL

struct msqid_ds {

  struct ipc_perm msg_perm;
  struct msg *msg_first; /* first message on queue */
  struct msg *msg_last; /* last message in queue */
  __kernel_time_t msg_stime; /* last msgsnd time */
  __kernel_time_t msg_rtime; /* last msgrcv time */
  __kernel_time_t msg_ctime; /* last change time */
  struct wait_queue *wwait;
  struct wait_queue *rwait;
  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 */
  };
   其中包括:
  l 一个ipc_perm的数据结构(msg_perm域),描述该消息队列的通用认证方式。
  l 一对消息指针(msg_first、msg_last),分别指向该消息队列的队头(第一个消息)和队尾(最后一个消息)(msg)。发送者将新消息加到队尾,接收者从队头读取消息。
  l 三个时间域(msg_stime、msg_rtime、msg_ctime)用于记录队列最后一次发送时间、接收时间和改动时间。
  l 两个进程等待队列(wwait、rwait)分别表示等待向消息队列中写的进程(wwait)和等待从消息队列中读的进程(rwait)。如果某进程向一个消息队列发送消息而发现该队列已满,则进程挂在wwait队列中等待。从该消息队列中读取消息的进程将从队列中删除消息,从而腾出空间,再唤醒wwait队列中等待的进程。如果某进程从一个消息队列中读消息而发现该队列已空,则进程挂在rwait队列中等待。向该消息队列中发送消息的进程将消息加入队列,再唤醒rwait队列中等待的进程。
  l 三个记数域(msg_cbytes、msg_qnum、msg_qbytes)分别表示队列中的当前字节数、队列中的消息数和队列中最大字节数;
  l 两个PID域(msg_lspid、msg_lrpid)分别表示最后一次向该消息队列中发送消息的进程和最后一次从该消息队列中接收消息的进程。


这坐半天不动 屁股也疼 腰也酸,玛德 年纪轻轻的,哎~

  看代码

#ifndef __MSQ_H__
#define __MSQ_H__
#include<string>
using namespace std;


class MsQ
{
    public:
      MsQ();
     ~MsQ();
    public:
      int create_msq(int _ikey);
      int send_msq(string &_strSnd, long _lType, int _iFlg, string &_strErr);
      int recv_msq(string &_strRcv, long _lType, int _iFlg, string &_strErr);
      int get_msq_ds(struct msqid_ds *_pMsq);
      int set_msq_mode(struct msqid_ds *_pMsq, const char* _pMode);
      int free_msq(); 	    	
    private:
    	int m_iMqid;
      struct MsqBuf 
      {
          long lType;
          char cData[1024];
          MsqBuf():lType(long()){memset(cData, 0, 1024);}
      };
};
#endif





  

#include"msq.h"
#include<sys/msg.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>


MsQ::MsQ()
{
}


MsQ::~MsQ()
{
}


int MsQ::create_msq(int _iKey)
{
    m_iMqid = msgget(_iKey, IPC_CREAT|S_IRUSR|S_IWUSR);
    return m_iMqid;
}


int MsQ::send_msq(string& _strSnd, long _lType, int _iFlg, string& _strErr)
{
    int iRet;
    char cErr[100] = {0};
    MsqBuf mBuf;
    mBuf.lType = _lType;
    strcpy(mBuf.cData, (char*)_strSnd.c_str());
    
    if( _iFlg != 0 )
    {
     _iFlg = IPC_NOWAIT;
    }
    
    iRet = msgsnd(m_iMqid, &mBuf, _strSnd.length()+1, _iFlg);
    if( iRet == -1 )
    {
     sprintf( cErr, "[发送消息失败:%s]", strerror(errno));
     _strErr = cErr;
    }
    return iRet;
}


int MsQ::recv_msq(string& _strRcv, long _lType, int _iFlg, string& _strErr)
{
    MsqBuf mBuf;   
    int iRet;
    char cErr[100] = {0};
    
    if( _iFlg != 0 )
    {
     _iFlg = IPC_NOWAIT;
    }
    
    iRet = msgrcv(m_iMqid, &mBuf, 1024, _lType, _iFlg);
    if( iRet == -1 )
    {
     sprintf( cErr, "[接收消息失败:%s]", strerror(errno));
     _strErr = cErr;
    }
    _strRcv = mBuf.cData;
    return iRet;
}


int MsQ::get_msq_ds(struct msqid_ds *_pMsq)
{
    return msgctl(m_iMqid, IPC_STAT, _pMsq);
}


int MsQ::set_msq_mode(struct msqid_ds *_pMsq, const char* _pMode)
{
    sscanf(_pMode, "%ho", &_pMsq->msg_perm.mode);
 
    return msgctl(m_iMqid, IPC_SET, _pMsq);
}
int MsQ::free_msq()
{
    return msgctl(m_iMqid, IPC_RMID, NULL);
}    


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值