UNIX网络编程:消息队列

IPC中除了管道PIPE、FIFO、信号量和共享内存区意外,还有一个很重要的形式——消息队列。消息队列相比共享内存来说,它本身带有同步机制。
消息队列是将消息按队列的方式组织成的链表,每个消息都是其中的一个节点。
消息队列的运行方式与命名管道非常相似。欲与其他进程通信的进程只要将消息发送到消息队列中,目的进程就从消息队列中读取需要的消息。需要注意的是,消息队列的长度以及每个消息的大小都是有限制的。
Linux系统提供的消息队列操作函数主要有以下几个:

(1)int msgget(key_t key,int msgflg);
msgget()函数与信号量的semget()函数相似,作用是创建一个消息队列。参数key是一个键值,可由用户设定也可通过ftok()函数获得。Msgflg参数设置的是一些标志位,可以是IPC_CREAT、IPC_EXCL、IPC_NOWAIT中的一个或者他们的组合。创建成功则返回消息队列ID;否则返回-1。

(2)int msgsnd(int msqid, const void *msgptr, int msgsz,int msgflg);
Msgsnd()函数的作用是将消息发送到消息队列中去。Msqid为消息队列ID。Msgptr是指想要发送的消息的指针,并且指向的缓冲区得第一个字段应为长整形,指定消息类型,消息内容存放在该缓冲区得紧跟消息类型字段得区域中。Msgsz是要发送的消息的长度。Msgflg与msgget()函数中的msgflg参数设置类似,设置当消息队列满等情况出现时的处理方式,如果msgflg设置为IPC_NOWAIT,则不发送消息并且立即返回-1;否则发送进程挂起等待。
如果msgsnd()函数调用成功,就会把消息复制到消息队列中去并返回0;否则返回-1。

(3)int msgrcv(int msqid, void *msgptr, int msgsz, long msgtyp, int msgflg);
Msgrcv()函数的作用是从消息队列中读取一个消息。Msqid是消息队列的ID。Msgptr保存从消息队列中读到的消息。Msgsz是msgptr指向的消息的长度。Msgtyp指定要求的消息类型,见表
Msgrcv()函数msgtyp参数说明:
大于0 接收消息队列中类型为msgtyp的第一个可用报文
等于0 接收消息队列中的第一个可用报文
小于0 接收消息队列中小于或等于msgtyp绝对值的第一个可用报文
Msgflg的设置与msgsnd()函数中 的参数msgflg设置类似,用于设置如何处理当前消息队列中没有满足条件的消息的情况。
如果msgrcv()函数调用成功,则返回读出的实际字节数;否则返回-1.

(4)int msgctl(int msqid, int cmd, struct msqid_ds *buf);
Msgctl()函数是消息队列的控制函数,类似于信号量的控制函数semctl()。Msqid是消息队列的ID。Cmd是要采取的控制操作,有3个可取值,如下所示:
IPC_SET:设置消息队列的属性,将buf指向的结构体中的数值设置为消息队列的相关性
IPC_STAT :获取消息队列的属性信息并保存到buf指向的结构体中
IPC_RMID:移除ID为msqid的消息队列

下面时用消息队列实现的一个客户端与服务器通信的小程序:

服务器程序:
ser.cpp

#include "utili.h"
#include "msg.h"

#define MSGSND 100 //定义服务器端MSGSND的type类型
#define MSGRCV 200 //定义服务器端MSGRCV的type类型

int main(int argc, char *argv[])
{  
    //获得一个唯一一个键值并检测是否创建成功
    key_t msg_key = ftok(argv[1], ID);
    if(msg_key == -1){
        printf("ftok error.\n");
        exit(1);
    }
    //用所获得的键值创建一个消息队列并检测是否创建成功
    key_t msg_id = msgget(msg_key, MSG_MODE);
    if(msg_id == -1){
        printf("msgget error.\n");
        exit(1);
    }
    //定义一个Msg类型的msg结构体
    Msg msg;
    while(1){
        //服务器发送数据
        printf("Ser:>");
        gets(msg.MsgText);
        //判断服务器发送的数据是否为quit,如果是则先将MSGSND类型给Msg.type,告诉它要发送的类型,然后将其发送,保证客户端可以读到结束信息,左后将消息队列删除
        if(strncmp(msg.MsgText, "quit", 4) == 0){ 
            msg.MsgType = MSGSND;
            msgsnd(msg_id, &msg, strlen(msg.MsgText)+1, IPC_NOWAIT);
            msgctl(msg_id, IPC_RMID, NULL); 
            break;
        }
        //如果不为quit,则将要发送类型赋给MsgType.并将其发送
        msg.MsgType = MSGSND;
        msgsnd(msg_id, &msg, strlen(msg.MsgText)+1, IPC_NOWAIT);
        //将服务器消息队列中要接受的类型数据读出【所以服务器端要接收的数据类型与客户端要发送的数据类型相一致】
        msgrcv(msg_id, &msg, 256, MSGRCV, 0);
        printf("Cli:>%s\n", msg.MsgText);
    }

    return 0;
}

客户端程序:
cli.cpp

#include "utili.h"
#include "msg.h"
//定义客户端的发送与接收数据类型,为了保证服务器端发送的数据能准确的被客户端所读取,所以服务器端的发送数据类型与客户端接收数据类型要一致;同理,服务器端所要接收的数据类型要与客户端所发送的数据类型所一致
#define MSGSND 200
#define MSGRCV 100

int main(int argc, char *argv[])
{ 
    //用于客户端同样的路经与ID值创建一个键值,并检测是否创建成功
    key_t msg_key = ftok(argv[1], ID);
    if(msg_key == -1){
        printf("ftok error.\n");
        exit(1);
    }
    //用创建得到的键值查找服务器中创建好的消息队列,并检测是否创建成功
    key_t msg_id = msgget(msg_key, 0);
    if(msg_id == -1){
        printf("msgget error.\n");
        exit(1);
    }

    Msg msg;
    while(1){
        msgrcv(msg_id, &msg, 256, MSGRCV, 0);   //接收来自服务器的消息
        printf("Ser:>%s\n", msg.MsgText);
        //比较接收到的消息是否为quit,如果是则退出
        if(strncmp(msg.MsgText, "quit", 4) == 0){ 
            break;
        }
        //服务器发送数据
        printf("Cli:>");
        gets(msg.MsgText);
        //如果发送的数据为quit,先确定发送消息类型并将其发送出去,确保服务器可以接收到结束信息,然后结束程序
        if(strncmp(msg.MsgText, "quit", 4) == 0){
            msg.MsgType = MSGSND;
            msgsnd(msg_id, &msg, strlen(msg.MsgText)+1, IPC_NOWAIT);
            msgctl(msg_id, IPC_RMID, NULL);
            break;
        }
        //如果不为quit,确定发送的数据类型并发送出去
        msg.MsgType = MSGSND;
        msgsnd(msg_id, &msg, strlen(msg.MsgText)+1, IPC_NOWAIT);
    }

    return 0;
}

头文件:
msg.h

#pragma once

typedef struct Msg
{
    long MsgType;
    char MsgText[256];
}Msg;

utili.h

#pragma once

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/msg.h>

using namespace std;

const char *write_fifo_name = "write_fifo";
const char *read_fifo_name = "read_fifo";
#define ID 0xFF
//#define ID1 0xFE
#define MSG_MODE IPC_CREAT|IPC_EXCL|0666 //创建消息队列时的权限信息

消息队列堆大的优点就是本身具有同步机制,不需要外加其他的同步形式来配合,这在操作上更加方便。

你可能感兴趣的:(unix,通信,网络编程,消息队列)