Linux开发(八):多进程通信与同步---消息队列

进程用户空间是相互独立的,一般而言是不能相互访问的。但很多情况下进程间需要互相通信,来完成系统的某项功能。进程通过与内核及其它进程之间的互相通信来协调它们的行为。

消息队列就是一个消息的链表,具有特定的格式以及特定的优先级,对消息队列有写入权限的进程可以向其中按照一定的规则添加新消息,对消息队列有读取权限的进程则可以从消息队列中读走消息,这样两个进程间就实现了通信。

消息队列有两种类型,分别为System V以及POSIX,它们的相似之处在于数据的交换单位都是整个消息。

目录

一、System V 消息队列

1、获取key值

2、创建消息队列

3、控制消息队列

4、发送消息

5、读取消息

二、POSIX 消息队列 

1、新建/打开消息队列

2、发送消息

3、接收消息

4、消息队列属性

5、关闭消息队列

6、删除消息队列

7、注册消息通知

三、 POSIX和System V消息队列比较


一、System V 消息队列

1、获取key值

Linux系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。

key_t ftok( char * fname, int id )

函数参数:

fname:指定的文件路径+文件名,该文件必须是存在而且可以访问

id:随机值,其前8个比特会被用做生成key值

返回值:

成功则返回key_t值,失败返回 -1  

2、创建消息队列

根据key值,创建一个消息队列

//from /usr/include/sys/msg.h
int msgget(key_t key,int msgflag);

函数参数:

key:ftok()函数生成的key值

msgflag:表示消息队列的访问权限,它与文件的访问权限一样,可以与以下值做或操作,以实现特定功能:

  • #define  IPC_CREAT  00001000   //如果key值不存在则创建
  • #define  IPC_EXCL   00002000     //如果key存在,则返回失败
  • #define  IPC_NOWAIT 00004000  //如果需要等待时,直接返回错误

返回值:

成功则返回该消息队列的ID,失败返回 -1

3、控制消息队列

 该函数用来控制消息队列

//from /usr/include/sys/msg.h
int msgctl(int msgid,int cmd,struct msqid_ds* buf);

函数参数:

msgid:消息队列的ID

cmd:控制命令:

  • #define  IPC_RMID  0    // 立即删除消息队列
  • #define  IPC_SET   1    // 设置buf中的消息队列属性
  • #define  IPC_STAT  2    // 获取消息队列的属性并保存在buf中
  • #define  IPC_INFO  3    // 获取限制信息

buf:一个指向消息队列模式和访问权限的结构,msgid_ds结构至少包括以下成员:

struct msgid_ds {    
    uid_t shm_perm.uid;    
    uid_t shm_perm.gid;    
    mode_t shm_perm.mode;    
};  

返回值:

成功根据不同的 cmd 有不同的返回值,失败返回 -1 

4、发送消息

将消息添加到消息队列中。

//from /usr/include/sys/msg.h
int msgsnd(int msgid,void* msg,size_t size,int msgflag);

函数参数:

msgid:消息队列的ID

msg:一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样:

struct my_message{    
    long int message_type;    
    // you data   
};  

size:这是msg指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说size是不包括长整型消息类型成员变量的长度。

msgflag:用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。一般取IPC_NOWAIT,即如果发送需要等待,则该函数将立即返回错误。若设置为0,则表示函数将会一直阻塞等待

返回值:

成功返回0,失败返回 -1

5、读取消息

读取消息队列中的消息。

//from /usr/include/sys/msg.h
int msgrcv(int msgid,void* msg,size_t size,,long mtype,int msgflag);

函数参数:

msgid:消息队列的ID

msg:一个指针,用以存储接收到的消息

size:消息的长度

mtype:接收消息的操作类型,有 3 种方式:

  1. msgtyp = 0:读取队列中的第一条消息
  2. msgtyp > 0:读取队列中类型为 msgtyp 的第一条消息,除非在 msgflg 中指定了 MSG_EXCEPT,否则将读取类型不等于 msgtyp 的队列中的第一条消息。
  3. msgtyp < 0:读取队列中最小类型小于或等于 msgtyp 绝对值的第一条消息

msgflag:用于控制当队列中没有相应类型的消息可以接收时将发生的事情 。一般取IPC_NOWAIT,即如果没有消息,则该函数将立即返回错误。若设置为0,则表示函数将会一直阻塞等待

返回值:

成功返回实际读取消息的字节数, 失败返回 -1

二、POSIX 消息队列 

1、新建/打开消息队列

创建一个新消息队列或打开一个既有消息队列。

#include  
#include  
#include 

mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);

函数参数:

name:作为消息队列标识,必须使用斜线打头后面跟着一个或多个非斜线字符的名字

oflag:是一个位掩码,具体如下:

Linux开发(八):多进程通信与同步---消息队列_第1张图片

mode:是一个位掩码,指定了施加于新消息队列之上的权限。并且与open()一样,mode中的值会与进程的umask取掩码。

attr:指定了新消息队列的特性。如果attr为NULL,那么将使用系统定义的默认特性创建队列。

返回值:

成功返回消息队列描述符, 失败返回 -1

2、发送消息

将缓冲区中的消息添加到消息队列中,如果消息队列满,那么后续发送会阻塞直到队列中存在可用空间为止,或者在O_NONBLOCK情况下立即失败并返回EAGAIN错误。

#include 
#include 
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);

int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout);

函数参数:

mqdes:消息队列标识符

msg_ptr:待发送到消息缓冲区

msg_len:是缓冲区长度

msg_pro:不为NULL,那么接收到消息的优先级就会被复制到msg_prio

abs_timeout:超时等待的绝对时间:

struct timespec {
    time_t tv_sec; /* 秒 */
    long tv_nsec; /* 纳秒*/ 
}

返回值:

成功返回0,失败返回 -1

3、接收消息

从消息队列中读取一条优先级最高、存在时间最长的消息。如果消息队列为空,那么该函数会阻塞直到存在可用的消息,或者在O_NONBLOCK情况下会立即失败并返回EAGAIN。

#include 
#include 
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);

ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio, const struct timespec *abs_timeout);

函数参数:

mqdes:消息队列标识符

msg_ptr:待接收消息缓冲区

msg_len:是缓冲区长度

msg_pro:不为NULL,那么接收到消息的优先级就会被复制到msg_prio

abs_timeout:超时等待的绝对时间:

struct timespec {
    time_t tv_sec; /* 秒 */
    long tv_nsec; /* 纳秒*/ 
}

返回值:

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

4、消息队列属性

 mq_open()、mq_getattr()以及mq_setattr()都可以设置或者读取消息队列特性,通过指向mq_attr结构的指针实现:

struct mq_attr {
  long mq_flags;   // 消息队列flag 
  long mq_maxmsg;  // 消息队列中消息数量上限,必须大于0。只能由mq_open()指定,后面无法修改。
  long mq_msgsize; // 消息队列中每条消息的大小上限,必须大于0。只能由mq_open()指定,后面无法修改。
  long mq_curmsgs; // 当前消息队列中消息数目
};
#include 
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr, struct mq_attr *oldattr);

函数参数:

mqdes:消息队列标识符

attr:获取到的消息队列属性

newattr:设置的消息队列属性

oldattr:不为NULL,那么老的消息队列属性将会保存在这里

返回值:

成功返回0,失败返回 -1

5、关闭消息队列

 关闭一个消息队列,当进程终止或调用exec()时,消息队列描述符会被自动关闭。与文件描述符一阳,应用程序应该在不再使用消息队列描述符的时候显式地关闭消息队列描述符以防止出现进程耗尽消息队列描述符的情况。

#include 
int mq_close(mqd_t mqdes);

函数参数:

mqdes:消息队列标识符

返回值:

成功返回0,失败返回 -1

6、删除消息队列

mq_unlink()函数删除通过name标识的消息队列,并将队列标记为在所有进程使用完该队列之后销毁该队列。

#include 
int mq_unlink(const char *name);

函数参数:

name:name标识的消息队列

返回值:

成功返回0,失败返回 -1

7、注册消息通知

POSIX消息队列区别于System V消息队列的一个特性是POSIX消息队列能够接收之前为空的队列上有可用消息的异步通知。

这个特性意味着无需执行一个阻塞或者将消息标记为O_NONBLOCK并在队列上定期执行mq_receive()调用。因为一个进程能够请求消息到达后通知,然后继续执行其他任务直到收到通知为止。

但有以下几点需要注意:

  • 任一时刻只有一个进程能够向一个特定的消息队列注册接收通知。如果消息队列上已经存在注册进程,那么后续在该队列上的注册请求将会失败,mq_notify()返回EBUSY错误。
  • 只有当一条新消息进入之前为空的队列时注册进程才会收到通知。如果在注册的时候队列中已经包含消息,那么之后当队列被清空之后有一条新消息到达之时才会发出通知。
  • 当向注册进程发送了一个通知之后就会删除注册信息,之后任何进程就能够向队列注册接收通知了。一个进程要想持续地接收通知,那么他就必须要在每次接收到通知之后再次调用mq_notify()来注册自己
  • 注册进程只有在当前不存在其他在该队列上调用mq_receive()而发生阻塞的进程时才会收到通知。如果其他进程在mq_receive()调用中被阻塞了,那么该进程会读取消息,注册进程会保持注册状态。
  • 一个进程可以通过在调用mq_notify()时传入一个值为NULL的notification参数来撤销自己在消息通知上的注册信息。

mq_notify()函数注册调用进程在一条消息进入描述符mqdes引用的空队列时接收通知

#include 
int mq_notify(mqd_t mqdes, const struct sigevent *notification);

函数参数:

mqdes:消息队列标识

notification:指定了进程接收通知的机制:

struct sigevent {
    int sigev_notify; /* Notification method */
    int sigev_signo; /* Notification signal for SIGEV_SIGNAL */
    union sigval sigev_value; /* Value passed to signal handler or thread function */
    void (*sigev_notify_function) (union sigval); /* Thread notification function */
    void *sigev_notify_attributes; /* Really 'pthread_attr_t' */
};
union sigval {
    int sival_int; /* Integer value for accompanying data */
    void *sival_ptr; /* Pointer value for accompanying data */
};

其中sigev_notify字段被设置成下列值中的一个:

  • SIGEV_NONE:注册这个进程接收通知,但当一条消息进入之前为空的队列时不通知改进程。与往常一样,当新消息进入空队列之后注册消息会被删除。
  • SIGEV_SIGNAL:通过生成一个sigev_signo指定的信号来通知进程。如果sigev_signo是一个实时信号,那么sigev_value字段将会指定信号都带的数据。通过传入信号处理器的siginfo_t中的si_value或通过sigwaitinfo()或sigtimedwait()返回值都能够去的这部分数据。
    • typedef struct {
        int si_signo; /* Signal number */
        int si_code; /* Signal code */----------对应应该为SI_MESGQ
        int si_trapno; /* Trap number for hardware-generated signal (unused on most architectures) */
        union sigval si_value; /* Accompanying data from sigqueue() */
        pid_t si_pid; /* Process ID of sending process */
        uid_t si_uid; /* Real user ID of sender */
      ...
      } siginfo_t;
  • SIGEV_THREAD:通过调用在sigev_notify_function中指定的函数来通知进程,就像是在一个新线程中启动该函数一样。sigev_notify_attreibutes字段你可以为NULL或是一个指向定义了线程特性的pthread_attr_t指针。sigev_value中指定的sigval值将作为参数传入这个参数。

返回值:

成功返回0,失败返回 -1

三、 POSIX和System V消息队列比较

POSIX消息队列是引用计数的,只有当所有当前使用队列的进程都关闭了,消息队列之后才会对队列进行标记以便删除,这点和System V消息队列有很大不同。

POSIX消息队列相对于System V消息队列优势:

  1.  POSIX IPC接口更加简单,并与UNIX文件模型更加一致。
  2.  POSIX IPC对象是引用技术的,简化了确定合适删除一个对象的任务。
  3.  消息通知特性允许一个进程能够在一条消息进入之前为空的队列时异步地通过信号或线程的实例化来接收通知。
  4. 4在Linux上可以使用poll()、select()以及epoll来监控POSIX消息队列。

POSIX消息队列相对劣势:

  1.  POSIX消息队列可移植性稍差。
  2.  与POSIX消息队列严格按照优先级排序相比,System V消息队列能够根据类型来选择消息的功能更加灵活。

你可能感兴趣的:(Linux,linux,消息队列,进程,通信)