Linux IPC实践--Posix消息队列

1. 创建/获取一个消息队列

[cpp] view plain copy
  1. #include <fcntl.h>           /* For O_* constants */  
  2. #include <sys/stat.h>        /* For mode constants */  
  3. #include <mqueue.h>  
  4. mqd_t mq_open(const char *name, int oflag); //专用于打开一个消息队列  
  5. mqd_t mq_open(const char *name, int oflag, mode_t mode,  
  6.               struct mq_attr *attr);  

参数:

   name:  消息队列名字;

   oflag: 与open函数类型, 可以是O_RDONLY, O_WRONLY, O_RDWR, 还可以按位或上O_CREAT, O_EXCL, O_NONBLOCK.

   mode: 如果oflag指定了O_CREAT, 需要指定mode参数;

   attr: 指定消息队列的属性;

返回值:

   成功: 返回消息队列文件描述符;

   失败: 返回-1;


注意-Posix IPC名字限制:

   1. 必须以”/”开头, 并且后面不能还有”/”, 形如:/file-name;

   2. 名字长度不能超过NAME_MAX

   3. 链接时:Link with -lrt.

/** System V 消息队列

通过msgget来创建/打开消息队列

int msgget(key_t key, int msgflg);

**/

 

2. 关闭一个消息队列

[cpp] view plain copy
  1. int mq_close(mqd_t mqdes);  
  2. /** System V 消息队列没有类似的该函数调用**/  

3. 删除一个消息队列

[cpp] view plain copy
  1. int mq_unlink(const char *name);  
  2. /** System V 消息队列 
  3. 通过msgctl函数, 并将cmd指定为IPC_RMID来实现 
  4. int msgctl(int msqid, int cmd, struct msqid_ds *buf); 
  5. **/  
[cpp] view plain copy
  1. //示例  
  2. int main()  
  3. {  
  4.     mqd_t mqid = mq_open("/abc", O_CREAT|O_RDONLY, 0666, NULL);  
  5.     if (mqid == -1)  
  6.         err_exit("mq_open error");  
  7.     cout << "mq_open success" << endl;  
  8.     mq_close(mqid);  
  9.     mq_unlink("/abc");  
  10.     cout << "unlink success" << endl;  
  11. }  

4. 获取/设置消息队列属性

[cpp] view plain copy
  1. int mq_getattr(mqd_t mqdes, struct mq_attr *attr);  
  2. int mq_setattr(mqd_t mqdes, struct mq_attr *newattr,  
  3.                         struct mq_attr *oldattr);  

参数:

   newattr: 需要设置的属性

   oldattr: 原来的属性

[cpp] view plain copy
  1. //struct mq_attr结构体说明  
  2. struct mq_attr  
  3. {  
  4.     long mq_flags;       /* Flags: 0 or O_NONBLOCK */  
  5.     long mq_maxmsg;      /* Max. # of messages on queue: 消息队列能够保存的消息数 */  
  6.     long mq_msgsize;     /* Max. message size (bytes): 消息的最大长度 */  
  7.     long mq_curmsgs;     /* # of messages currently in queue: 消息队列当前保存的消息数 */  
  8. };  

/** System V 消息队列

通过msgctl函数, 并将cmd指定为IPC_STAT/IPC_SET来实现

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

**/

[cpp] view plain copy
  1. /** 示例: 获取消息队列的属性 
  2. **/  
  3. int main(int argc,char **argv)  
  4. {  
  5.     mqd_t mqid = mq_open("/test", O_RDONLY|O_CREAT, 0666, NULL);  
  6.     if (mqid == -1)  
  7.         err_exit("mq_open error");  
  8.   
  9.     struct mq_attr attr;  
  10.     if (mq_getattr(mqid, &attr) == -1)  
  11.         err_exit("mq_getattr error");  
  12.     cout << "Max messages on queue: " << attr.mq_maxmsg << endl;  
  13.     cout << "Max message size: " << attr.mq_msgsize << endl;  
  14.     cout << "current messages: " << attr.mq_curmsgs << endl;  
  15.   
  16.     mq_close(mqid);  
  17.     return 0;  
  18. }  

5. 发送消息

[cpp] view plain copy
  1. int mq_send(mqd_t mqdes, const char *msg_ptr,  
  2.            size_t msg_len, unsigned msg_prio);  

参数:

   msg_ptr: 指向需要发送的消息的指针

   msg_len: 消息长度

   msg_prio: 消息的优先级

/** System V 消息队列

通过msgsnd函数来实现消息发送

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

**/

[cpp] view plain copy
  1. /** 示例: 向消息队列中发送消息, prio需要从命令行参数中读取 **/  
  2. struct Student  
  3. {  
  4.     char name[36];  
  5.     int age;  
  6. };  
  7. int main(int argc,char **argv)  
  8. {  
  9.     if (argc != 2)  
  10.         err_quit("./send <prio>");  
  11.   
  12.     mqd_t mqid = mq_open("/test", O_WRONLY|O_CREAT, 0666, NULL);  
  13.     if (mqid == -1)  
  14.         err_exit("mq_open error");  
  15.   
  16.     struct Student stu = {"xiaofang", 23};  
  17.     unsigned prio = atoi(argv[1]);  
  18.     if (mq_send(mqid, (const char *)&stu, sizeof(stu), prio) == -1)  
  19.         err_exit("mq_send error");  
  20.   
  21.     mq_close(mqid);  
  22.     return 0;  
  23. }  

6. 从消息队列中读取消息

[cpp] view plain copy
  1. ssize_t mq_receive(mqd_t mqdes, char *msg_ptr,  
  2.                        size_t msg_len, unsigned *msg_prio);  

参数:

  msg_len: 读取的消息的长度, 注意: 此值一定要等于mq_attr::mq_msgsize的值, 该值可以通过mq_getattr获取, 但一般是8192字节     [this must be greater than the mq_msgsize attribute of the queue (see mq_getattr(3)).]

  msg_prio: 保存获取的消息的优先级

返回值:

  成功: 返回读取的消息的字节数

  失败: 返回-1

  注意: 读取的永远是消息队列中优先级最高的最早的消息, 如果消息队列为, 如果不指定为非阻塞模式, 则mq_receive会阻塞;

/** System V 消息队列

通过msgrcv函数来实现消息发送的

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

**/

[cpp] view plain copy
  1. /** 示例: 从消息队列中获取消息 **/  
  2. int main(int argc,char **argv)  
  3. {  
  4.     mqd_t mqid = mq_open("/test", O_RDONLY);  
  5.     if (mqid == -1)  
  6.         err_exit("mq_open error");  
  7.   
  8.     struct Student buf;  
  9.     int nrcv;  
  10.     unsigned prio;  
  11.     struct mq_attr attr;  
  12.     if (mq_getattr(mqid, &attr) == -1)  
  13.         err_exit("mq_getattr error");  
  14.   
  15.     if ((nrcv = mq_receive(mqid, (char *)&buf, attr.mq_msgsize, &prio)) == -1)  
  16.         err_exit("mq_receive error");  
  17.   
  18.     cout << "receive " << nrcv << " bytes, priority: " << prio << ", name: "  
  19.          << buf.name << ", age: " << buf.age << endl;  
  20.   
  21.     mq_close(mqid);  
  22.     return 0;  
  23. }  

7. 建立/删除消息到达通知事件

[cpp] view plain copy
  1. int mq_notify(mqd_t mqdes, const struct sigevent *sevp);  

参数sevp:

   NULL: 表示撤销已注册通知;

   非空: 表示当消息到达且消息队列当前为空, 那么将得到通知;

通知方式:

   1. 产生一个信号, 需要自己绑定

   2. 创建一个线程, 执行指定的函数

注意: 这种注册的方式只是在消息队列从空到非空时才产生消息通知事件, 而且这种注册方式是一次性的!

[cpp] view plain copy
  1. //sigevent结构体  
  2. struct sigevent  
  3. {  
  4.     int          sigev_notify; /* Notification method */  
  5.     int          sigev_signo;  /* Notification signal */  
  6.     union sigval sigev_value;  /* Data passed with notification */  
  7.     void       (*sigev_notify_function) (union sigval);  /* Function used for thread notification (SIGEV_THREAD) */  
  8.     void        *sigev_notify_attributes; /* Attributes for notification thread (SIGEV_THREAD) */  
  9.     pid_t        sigev_notify_thread_id; /* ID of thread to signal (SIGEV_THREAD_ID) */  
  10. };  
  11. union sigval            /* Data passed with notification */  
  12. {  
  13.     int     sival_int;         /* Integer value */  
  14.     void   *sival_ptr;         /* Pointer value */  
  15. };  

sigev_notify代表通知的方式: 一般常用两种取值:SIGEV_SIGNAL, 以信号方式通知; SIGEV_THREAD, 以线程方式通知

如果以信号方式通知: 则需要设定一下两个参数:

   sigev_signo: 信号的代码

   sigev_value: 信号的附加数据(实时信号)

如果以线程方式通知: 则需要设定以下两个参数:

   sigev_notify_function

   sigev_notify_attributes

/** Posix IPC所特有的功能, System V没有 **/

[cpp] view plain copy
  1. /**示例: 将下面程序多运行几遍, 尤其是当消息队列”从空->非空”, 多次”从空->非空”, 当消息队列不空时运行该程序时, 观察该程序的状态; 
  2. **/  
  3. mqd_t mqid;  
  4. long size;  
  5. void sigHandlerForUSR1(int signo)  
  6. {  
  7.     //将数据的读取转移到对信号SIGUSR1的响应函数中来  
  8.     struct Student buf;  
  9.     int nrcv;  
  10.     unsigned prio;  
  11.     if ((nrcv = mq_receive(mqid, (char *)&buf, size, &prio)) == -1)  
  12.         err_exit("mq_receive error");  
  13.   
  14.     cout << "receive " << nrcv << " bytes, priority: " << prio << ", name: "  
  15.          << buf.name << ", age: " << buf.age << endl;  
  16. }  
  17.   
  18. int main(int argc,char **argv)  
  19. {  
  20.     // 安装信号响应函数  
  21.     if (signal(SIGUSR1, sigHandlerForUSR1) == SIG_ERR)  
  22.         err_exit("signal error");  
  23.   
  24.     mqid = mq_open("/test", O_RDONLY);  
  25.     if (mqid == -1)  
  26.         err_exit("mq_open error");  
  27.   
  28.     // 获取消息的最大长度  
  29.     struct mq_attr attr;  
  30.     if (mq_getattr(mqid, &attr) == -1)  
  31.         err_exit("mq_getattr error");  
  32.     size = attr.mq_msgsize;  
  33.   
  34.     // 注册消息到达通知事件  
  35.     struct sigevent event;  
  36.     event.sigev_notify = SIGEV_SIGNAL;  //指定以信号方式通知  
  37.     event.sigev_signo = SIGUSR1;        //指定以SIGUSR1通知  
  38.     if (mq_notify(mqid, &event) == -1)  
  39.         err_exit("mq_notify error");  
  40.   
  41.     //死循环, 等待信号到来  
  42.     while (true)  
  43.         pause();  
  44.   
  45.     mq_close(mqid);  
  46.     return 0;  
  47. }  
[cpp] view plain copy
  1. /** 示例:多次注册notify, 这样就能过多次接收消息, 但是还是不能从队列非空的时候进行接收, 将程序改造如下: 
  2. **/  
  3. mqd_t mqid;  
  4. long size;  
  5. struct sigevent event;  
  6. void sigHandlerForUSR1(int signo)  
  7. {  
  8.     // 注意: 是在消息被读走之前进行注册,  
  9.     // 不然该程序就感应不到消息队列"从空->非空"的一个过程变化了  
  10.     if (mq_notify(mqid, &event) == -1)  
  11.         err_exit("mq_notify error");  
  12.   
  13.     //将数据的读取转移到对信号SIGUSR1的响应函数中来  
  14.     struct Student buf;  
  15.     int nrcv;  
  16.     unsigned prio;  
  17.     if ((nrcv = mq_receive(mqid, (char *)&buf, size, &prio)) == -1)  
  18.         err_exit("mq_receive error");  
  19.   
  20.     cout << "receive " << nrcv << " bytes, priority: " << prio << ", name: "  
  21.          << buf.name << ", age: " << buf.age << endl;  
  22. }  
  23.   
  24. int main(int argc,char **argv)  
  25. {  
  26.     // 安装信号响应函数  
  27.     if (signal(SIGUSR1, sigHandlerForUSR1) == SIG_ERR)  
  28.         err_exit("signal error");  
  29.   
  30.     mqid = mq_open("/test", O_RDONLY);  
  31.     if (mqid == -1)  
  32.         err_exit("mq_open error");  
  33.   
  34.     // 获取消息的最大长度  
  35.     struct mq_attr attr;  
  36.     if (mq_getattr(mqid, &attr) == -1)  
  37.         err_exit("mq_getattr error");  
  38.     size = attr.mq_msgsize;  
  39.   
  40.     // 注册消息到达通知事件  
  41.     event.sigev_notify = SIGEV_SIGNAL;  //指定以信号方式通知  
  42.     event.sigev_signo = SIGUSR1;        //指定以SIGUSR1通知  
  43.     if (mq_notify(mqid, &event) == -1)  
  44.         err_exit("mq_notify error");  
  45.   
  46.     //死循环, 等待信号到来  
  47.     while (true)  
  48.         pause();  
  49.   
  50.     mq_close(mqid);  
  51.     return 0;  
  52. }  

mq_notify 注意点总结:

   1. 任何时刻只能有一个进程可以被注册为接收某个给定队列的通知;

   2. 当有一个消息到达某个先前为空的队列, 而且已有一个进程被注册为接收该队列的通知时, 只有没有任何线程阻塞在该队列的mq_receive调用的前提下, 通知才会发出;

   3. 当通知被发送给它的注册进程时, 该进程的注册被撤销. 进程必须再次调用mq_notify以重新注册(如果需要的话),但是要注意: 重新注册要放在从消息队列读出消息之前而不是之后(如同示例程序);

 

附-查看已经成功创建的Posix消息队列

#其存在与一个虚拟文件系统中, 需要将其挂载到系统中才能查看

  Mounting the message queue filesystem On Linux, message queues are created in a virtual filesystem.  

(Other implementations may also  provide such a feature, but the details are likely to differ.)  This 

file system can be mounted (by the superuser, 注意是使用root用户才能成功) using the following commands:

mkdir /dev/mqueue

mount -t mqueue none /dev/mqueue

还可以使用cat查看该消息队列的状态, rm删除:

cat /dev/mqueue/abc 

rm abc

还可umount该文件系统

umount /dev/mqueue

 

附-Makefile

[plain] view plain copy
  1. .PHONY: clean all  
  2. CC = g++  
  3. CPPFLAGS = -Wall -g  
  4. BIN = main  
  5. SOURCES = $(BIN.=.cpp)  
  6. all: $(BIN)  
  7.   
  8. %.o: %.c  
  9.     $(CC) $(CPPFLAGS) -c $^ -o $@  
  10. main: main.o  
  11.     $(CC) $(CPPFLAGS) $^ -lrt -o $@  
  12.   
  13. clean:  
  14.     -rm -rf $(BIN) *.o bin/ obj/ core 

你可能感兴趣的:(Linux IPC实践--Posix消息队列)