消息队列&信号灯/信号量(semaphore)

消息队列

概念

消息队列是System V IPC对象的一种

消息队列由消息队列ID来唯一标识

消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等

消息队列可以按照类型来发送/接收消息

消息队列可以链表实现

消息队列的使用

发送端:

1.申请Key

2.打开/创建消息队列   msgget

3.向消息队列发送消息   msgsnd

接收端:

1打开/创建消息队列   msgget

2从消息队列接收消息   msgrcv

3 控制(删除)消息队列   msgctl

示例代码:(发送)

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

//消息格式结构体定义
typedef struct{
    long msg_type;
    char buf[128];
}msgT;    


#define MSGLEN  (sizeof(msgT)-sizeof(long))

int main(){
    key_t key;
    int msgid;
    int ret;
    msgT msg;
    key = ftok(".",100);
    if(key<0){
        perror("ftok");
        return 0;
    }
    msgid = msgget(key,IPC_CREAT|0666);   //创建消息队列
    if(msgid<0){
        perror("msgget");
        return 0;
    }

//定义&写消息
    msg.msg_type = 1;
    strcpy(msg.buf,"this msg type 1");

    ret = msgsnd(msgid,&msg,MSGLEN,0);     //发送消息,成功返回0
//第一个参数msgid为消息ID
//第二个参数为消息缓冲区地址
//第三个参数为消息正文长度
//msgflg为0:当消息队列满时,msgsnd 将会阻塞,直到消息能写进消息队列
    
    if(ret<0){   
        perror("msgsnd");
        return 0;
    }    

    msg.msg_type = 2;
    strcpy(msg.buf,"this msg type 2");
    ret = msgsnd(msgid,&msg,MSGLEN,0);
    if(ret<0){
        perror("msgsnd");
        return 0;
    }
    msg.msg_type = 3;
    strcpy(msg.buf,"this msg type 3");
    ret = msgsnd(msgid,&msg,MSGLEN,0);
    if(ret<0){
        perror("msgsnd");
        return 0;
    }
    msg.msg_type = 4;
    strcpy(msg.buf,"this msg type 4");
    ret = msgsnd(msgid,&msg,MSGLEN,0);
    if(ret<0){
        perror("msgsnd");
        return 0;
    }
    msg.msg_type = 5;
    strcpy(msg.buf,"this msg type 5");
    ret = msgsnd(msgid,&msg,MSGLEN,0);
    if(ret<0){
        perror("msgsnd");
        return 0;
    }
}

(接收)

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

typedef struct{
    long msg_type;
    char buf[128];
}msgT;    

#define MSGLEN  (sizeof(msgT)-sizeof(long))
int main(){
    
    int msgid;
    key_t key;
    msgT msg;  //定义消息结构体msg
    int ret;
    key = ftok(".",100);
    if(key<0){
        perror("ftok");
        return 0;
    }    
    msgid = msgget(key,IPC_CREAT|0666);  //打开消息队列
    if(msgid<0){
        perror("msgget");
        return 0;
    }
    int count=0;
    while(1){  //循环连续接收数据
        ret = msgrcv(msgid,&msg,MSGLEN,0,0);
        if(ret<0){
            perror("msgrcv");
            return 0;
        } 
        count++;
        if(count>3){  //控制接收数量,收到三条消息之后跳出循环,退出
            break;
        }
        printf("收到消息类型 %d 的消息,内容为 %s\n",(int)msg.msg_type,msg.buf);
        //接收成功并打印类型以及消息
    }

    ret = msgctl(msgid,IPC_RMID,NULL);  //删除消息队列
    if(ret<0){
        perror("msgctl");
        return 0;
    }    


}

打开/创建消息队列

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

成功时返回消息队列的id,失败时返回EOF

key 和消息队列关联的key  IPC_PRIVATE 或 ftok

msgflg  标志位  IPC_CREAT|0666  IPC_CREAT:没有创建,有则打开。

发送消息

#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid, const void *msgp, size_t size,int msgflg);

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

msgid   消息队列id

msgp    消息缓冲区地址

size    消息正文长度

msgflg   标志位 0 或 IPC_NOWAIT

msgflg:

0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列

IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回

消息格式

typedef struct{
    long msg_type;
    char buf[128];
}msgT;

注意:

1 .消息结构必须有long类型的msg_type字段,表示消息的类型。

2 .消息长度不包括首类型 long  (!!!)

消息的接收

#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msgid, void *msgp, size_t size, long msgtype, int msgflg);

成功时返回收到的消息长度,失败时返回-1

msgid   消息队列id

msgp   消息缓冲区地址

size   指定接收的消息长度

msgtype   指定接收的消息类型   

msgflg   标志位  

msgtype:

        msgtype=0:收到的第一条消息,任意类型。(什么类型都接收)

        msgtype>0:收到的第一条 msg_type类型的消息。

        msgtype<0:接收类型等于或者小于msgtype绝对值的第一个消息。

        (例子:如果msgtype= -4,只接受类型是1、2、3、4的消息)

msgflg:

0:阻塞式接收消息

IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG

MSG_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息

消息队列的控制

#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid, int cmd, struct msqid_ds *buf);

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

msgid    消息队列id

cmd    要执行的操作  IPC_STAT / IPC_SET / IPC_RMID(删除)

buf   存放消息队列属性的地址

信号灯/信号量(semaphore)

信号量代表某一类资源,其值代表系统中该资源的数量

信号量是一个受保护的变量,只能通过三种操作来访问:

            初始化、P操作(申请资源)-- 消费--  -1 、V操作(释放资源)-- 生产 -- +1

概念:是不同进程间或一个给定进程内部不同线程间同步的机制。类似我们的

PV操作概念

生产者和消费者场景

P(S) 含义如下:

     if  (信号量的值大于0) {  

                 申请资源的任务继续运行;

                 信号量的值减一;

} else {   

申请资源的任务阻塞;

}

V(S) 含义如下:

     信号量的值加一;

     if (有任务在等待资源) {   

        唤醒等待的任务,让其继续运行

}

代码实现:

有名信号量执行同步操作(写进程)

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
void delsemfile(int sig){
    sem_unlink("mysem_w");
    exit(0);

}

int main(){


   sem_t *sem_r,*sem_w;  //定义读写信号量

   key_t key;
   int shmid;
   char *shmaddr;

   struct sigaction act;
   act.sa_handler = delsemfile;
   act.sa_flags = 0;
   sigemptyset(&act.sa_mask);

   sigaction(SIGINT,&act,NULL);

   key = ftok(".",100);
   if(key<0){
       perror("ftok");
       return 0;
   }

   shmid = shmget(key,500,0666|IPC_CREAT); 
   if(shmid<0){
       perror("shmget");
       return 0;
   }

   shmaddr = shmat(shmid,NULL,0);   
   
   sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);    //打开读信号量,初始为0
   sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);    //打开写信号量,初始为1

   while(1){
        sem_wait(sem_w);   //写信号量 -1
        printf(">");
        fgets(shmaddr,500,stdin);
        sem_post(sem_r);   //读信号量 +1
   }

}

(读进程)

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include <signal.h>


void delsemfile(int sig){
    sem_unlink("mysem_r");
    exit(0);

}


int main(){

   sem_t *sem_r,*sem_w;
   key_t key;
   int shmid;
   char *shmaddr;
   struct sigaction act;
   act.sa_handler = delsemfile;
   act.sa_flags = 0;
   sigemptyset(&act.sa_mask);

   sigaction(SIGINT,&act,NULL);  //注册处理函数



   key = ftok(".",100);
   if(key<0){
       perror("ftok");
       return 0;
   }

   shmid = shmget(key,500,0666|IPC_CREAT);
   if(shmid<0){
       perror("shmget");
       return 0;
   }

   shmaddr = shmat(shmid,NULL,0);
   
   sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);
   sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);

   while(1){
        sem_wait(sem_r);
        printf("%s\n",shmaddr);
        sem_post(sem_w);
   }

}

无名信号灯代码实现:

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <pthread.h>


sem_t sem_r,sem_w;
char *shmaddr;
void destroysem(int sig){
//    sem_unlink("mysem_w");
   sem_destroy(&sem_r);  //销毁信号量
   sem_destroy(&sem_w);  
   exit(0);

}

void *readmem(void *arg){
    while(1){
        sem_wait(&sem_r);
        printf("%s\n",shmaddr);
        sem_post(&sem_w);

    }

}


int main(){


   key_t key;
   int shmid;

   struct sigaction act;
   act.sa_handler = destroysem;
   act.sa_flags = 0;
   sigemptyset(&act.sa_mask);

   sigaction(SIGINT,&act,NULL);

   key = ftok(".",100);
   if(key<0){
       perror("ftok");
       return 0;
   }

   shmid = shmget(key,500,0666|IPC_CREAT);
   if(shmid<0){
       perror("shmget");
       return 0;
   }

   shmaddr = shmat(shmid,NULL,0);
   
//   sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);
//   sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);
   sem_init(&sem_r,0,0);
   sem_init(&sem_w,0,1);

   pthread_t tid;
   pthread_create(&tid,NULL,readmem,NULL);
    
   while(1){
        sem_wait(&sem_w);
        printf(">");
        fgets(shmaddr,500,stdin);
        sem_post(&sem_r);
   }

}


 

三种信号灯

Posix 有名信号灯

Posix 无名信号灯 (linux只支持线程同步)

System V 信号灯

(要使用pthread库)

Posix 有名信号灯和无名信号灯使用:

wait 是 P操作  post 是 V 操作

操作函数

有名信号灯打开
sem_t *sem_open(const char *name, int oflag);

sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);

参数:

name:name是给信号灯起的名字

oflag:打开方式,常用O_CREAT

mode:文件权限。常用0666

value:信号量值。二元信号灯值为1,普通表示资源数目

信号灯文件位置:/dev/shm

有名信号灯关闭
int sem_close(sem_t *sem);

有名信号灯的删除
int sem_unlink(const char* name);

无名信号灯初始化
int sem_init(sem_t *sem, int shared, unsigned int value);

参数:

sem:需要初始化的信号灯变量

shared: shared指定为0,表示信号量只能由初始化这个信号量的进程使用,不能在进程间使用,linux 不支持进程间同步。

Value:信号量的值

无名信号灯销毁
int sem_destroy(sem_t* sem);

信号灯P操作
int sem_wait(sem_t *sem);

获取资源,如果信号量为0,表示这时没有相应资源空闲,那么调用线程就将挂起,直到有空闲资源可以获取

信号灯V操作
int sem_post(sem_t *sem);

释放资源,如果没有线程阻塞在该sem上,表示没有线程等待该资源,这时该函数就对信号量的值进行增1操作,表示同类资源多增加了一个。如果至少有一个线程阻塞在该sem上,表示有线程等待资源,信号量为0,这时该函数保持信号量为0不变,并使某个阻塞在该sem上的线程从sem_wait函数中返回

注意:编译posix信号灯需要加pthread动态库。

System V 信号灯使用

int semget(key_t key, int nsems, int semflg);

功能:创建/打开信号灯

参数:key:ftok产生的key值(和信号灯关联的key值)

          nsems:信号灯集中包含的信号灯数目

          semflg:信号灯集的访问权限,通常为IPC_CREAT |0666

返回值:成功:信号灯集ID ; 失败:-1

int semop ( int semid, struct sembuf *opsptr, size_t nops);

功能:对信号灯集合中的信号量进行P - V操作

参数:semid:信号灯集ID

struct sembuf {

        short sem_num; // 要操作的信号灯的编号

        short sem_op;  // 1 : 释放资源,V操作

                                 // -1 : 分配资源,P操作  

        short sem_flg;  // 0(阻塞),IPC_NOWAIT, SEM_UNDO

};//对某一个信号灯的操作,如果同时对多个操作,则需要定义这种结构体数组

nops: 要操作的信号灯的个数 ,1个

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

int semctl ( int semid, int semnum, int cmd…/*union semun arg*/);

功能:信号灯集合的控制(初始化/删除)

参数:semid:信号灯集ID

semnum: 要操作的集合中的信号灯编号

cmd:

GETVAL:获取信号灯的值,返回值是获得值

SETVAL:设置信号灯的值,需要用到第四个参数:共用体

IPC_RMID:从系统中删除信号灯集合

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

示例代码:

#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/type.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define SEM_READ 0
#define SEM_WRITE 1

union semum{
    int val;
};

//P-V操作定义
void Poperation(int semid,int semindex){
   struct sembuf sbuf;
   sbuf.sem_num =  semindex;
   sbuf.sem_op = -1;
   sbuf.sem_flg = 0;

   semop(semid,&sbuf,1);


}
void Voperation(int semid,int semindex){
   struct sembuf sbuf;
   sbuf.sem_num =  semindex;
   sbuf.sem_op = 1;
   sbuf.sem_flg = 0;
              
   semop(semid,&sbuf,1);


}
    
    key_t key;
    int semid,shmid;
    char *shmaddr;  //共享内存地址
    key = ftok(".",100);
    if(key < 0){
        perror("ftok")
        return 0;
    }
    semid = semget(key,2,IPC_CREAT|0666); //创建信号灯
    if(semid < 0){
        perror("semget")
        return 0;
    }
    
//共享内存
    shmid = shmget(key,500,IPC_CREAT|0666); //创建共享内存
    shmaddr = shmat(shmid,NULL,0);     //获取共享内存地址
    
//初始化信号灯值
    union semum mysem;//定义结构体
    mysem.val = 0;
    semctl(semid,SEM_READ,SETVAL,mysem);
    mysem.val = 1;
    semctl(semid,SEM_WRITE,SETVAL,mysem);


//通讯进程

    pid_t pid;
    pid = fork();
    if(pid < 0){
        perror("fork")
        shmctl(shmid,IPC_RMID,NULL);//出错删除共享内存
        shmctl(semid,0,IPC_RMID);//出错删除共享内存
       exit(-1);
    }else if(pid == 0){
        while(1){
            Poperation(semid,SEM_READ);
            printf("%s\n",shmaddr);
            Voperation(semid,SEM_WRITE);

            }else{
                 while(1){
                Poperation(semid,SEM_WRITE);
                printf(">");
                fgets(shmaddr,32,stdin);

                Voperation(semid,SEM_READ);

                    }
                }

        }

}

运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值