消息队列与共享内存

消息队列与共享内存

1. 消息队列

1.1 消息队列的概述

消息队列是消息的链表,存放在内存中,由内核维护

消息队列的特点

1.消息队列中的消息是有类型的
2.消息队列中的消息是有格式的
3.消息队列可以实现消息的随机查询。消息不一定要以先进先出的次序读取,编程时可以按消息的类型读取
4.消息队列允许一个或多个进程向它写入或者读取消息
5.与无名管道、命名管道一样,从消息队列中读取消息,消息队列中对应的数据都会被删除
6.每个消息队列都有消息队列标识符,消息队列的标识符在整个系统中是唯一的。
7.只有内核重启或人工删除消息队列时,该消息队列才会被删除。若不人工删除消息队列,消息队列会一直存在与系统中

在ubantu相关版本(12)的消息队列限制如下:

1.每个消息内容最多为8k字节

2.每个消息队列容量最多为16k字节

3.系统中消息队列个数最多为1609个

4.系统中消息个数最多为16384个

1.2 ftok函数

System V提供的IPC通信机制需要一个key值,通过key值就可在系统内获得一个唯一的消息队列标识符。key值可以是认为指定的,也可以通过ftok函数获得

#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname,int proj_id);

功能:获得项目相关的唯一的IPC键值

参数:

  • pathname:路径名

  • proj_id:项目ID,非0整数(只有低8位有效:0-255)

    返回值:成功返回key值,失败返回-1

1.3 消息队列的操作

1.3.1 创建消息队列

#include <sys/msg.h>
int msgget(key_t key,int msgflg);

功能:创建一个新的或打开一个已经存在的消息队列。不同的进程调用此函数,只要用相同的key值就能得到同一个消息队列的标识符

参数:

  • key:IPC键值
  • msgflg:标识函数的行为及消息队列的权限,其取值:
    • IPC_CREAT:创建消息队列
    • IPC_EXCL:检测消息队列是否存在
    • 位或权限位:消息队列位或权限位后可以设置消息队列的访问权限,格式和open函数的mode_t一样,但可执行权限未使用

返回值:成功则返回消息队列的标识符,失败则返回-1

使用shell命令操作消息队列

  • ipcs -q查看消息队列
  • ipcrm -q msqid删除消息队列

1.3.2 发送消息

消息队列的消息的格式:

typedef struct _msg
{
    long mtype; /*消息类型*/
    char mtext[100]; /*消息正文*/
    ...      /*消息的正文可以有多个成员*/
}MSG;

消息类型必须是长整型的,而且必须是结构体类型的第一个成员,之后的是消息正文,正文可以有多个成员(正文成员可以是任意数据类型的)。

发送消息的函数:

将新消息添加到消息队列

#include <sys/msg.h>
int msgsnd(int msqid,cosnt void *msgp,siz_t msgsz,int msgflg);

参数:

  • msqid:消息队列的标识符

  • msgp:待发送消息结构体的地址

  • msgsz:消息正文的字节数(除了第一个成员long的大小之外的所有成员)

  • msgflg:函数的控制属性

    • 0:阻塞(达到消息队列的最大容量时,则阻塞)直到条件满足为止
    • IPC_NOWAIT:立即返回非阻塞

返回值:成功0;失败-1。

1.3.3 接收消息

从标识符为msqid的消息队列中接收一个消息

一旦接收消息成功,则消息在消息队列中被删除

#include <sys/msg.h>
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);

参数:

  • msqid:消息队列的标识符,代表要从哪个消息列中获取消息
  • msgq:存放消息结构体的地址
  • msgsz:消息正文的字节数
  • msgtyp:消息的类型,可以有以下几种类型
    • msgtyp=0:返回队列中的第一个消息
    • msgtyp>0:返回队列中消息类型为msgtyp的消息
    • msgtyp<0:返回队列中消息类型值小于或等于msgtyp绝对值的消息,如果这种消息有若干个,则取类型值最小的属性
  • msgflg:函数的控制属性
    • 0msgrcv调用阻塞(空消息队列)直到接收消息成功为止
    • MSG_NOERROR:若返回的消息字节数比msgsz字节数多,则消息就会截短到msgsz字节,且不通知消息发送进程
    • IPC_NOWAIT:调用进程会立即返回,若没有收到消息则立即返回-1。

返回值:成功返回读取消息的长度,失败返回-1

1.3.4 消息队列的控制

对消息队列进行各种控制,如修改消息队列的属性,或删除消息队列

参数:

  • msqid:消息队列的标识符

  • cmd:函数功能的控制

    • IPC_RMID:删除由msqid指示的消息队列,将它从系统中删除并破坏相关数据结构
    • IPC_STAT:将msqid相关的数据结构中各个元素的当前值存入到由buf指向的结构中
    • IPC_SET:将msqid相关的数据结构中的元素设置为由buf指向的结构中的对应值
  • bufmsqid_ds数据类型的地址,用来存放或更改消息队列的属性

struct msqid_ds
{
    struct ipc_perm msg_perm; /* structure describing operation permission */
    __time_t msg_stime; /* time of last msgsnd command */
#ifndef __x86_64__
    unsigned long int __glibc_reserved1;
#endif
    __time_t msg_rtime; /* time of last msgrcv command */
#ifndef __x86_64__
    unsigned long int __glibc_reserved2;
#endif
    __time_t msg_ctime; /* time of last change */
#ifndef __x86_64__
    unsigned long int __glibc_reserved3;
#endif
    __syscall_ulong_t __msg_cbytes; /* current number of bytes on queue */
    msgqnum_t msg_qnum; /* number of messages currently on queue */
    msglen_t msg_qbytes; /* max number of bytes allowed on queue */
    __pid_t msg_lspid; /* pid of last msgsnd() */
    __pid_t msg_lrpid; /* pid of last msgrcv() */
    __syscall_ulong_t __glibc_reserved4;
    __syscall_ulong_t __glibc_reserved5;
}

返回值:成功0;失败-1

1.4 综合案例

消息队列实现多人聊天程序

消息结构体类型

typedef struct msg
{
    long type;//接收者类型
    char text[100];//发送内容
    char name[20];//发送者姓名
}MSG;

每个程序都有两个任务,一个任务是负责接收消息,一个任务是负责发送消息,通过fork创建子进程实现多任务。

接收信息的进程,只接收某种类型的消息,只要别的进程发送此类型的消息,该进程就能收到。收到后通过消息的name成员就可知道是谁发送的消息

另一个进程负责发消息,可以通过输入来决定发送消息的类型

image-20230912083231164

代码实现:mq_bbs.c

#include <stdio.h>
#include <sys/typs.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

typedef struct msg_
{
    long mType;//消息类型
    char content[100];//正文数据
    char name[32];//姓名
}MSG;
int main(int argc,char const *argv[])
{
    if(argc!=2)
    {
        printf("invalid,format is:./xx name\n");
        return 1;
    }
    key_t key = ftok("/",160);
    int msgqid = msgget(key,IPC_CREAT|0666);
    if(msgqid == -1)
    {
        perror("msgget");
        return -1;
    }
    int pid = fork();
    if(pid == 0)
    {
        //子进程
        while(1)
        {
            write(STDOUT_FILENO,"我说:",7);
            MSG msg;
            scanf("%s",msg.content);
            strcpy(msg.name,argv[1]);
#ifdef BOB
            msg.mType = 1;
#else
            msg.mType = 2;
#endif
            if(msgsnd(msgqid,&msg,sizeof(MSG)-sizeof(long),0)!=-1)
            {
                printf("send msg ok\n");
                if(strncmp(msg.content,"bye",3)==0)
                {
                    break;
                }
            }
        }//kill(getpid(),SIGTOP);
        _exit(0);
    }
    else if(pid>0)
    {
        //父进程
        while(1)
        {
            MSG msg;
#ifdef BOB
            msg.mType = 2;
#else
            msg.mTyp = 1;
#endif
            if(msgrcv(msgqid,&msg,sizeof(MSG)-sizeof(long),msg.mType,0)!=-1)
            {
                printf("\n %s 说:%s\n",msg.name,msg.content);
                if(strncmp(msg.content,"bye",3)==0)
                {
                    break;
                }
                write(STDOUT_FILENO,"我说:",7);
            } 
        }
        //kill(pid,SIGSTOP);
        wait(NULL);
    }
    return 0;
}

运行需两次编译

gcc mq_bbs.c -o bob -D BOB
gcc mq_bbs.c -o lucy

2. 共享内存

2.1 共享内存概述

共享内存允许两个或者多个进程共享给定的存储区域

共享内存的特点

1.共享内存是进程间共享数据的一种最快的方法。

一个进程向共享的内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容

2.使用共享内存要注意的是多个进程之间对一个给定存储区访问的互斥

若一个进程正在向共享内存区写数据,则在它做完这一步操作前,别的进程不应当去读、写这些数据

在ubantu相关版本中(12+)中共享内存限制值如下:

1.共享存储区的最小字节数:1

2.共享存储区的最大字节数:32M

3.共享存储区的最大个数:4096

4.每个进程最多能映射的共享存储区的个数:4096

Linux命令查看共享内存限制:

ipcs -l

2.2 共享内存操作

2.2.1 获得共享存储标识符

创建或打开一块共享内存区

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key,size_t size ,int shmflg);

参数:

  • key:IPC键值
  • size:该共享存储段的长度(字节)
  • shmflg:标识函数的行为及共享内存的权限
    • IPC_CREAT:如果不存在就创建
    • IPC_EXCL:如果已经存在则返回失败
    • 位或权限位:共享内存位或权限位后可以设置共享内存的访问权限,格式和open函数的mode_t一样,但可执行权限或未使用

返回值:成功,返回共享内存标识符(shmid);失败,返回-1。

使用shell命令操作共享内存:

  • 查看共享内存ipcs -m
  • 删除共享内存ipcrm -m shmid

2.2.2 共享内存映射

共享内存映射(attach),将一个共享内存段映射到调用进程的数据段中

#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shmid,const void *shmaddr,int shmflg);

参数:

  • shmid:共享内存标识符

  • shmaddr:共享内存映射地址(若为NULL则由系统自动指定),推荐使用NULL

  • shmflg:共享内存段的访问权限和映射条件

    • 0:共享内存具有可读可写权限

    • SHM_RDONLY:只读

    • SHM_RND:(shmaddr非空时才有效)

      没有指定SHM_RND则此段连接到shmaddr所指定的地址上(shmaddr必需页对齐)。指定了SHM_RND则此段连接到shmaddr-shmaddr%SHMLBA所表示的地上。

返回值:成功,返回共享内存段映射地址;失败,返回-1

【注意】shmat函数使用的时候第二个和第三个参数一般设为NULL和0,即系统自动指定共享内存地址,并且共享内存可读可写

2.2.3 解除共享内存映射

解除共享内存映射(detach),将共享内存和当前进程分离

【注意】仅仅是断开联系并不删除共享内存

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);

参数:

shmaddr:共享内存映射地址

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

2.2.4 共享内存控制

共享内存空间的控制

#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shmid,int cmd,struct shmid_ds *buf);

参数:

  • shmid:共享内存标识符
  • cmd:函数功能的控制
    • IPC_RMID:删除
    • IPC_SET:设置shmid_ds参数
    • IPC_STAT:保存shmid_ds参数
    • SHM_LOCK:锁定共享内存段(超级用户)。
    • SHM_UNLOCK:解锁共享内存段。
  • buf:shmid_ds数据类型的地址,用来存放或修改共享内存的属性。

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

2.3 综合练习

生产者消费者模式

编写两个进程,一个是生产者进程,一个是消费者进程。它们通过共享内存区域进行通信,生产者将数据写入共享内存,消费者从共享内存中读取数据

typedef struct
{
    int data;
    int flag;
}Data;

如:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

typedef struct
{
    int data;
    int flag;
}Data;
int main(int argc,char const *argv[])
{
    key_t key=ftok("/",28);//IPC键
    int shmid = shmget(key,sizeof(Data),IPC_CREAT|0666);
    if(shmid == -1)
    {
        perror("shmget");
        return 1;
    }
    if(fork()==0)
    {
        //生产进程
        Data *data=shmat(shmid,NULL,0);
        if(data==(Data*)(-1))
        {
            perror("shmat");
            _exit(1);
        }
        data->flag=0;
        data->data=0;
        for(int i=0;i<100;i++)
        {
            while(data->flag==1);
            data->data++;
            data->flag=1;
            printf("生产进程 生产了data(%d)\n",data->data);
        }
        if(shmdt(data)==-1)
        {
            perror("shmat");
        }
        _exit(0);
    }
    if(fork()==0)
    {
        //消费者进程
        Data *data=shmat(shmid,NULL,0);
        if(data==(Data*)(-1))
        {
            perror("shmat");
            _exit(1);
        }
        while(1)
        {
            while(data->flag==0);
            printf("消费进程消费了data(%d)\n",data->data);
            if(data->data==100)
                break;
            sleep(1);
            data->flag=0;
        }
        if(shmdt(data)==-1)
        {
            perror("shmdt");
        }
        _exit(0);
    }
    while(1)//主进程
    {
        int pid = waitpid(0,NULL,WUNTRACED);
        if(pid==-1)
        {
            break;
        }
        printf("%d 子进程结束\n",pid);
    }
    //删除共享内存
    shmctl(shmid,IPC_RMID,NULL);
    return 0;
}
posted @ 2023-09-12 14:37  常羲和  阅读(53)  评论(0)    收藏  举报
// 侧边栏目录