进程间通信

目录

思维导图:

学习内容:

1. IPC通信机制

2. 消息队列

1> 消息队列的原理图

2> 消息队列的特点 

3> 消息队列的相关API函数接口

4> 发送端实现

5> 接收端实现 

6> 消息队列的属性 

3. 共享内存 

3.1. 特点

3.2. 共享内存的API函数接口

3.3. 发送端实现

3.4. 接收端实现

4. 信号量集

4.1. 原理图

4.2. 信号量集的API函数接口 

4.3. 将信号量集函数二次封装 

4.3.1. sem.h

4.3.2. sem.c

4.4 使用信号灯集完成共享内存的进程同步 

4.4.1. 发送端流程

4.4.2. 接收端流程 

课外作业:

1> 使用消息队列完成两个进程之间相互通信

recv.c

snd.c


思维导图:


学习内容:

1. IPC通信机制

1> system V提供了三种IPC(interprocess communication)进程间通信方式

        消息队列:本质上在内核空间维护了一个队列,发送者可以将消息先放入到该队列上,接收者根据自己的需求读取队列上的信息

        共享内存:本质上在独立于多个进程外,分配一个物理内存的映射,使得多个进程可以共享该内存空间

        信号量集:将多个无名信号量,放入一个集合中,分别控制不同的进程,用于进程间同步问题

2> 该通信机制是独立于进程而存在的:当进程将数据写入该通信对象中后,即使进程已经结束,对象中保存的内容仍然存在

3> IPC对象相关指令

1、ipcs :查看当前所有的ipc对象的信息

2、ipcs -q:查看消息队列的信息

3、ipcs -m:查看共享内存的信息

4、ipcs -s:查看信号量集的信息

5、ipcrm -q\-m\-s ID号:删除指定的ipc对象

2. 消息队列

1> 消息队列的原理图

2> 消息队列的特点 

        1. 放入消息队列中的消息需要进行封装,包括消息类型和消息数据

        2. 消息队列的消息遵循先进先出原则,如果取出时不指定类型,则默认取第一个,如果指定了类型,则取第一个放入队列中的该消息

        3. 消息队列独立于进程而存在,当一个进程将消息放入队列后,及时进程退出了,也不会删除消息队列中的该消息

        4. 消息队列的大小为16KB

3> 消息队列的相关API函数接口

1、创建用于生成消息队列的钥匙
       #include <sys/types.h>
       #include <sys/ipc.h>

       key_t ftok(const char *pathname, int proj_id);
        功能:通过给定的文件路径和一个随机ID值创建出一个用于IPC通信的key值       ftok("/", 'k');
        参数1:文件路径,该文件的inode号占key值的2字节,该文件的设备号占key值的1字节
        参数2:一个给定的随机值,该值占key值的1字节
        返回值:成功返回创建出的key值,失败返回-1并置位错误码

2、通过钥匙创建出一个消息队列对象
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>

       int msgget(key_t key, int msgflg);
        功能:通过给定的key值创建一个消息队列
        参数1:用于创建消息队列的key值,该值可以由ftok创建出来,也可以是 IPC_PRIVATE,表示进行亲缘进程间的通信
        参数2:创建标识位
                IPC_CREAT:表示本次操作要创建一个消息队列,如果该key值对应的消息队列已经存在,则直接打开该消息对象
                IPC_EXCL:表示本次确保要创建一个新的消息队列,如果该消息队列已经存在,则该函数报错,错误码为EEXIST
                创建文件的权限,也在该参数中,使用位或连接
        返回值:成功返回消息队列的id号,失败返回-1并置位错误码

3、向消息队列中存放消息
       int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
       功能:向消息队列中存放消息,要求当前进程对消息队列具有可写权限
       参数1:消息队列的id号
       参数2:是指向封装好的消息的起始地址,通常类型如下,但是需要用户自己定义
          struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[1];    /* message data */
           };
        参数3:表示参数2中,消息正文的大小,不包含消息类型的大小
        参数4:发送标识位,表示是否阻塞
            0:表示阻塞
            IPC_NOWAIT:表示非阻塞
        返回值:成功返回0,失败返回-1并置位错误码

4、从消息队列中取消息
        ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
 int msgflg);
        功能:从消息队列中取消息
        参数1:消息队列的id号
        参数2:存放消息的容器起始地址
        参数3:消息正文的大小
        参数4:取出消息的类型
                >0: 表示取出该类型的消息的第一个消息
                =0:不限制类型,直接取消息队列中的第一个
                <0: 取出消息队列中类型小于msgtyp绝对值的第一个
                    例如:  50   10   2   10    6   6  8
                    -7:    2  6  6
    ---> 2
        参数5:是否阻塞
            0:表示阻塞
            IPC_NOWAIT:表示非阻塞
        返回值:成功返回成功读取的字节个数,失败返回-1并置位错误码

5、控制消息队列
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>

       int msgctl(int msqid, int cmd, struct msqid_ds *buf);
       功能:完成对消息队列指定的cmd操作
       参数1:消息队列的id号
       参数2:操作指令
          IPC_STAT:获取消息队列的属性,此时参数3必须要给定,表示接收消息队列的属性
             struct msqid_ds {
               struct ipc_perm msg_perm;     /* 拥有者和权限 */
               time_t          msg_stime;    /* 最新一次向消息队列中发送数据的时间 */
               time_t          msg_rtime;    /* 最新一次消息队列接受数据的时间 */
               time_t          msg_ctime;    /* 最新一次操作消息队列的时间 */
               unsigned long   __msg_cbytes; /* 当前队列中已用的字节数 */
               msgqnum_t       msg_qnum;     /* 当前队列中消息的个数*/
               msglen_t        msg_qbytes;   /* 队列的最大容量,默认是16K */
               pid_t           msg_lspid;    /* 最后一次向消息队列中发送消息的进程id号 */
               pid_t           msg_lrpid;    /* 最后一次从消息队列中取消息的进程id号 */
           };
               对第一个成员的介绍
              struct ipc_perm {
               key_t          __key;       /* 键值 */
               uid_t          uid;         /* 当前拥有者的用户id号 */
               gid_t          gid;         /*当前拥有者的组id号 */
               uid_t          cuid;        /* 创建消息队列的进程的用户id */
               gid_t          cgid;        /* 创建消息队列进程的组id号 */
               unsigned short mode;        /* 操作权限 */
               unsigned short __seq;       /* 队列号 */
           };
         IPC_SET:设置消息队列的属性  
         IPC_RMID:删除消息队列,当参数2位该值时,参数3可以忽略,直接填NULL即可
         返回值:成功返回0,失败返回-1并置位错误码

4> 发送端实现

#include<myhead.h>
//要发送的消息类型
struct msgbuf 
{
    long mtype;       /* message type, must be > 0 */
    char mtext[1024];    /* message data */
};
#define SIZE sizeof(struct msgbuf)-sizeof(long)


int main(int argc, const char *argv[])
{
    //1、创建key值,用于生产消息队列
    key_t key = ftok("/", 'k');
    if(key == -1)
    {
        perror("ftok error");
        return -1;
    }
    printf("key = %#x\n", key);

    //2、通过key值创建一个消息队列
    int msqid = msgget(key, IPC_CREAT|0664);
    if(msqid == -1)
    {
        perror("msgget error");
        return -1;
    }
    printf("msqid = %d\n", msqid);        //id号

    //向消息队列中存放消息
    struct msgbuf buf;
    
    while(1)
    {
        printf("请输入消息类型:");
        scanf("%ld", &buf.mtype);
        getchar();                    //吸收回车
        printf("请输入消息正文:");
        fgets(buf.mtext, SIZE, stdin);        //从终端获取数据
        buf.mtext[strlen(buf.mtext)-1] = 0;   //将换行换成 '\0'

        //将消息发送到消息队列中
        msgsnd(msqid, &buf, SIZE, 0);
        //参数1:消息队列id号
        //参数2:消息的起始地址
        //参数3:消息正文大小
        //参数4:阻塞形式发送数据
        printf("发送成功\n");

        if(strcmp(buf.mtext, "quit") == 0)
        {
            break;
        }
    }

    return 0;
}

5> 接收端实现 

#include<myhead.h>
//要发送的消息类型
struct msgbuf 
{
    long mtype;       /* message type, must be > 0 */
    char mtext[1024];    /* message data */
};
#define SIZE sizeof(struct msgbuf)-sizeof(long)


int main(int argc, const char *argv[])
{
    //1、创建key值,用于生产消息队列
    key_t key = ftok("/", 'k');
    if(key == -1)
    {
        perror("ftok error");
        return -1;
    }
    printf("key = %#x\n", key);

    //2、通过key值创建一个消息队列
    int msqid = msgget(key, IPC_CREAT|0664);
    if(msqid == -1)
    {
        perror("msgget error");
        return -1;
    }
    printf("msqid = %d\n", msqid);        //id号

    //从消息队列中读取消息
    struct msgbuf buf;
    
    while(1)
    {
        msgrcv(msqid, &buf, SIZE, 0, 0);
        //参数1:消息队列id号
        //参数2:数据容器起始地址
        //参数3:数据的正文大小
        //参数4:消息类型,0表示任意类型
        //参数5:表示阻塞读取消息

        printf("收到消息为:%s\n", buf.mtext);

        if(strcmp(buf.mtext, "quit") == 0)
        {
            break;
        }
    }

    //删除消息队列
    if(msgctl(msqid, IPC_RMID, NULL) ==-1)
    {
        perror("msgctl error");
        return -1;
    }

    return 0;
}

6> 消息队列的属性 

#include<myhead.h>


int main(int argc, const char *argv[])
{
    //1、创建key值,用于生产消息队列
    key_t key = ftok("/", 'k');
    if(key == -1)
    {
        perror("ftok error");
        return -1;
    }
    printf("key = %#x\n", key);

    //2、通过key值创建一个消息队列
    int msqid = msgget(key, IPC_CREAT|0664);
    if(msqid == -1)
    {
        perror("msgget error");
        return -1;
    }
    printf("msqid = %d\n", msqid);        //id号

    //获取该消息队列的属性
    struct msqid_ds ds;          //用于存放消息队列属性的变量
    if(msgctl(msqid, IPC_STAT, &ds) == -1)
    {
        perror("msgctl error");
        return -1;
    }

    //程序执行至此,该消息队列中的相关信息就被存入到ds结构体中了
    printf("键:%#x, msqid:%d, 权限:%#o, 已用字节:%ld, 消息:%ld\n", \
            ds.msg_perm.__key, msqid, ds.msg_perm.mode, ds.__msg_cbytes,\
            ds.msg_qnum);



    return 0;
}

3. 共享内存 

3.1. 特点

        1、共享内存表示的是多个进程共享一个外部的物理内存,效率比较高

        2、共享内存具有时效性,存放到共享内存区域中的数据,如果不及时读取,下一次写入后,前面的数据会被覆盖

        3、共享内存的操作不是一次性的,写入到共享内存中的数据,即使读取出去后,依然存在于共享内存,直到下一次被覆盖

3.2. 共享内存的API函数接口

1、创建共享内存对象
         #include <sys/ipc.h>
       #include <sys/shm.h>

       int shmget(key_t key, size_t size, int shmflg);
       功能:通过给定的key值创建一个共享内存的对象,并返回该对象的id
       参数1:key值,可以是IPC_PRIVATE也可以是由ftok创建出来的
       参数2:申请的共享内存段的大小,必须是PAGE_SIZE的整数倍,如果超过,则向上取整
       参数3:创建的标识
                IPC_CREAT:表示本次操作要创建一个共享内存,如果该key值对应的共享内存已经存在,则直接打开该共享内存对象
                IPC_EXCL:表示本次确保要创建一个新的共享内存,如果该共享内存已经存在,则该函数报错,错误码为EEXIST
                创建文件的权限,也在该参数中,使用位或连接
        返回值:成功返回创建出来的共享内存的id,失败返回-1并置位错误码
 
2、将共享内存地址映射到用户空间
       void *shmat(int shmid, const void *shmaddr, int shmflg);
       功能:映射共享内存的地址到用户空间
       参数1:共享内存ID
       参数2:对齐页地址,一般填NULL,让系统选择合适的对齐页
       参数3:共享内存的权限
           SHM_RDONLY:只读权限
           0:    读写权限
       返回值:  成功返回映射的共享内存段的地址,失败返回(void *)-1并置位错误码          
       
 3、取消共享内存的映射
             int shmdt(const void *shmaddr);
             功能:取消共享内存的映射
             参数:共享内存映射的地址
             返回值:成功返回0,失败返回-1并置位错误码
                 
 4、共享内存的控制函数
        #include <sys/ipc.h>
       #include <sys/shm.h>

       int shmctl(int shmid, int cmd, struct shmid_ds *buf);
       功能:控制共享内存对象
       参数1:共享内存的id号
       参数2:操作指令
          IPC_STAT:获取消息队列的属性,此时参数3必须要给定,表示接收消息队列的属性
         IPC_SET:设置消息队列的属性  
         IPC_RMID:删除消息队列,当参数2位该值时,参数3可以忽略,直接填NULL即可
        返回值: 成功返回0,失败返回-1并置位错误码

3.3. 发送端实现

#include<myhead.h>

#define SHMSIZE 4096



int main(int argc, const char *argv[])
{
    //1、定义key值
    key_t key;
    if((key = ftok("/", 'k')) == -1)
    {
        perror("ftok error");
        return -1;
    }

    //2、创建共享内存
    int shmid;
    if((shmid = shmget(key, SHMSIZE, IPC_CREAT|0664)) == -1)
    {
        perror("shmget error");
        return -1;
    }

    //3、将共享内存的空间映射到该进程中
    //NULL表示让系统自动分配
    char *addr = shmat(shmid, NULL, 0);
    if(addr == (void*)-1)
    {
        perror("shmat error");
        return -1;
    }

    //输出映射的空间地址
    printf("addr = %p\n", addr);

    //4、向共享内存中写入数据
    printf("请输入:");
    fgets(addr, SHMSIZE, stdin);
    addr[strlen(addr)-1] = '\0';

    getchar();

    //5、删除共享内存
    shmctl(shmid, IPC_RMID, NULL);

    return 0;
}

3.4. 接收端实现

#include<myhead.h>

#define SHMSIZE 4096



int main(int argc, const char *argv[])
{
    //1、定义key值
    key_t key;
    if((key = ftok("/", 'k')) == -1)
    {
        perror("ftok error");
        return -1;
    }

    //2、创建共享内存
    int shmid;
    if((shmid = shmget(key, SHMSIZE, IPC_CREAT|0664)) == -1)
    {
        perror("shmget error");
        return -1;
    }

    //3、将共享内存的空间映射到该进程中
    //NULL表示让系统自动分配
    char *addr = shmat(shmid, NULL, 0);
    if(addr == (void*)-1)
    {
        perror("shmat error");
        return -1;
    }

    //输出映射的空间地址
    printf("addr = %p\n", addr);

    //4、读取共享内存中的数据
    printf("读取的数据为:%s\n", addr);


    getchar();

    //5、删除共享内存
    //shmctl(shmid, IPC_RMID, NULL);

    return 0;
}

4. 信号量集

4.1. 原理图

4.2. 信号量集的API函数接口 

1、创建一个信号量集
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

       int semget(key_t key, int nsems, int semflg);
       功能:创建信号灯集
       参数1:用于创建信号量集的key值,可以是IPC_PRIVATE,也可以由ftok创建出来
       参数2:创建的信号灯集中的信号量的个数
       参数3:创建的标识
                IPC_CREAT:表示本次操作要创建一个信号量集,如果该key值对应的信号量集已经存在,则直接打开该信号量集对象
                IPC_EXCL:表示本次确保要创建一个新的信号量集,如果该信号量集已经存在,则该函数报错,错误码为EEXIST
                创建文件的权限,也在该参数中,使用位或连接
        返回值:成功返回创建出来的共享内存的id,失败返回-1并置位错误码

2、信号量集的控制函数
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

       int semctl(int semid, int semnum, int cmd, ...);
       功能:信号量集的控制函数
       参数1:信号量集的id
       参数2:要控制的信号灯集中的信号灯的编号,编号从0开始的
       参数3:控制指令
           IPC_RMID:删除信号灯集,此时参数4可以省略不写,参数2被忽略
           IPC_STAT\IPC_SET:获取或设置信号灯集的属性
           struct semid_ds {
               struct ipc_perm sem_perm;  /* Ownership and permissions */
               time_t          sem_otime; /* Last semop time */
               time_t          sem_ctime; /* Last change time */
               unsigned long   sem_nsems; /* No. of semaphores in set */
           };

       The ipc_perm structure is defined as follows (the highlighted fields are settable using IPC_SET):

           struct ipc_perm {
               key_t          __key; /* Key supplied to semget(2) */
               uid_t          uid;   /* Effective UID of owner */
               gid_t          gid;   /* Effective GID of owner */
               uid_t          cuid;  /* Effective UID of creator */
               gid_t          cgid;  /* Effective GID of creator */
               unsigned short mode;  /* Permissions */
               unsigned short __seq; /* Sequence number */
           };
           GETVAL\SETVAL:设置参数2这一个信号灯中的值,放到参数4提供的整数中
           GETALL\SETALL:设置或获取所有信号灯的值,放入到参数4提供的数组中
        参数4:可变参数,会根据参数3的不同,使用的类型也不同,所以是一个共用体变量
        union semun {
               int              val;    /* 参数3为 SETVAL使用该成员 */
               struct semid_ds *buf;    /* 参数3位 IPC_STAT, IPC_SET使用该成员 */
               unsigned short  *array;  /* 参数3位 GETALL, SETALL 使用该成员*/
               struct seminfo  *__buf;  /* 参数3为 IPC_INFO
 使用该成员*/
           };
        返回值:对于GETVAL成功返回获取的信号号
                对于GETVAL:返回信号量集ID
                失败全部返回-1并置位错误码

 3、对信号灯进行PV操作(申请资源和释放资源)
        #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

       int semop(int semid, struct sembuf *sops, size_t nsops);         
       功能:完成对信号灯集的相关操作
       参数1:信号灯集的id
       参数2:执行的操作,是一个结构体,成员如下
           unsigned short sem_num;  /* 要操作的信号灯的编号 */
           short          sem_op;   执行的操作:正数表示释放资源,负数表示申请资源
           short          sem_flg;  是否阻塞:0表示阻塞,IPC_NOWAIT表示非阻塞
        参数3:参数2的个数
        返回值:        成功返回0,失败返回-1并置位错误码 

4.3. 将信号量集函数二次封装 

4.3.1. sem.h

#ifndef SEM_H
#define SEM_H
#include <myhead.h>

union semun
{
    int val;               /* 参数3为 SETVAL使用该成员 */
    struct semid_ds *buf;  /* 参数3位 IPC_STAT, IPC_SET使用该成员 */
    unsigned short *array; /* 参数3位 GETALL, SETALL 使用该成员*/
    struct seminfo *__buf; /* 参数3为 IPC_INFO
使用该成员*/
};

// 创建信号灯集并初始化
// 返回值:信号灯集id
// 参数:信号灯集中灯的个数
int sem_create(int semcont);

// 执行申请某个信号灯的资源操作(P操作)
// 返回值:成功返回0,失败返回-1
// 参数1:信号灯集id
// 参数2:要操作的信号灯编号
int P(int semid, int semnum);

// 执行释放某个信号灯的资源操作(V操作)
// 返回值:成功返回0,失败返回-1
// 参数1:信号灯集id
// 参数2:要操作的信号灯编号
int V(int semid, int semnum);

// 删除信号灯集
// 参数:信号灯id号
int sem_del(int semid);

#endif

4.3.2. sem.c

#include"sem.h"
//定义设置某个信号灯的值的函数
int init_semnum(int semid, int semnum)
{
    int val = 0;
    printf("请输入第%d号灯的初始值:", semnum+1);
    scanf("%d", &val);
    getchar();

    //准备共用体变量
    union semun buf;
    buf.val = val;     //要传递的数据
    //调用semctl函数完成对信号灯的值的设置
    if(semctl(semid, semnum, SETVAL, buf)==-1)
    {
        perror("semctl error");
        return -1;
    }

    return 0;
}


//创建信号灯集并初始化
int sem_create(int semcont)
{
    //1、创建key值
    key_t key = ftok("/", 't');
    if(key == -1)
    {
        perror("ftok error");
        return -1;
    }

    //2、创建信号量集
    int semid = semget(key, semcont, IPC_CREAT|IPC_EXCL|0664);
    if(semid == -1)
    {
        //对错误码进行判断
        if(errno == EEXIST)
        {
            //说明消息队列已经存在,直接打开即可
            semid = semget(key, semcont, IPC_CREAT|0664);
            return semid;
        }
        perror("semget error");
        return -1;
    }

    //3、对信号灯进行初始化
    for(int i=0; i<semcont; i++)
    {
        init_semnum(semid, i);
    }

    //4、返回创建的信号灯集id
    return semid;

}


//P操作:申请资源
int P(int semid, int semnum)
{
    //定义操作结构体变量
    struct sembuf buf;
    buf.sem_num = semnum;   //要操作的信号灯
    buf.sem_op = -1;        //表示申请资源,如果semnum灯的资源为0,则阻塞
    buf.sem_flg = 0;          //表示如果没有资源,则阻塞

    //调用函数进行申请资源
    if(semop(semid, &buf, 1) ==-1)
    {
        perror("P error");
        return -1;
    }

    //成功返回0
    return 0;
}


//V操作:释放资源
int V(int semid, int semnum)
{
    //定义操作结构体变量
    struct sembuf buf;
    buf.sem_num = semnum;   //要操作的信号灯
    buf.sem_op = 1;        //表示申请资源,如果semnum灯的资源为0,则阻塞
    buf.sem_flg = 0;          //表示如果没有资源,则阻塞

    //调用函数进行申请资源
    if(semop(semid, &buf, 1) ==-1)
    {
        perror("V error");
        return -1;
    }

    //成功返回0
    return 0;
}

//删除信号灯集
int sem_del(int semid)
{
    //调用semctl删除信号灯集
    if(semctl(semid, 1, IPC_RMID) ==-1)
    {
        perror("delete error");
        return -1;
    }

    printf("信号灯集删除成功\n");
    return 0;
}

4.4 使用信号灯集完成共享内存的进程同步 

4.4.1. 发送端流程

#include<myhead.h>
#include<sys/user.h>
#include"sem.h"

int main(int argc, const char *argv[])
{

    //11、创建一个信号灯集并初始化
    int semid = sem_create(2);

    //1、创建key值用于创建共享内存段
    key_t key = ftok("/", 't');
    if(key == -1)
    {
        perror("ftok error");
        return -1;
    }

    printf("key = %d\n", key);

    //2、创建一个共享内存的对象
    int shmid = shmget(key, PAGE_SIZE, IPC_CREAT|0664);
    if(shmid == -1)
    {
        perror("shmget error");
        return -1;
    }
    printf("shmid = %d\n", shmid);

    //3、将共享内存段映射到程序中来
    char *addr = (char *)shmat(shmid, NULL, 0);
    //参数1:共享内存的id号
    //参数2:系统自动映射对齐页
    //参数3:表示对共享内存的操作权限为读写权限
    printf("addr = %p\n", addr);         //输出映射的地址

    //向共享内存中写入数据
    while(1)
    {
        printf("请输入>>>");
        //22、申请资源
        P(semid, 0);

        fgets(addr, PAGE_SIZE, stdin);
        addr[strlen(addr)-1] = 0;

        //33、释放资源
        V(semid, 1);
        printf("发送成功\n");
        if(strcmp(addr,"quit") == 0)
        {
            break;
        }
    }

    //取消映射关系
    if(shmdt(addr) ==-1)
    {
        perror("shmdt error");
        return -1;
    }


    

    return 0;
}

4.4.2. 接收端流程 

#include<myhead.h>
#include<sys/user.h>
#include"sem.h"

int main(int argc, const char *argv[])
{
    //11、创建信号灯集
    int semid = sem_create(2);

    //1、创建key值用于创建共享内存段
    key_t key = ftok("/", 't');
    if(key == -1)
    {
        perror("ftok error");
        return -1;
    }

    printf("key = %d\n", key);

    //2、创建一个共享内存的对象
    int shmid = shmget(key, PAGE_SIZE, IPC_CREAT|0664);
    if(shmid == -1)
    {
        perror("shmget error");
        return -1;
    }
    printf("shmid = %d\n", shmid);

    //3、将共享内存段映射到程序中来
    char *addr = (char *)shmat(shmid, NULL, 0);
    //参数1:共享内存的id号
    //参数2:系统自动映射对齐页
    //参数3:表示对共享内存的操作权限为读写权限
    printf("addr = %p\n", addr);         //输出映射的地址

    //读出共享内存中的数据
    while(1)
    {
        //22、申请资源
        P(semid, 1);

        printf("消息为:%s\n", addr);

        if(strcmp(addr,"quit") == 0)
        {
            break;
        }

        //33、释放资源
        V(semid, 0);
    }


    if(shmdt(addr) == -1)
    {
        perror("shmdt error");
        return -1;
    }

    //删除共享内存
    if(shmctl(shmid, IPC_RMID, NULL) == -1)
    {
        perror("shmctl error");
        return -1;
    }


    //44、删除信号灯集
    sem_del(semid);

    return 0;
}

课外作业:

1> 使用消息队列完成两个进程之间相互通信

解析:

recv.c

#include<myhead.h>
typedef struct
{
    long msgtype;        //消息类型
    char data[1024];        //消息正文
}Msg_ds;
#define SIZE sizeof(Msg_ds)-sizeof(long)      //正文大小
void *task2(void *arg)
{
    // 获取消息队列键值
    key_t key1 = ftok("/",'a');
    if(key1 == -1)
    {
        perror("key error");
        return NULL;
    }
    int msgpid = 0;
    // 创建消息队列
    if((msgpid = msgget(key1,IPC_CREAT|0664))==-1)
    {
        perror("msgget error");
        return NULL;
    }
    // 初始化
    Msg_ds msg; 
    while (1)
    {
        printf("请输入消息类型:");
        // 读取用户输入的消息类型
        scanf("%ld",&msg.msgtype);
        // 清除输入缓冲区换行符
        getchar();
        printf("B请输入:");
        // 读取用户输入的消息内容
        fgets(msg.data,sizeof(msg.data),stdin);
        // 移除消息内容末尾的换行符
        msg.data[strlen(msg.data)-1] = '\0';
        // 向消息队列发送消息
        if(msgsnd(msgpid,&msg,SIZE,0) == -1)
        {
            perror("snd error");
            return NULL;
        }
        // 若用户输入"quit",则退出循环
        if(strcmp(msg.data, "quit") == 0)
        {
            // 退出主循环
            break;
        }
    }
    return NULL;
}
// 主函数
int main(int argc, char const *argv[])
{
    // 通过ftok函数生成消息队列的键值
    key_t key = ftok("/",'k');
    if(key == -1)
    {
        perror("key error");
        return -1;
    }
    // 创建线程
    pthread_t pid;
    if(pthread_create(&pid,NULL,task2,NULL) != 0)
    {
        printf("线程创建失败\n");
        return -1;
    }
    // 创建消息队列
    int msgpid = 0;
    if((msgpid = msgget(key,IPC_CREAT|0664))==-1)
    {
        perror("msgget error");
        return -1;
    }
    Msg_ds msg; 
    while (1)
    {
        // 接收消息失败,打印错误信息并返回
        if(msgrcv(msgpid,&msg,SIZE,0,0) == -1)
        {
            perror("recv error");
            return -1;
        }
        // 如果接收到的消息为"quit",则退出循环
        if(strcmp(msg.data, "quit") == 0)
        {
            break;
        }
        // 打印接收到的消息
        printf("rcv1:%s\n",msg.data);
    }
    // 删除消息队列
    msgctl(msgpid, IPC_RMID, NULL);
    return 0;
}

snd.c

#include<myhead.h>
typedef struct
{
    long msgtype;        //消息类型
    char data[1024];        //消息正文
}Msg_ds;
#define SIZE sizeof(Msg_ds)-sizeof(long)      //正文大小

void *task1(void *arg)
{
    // 获取消息队列的键值
    key_t key1 = ftok("/",'a');
    if(key1 == -1)
    {
        perror("key error");
        return NULL;
    }
    int msgpid = 0;
    // 创建或获取消息队列
    if((msgpid = msgget(key1,IPC_CREAT|0664))==-1)
    {
        perror("msgget error");
        return NULL;
    }
    Msg_ds msg; 
    while (1)
    {
        // 接收消息,如果接收失败,输出错误信息并返回
        if(msgrcv(msgpid,&msg,SIZE,0,0) == -1)
        {
            perror("recv error");
            return NULL;
        }
        // 如果接收到的消息是"quit",则退出循环
        if(strcmp(msg.data, "quit") == 0)
        {
            break;
        }
        // 输出接收到的消息
        printf("rcv2:%s\n",msg.data);
    }
    // 删除消息队列
    msgctl(msgpid, IPC_RMID, NULL);
}

int main(int argc, char const *argv[])
{
    // 获取消息队列的键值
    key_t key = ftok("/",'k');
    if(key == -1)
    {
        perror("key error");
        return -1;
    }
    // 创建线程
    pthread_t pid;
    if(pthread_create(&pid,NULL,task1,NULL) != 0)
    {
        printf("线程创建失败\n");
        return -1;
    }
    int msgpid = 0;
    // 创建或获取消息队列
    if((msgpid = msgget(key,IPC_CREAT|0664))==-1)
    {
        perror("msgget error");
        return -1;
    }
    Msg_ds msg; 
    while (1)
    {
        // 用户输入消息类型
        printf("请输入消息类型:");
        scanf("%ld",&msg.msgtype);
        getchar(); // 清除输入缓冲区的换行符
        printf("A请输入:");
        // 用户输入消息内容
        fgets(msg.data,sizeof(msg.data),stdin);
        msg.data[strlen(msg.data)-1] = '\0'; // 去除输入内容末尾的换行符
        if(msgsnd(msgpid,&msg,SIZE,0) == -1)
        {
            // 发送消息失败,打印错误信息并退出
            perror("snd error");
            return -1;
        }
        // 判断输入内容是否为退出指令
        if(strcmp(msg.data, "quit") == 0)
        {
            break;
        }
    }
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值