【Linux】应用篇十四--消息队列与信号灯



一、消息队列

1、消息队列的概念

消息队列System V IPC对象的一种
在这里插入图片描述

  • 消息队列由消息队列ID来唯一标识
  • 消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息
  • 消息队列可以按照类型发送/接收消息

2、消息队列的使用

发送端:

①申请Key
②打开/创建消息队列   msgget
③向消息队列发送消息   msgsnd

接收端:

①打开/创建消息队列   msgget
②从消息队列接收消息   msgrcv
③控制(删除)消息队列   msgctl

3、消息队列的创建

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

成功时返回消息队列的id失败时返回EOF(-1)

  • key消息队列关联的key IPC_PRIVATEftok
  • msgflg 标志位: IPC_CREAT|0666IPC_CREAT:没有创建则创建,有则打开。
if ((key = ftok(., ‘q’)) == -1) 
{
	perror(“ftok”);  
	exit(-1);
}
if ((msgid = msgget(key, IPC_CREAT|0666)) < 0) 
{
	perror(“msgget”); 
	exit(-1);
}

4、发送消息

#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:标志位 0IPC_NOWAIT
    • 0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列
    • IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回

消息格式:

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

注意:

  • 消息结构必须有long类型msg_type字段,表示消息的类型
  • 消息长度不包括首类型 long

示例:

typedef  struct 
{
   long mtype;
   char mtext[64];
}MSG;
#define  LEN  (sizeof(MSG)sizeof(long))
int main() 
{
   MSG buf;
   ……
   buf.mtype = 100; 
   fgets(buf.mtext, 64, stdin);
   msgsnd(msgid, &buf,LEN, 0);
   …… 
   return 0;
}

5、消息接收

#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 : 指定接收的消息类型
    • msgtype=0:收到的第一条消息,任意类型
    • msgtype>0:收到的第一条 msg_type类型的消息。
    • msgtype<0:接收类型等于或者小于msgtype绝对值的第一个消息。
    • 例子:如果msgtype=-4,只接受类型是1、2、3、4的消息
  • msgflg : 标志位
    • 0:阻塞式接收消息
    • IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG
    • MSG_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息

示例:

typedef  struct 
{
   long mtype;
   char mtext[64];
} MSG;
#define  LEN  (sizeof(MSG)sizeof(long))
int main() 
{
   MSG buf;
   ……
   if (msgrcv(msgid, &buf,LEN, 200, 0) < 0) 
   {
      perror(“msgrcv”);
      exit(-1);
   }
   ……
}

6、消息队列的控制

#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 : 存放消息队列属性的地址

7、示例程序

发消息:

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


#define MSGSIZE (sizeof(msgT) - sizeof(long))
typedef struct msg
{
	long msg_type;
	char buf[128];
}msgT;

int main(int argc, const char *argv[])
{
	key_t key;
	int get,send;
	msgT msg;

	key = ftok(".", 100);
	if(key < 0)
	{
		perror("fork:");
		exit(-1);
	}

	get = msgget(key, IPC_CREAT|0666);
	if(get < 0)
	{
		perror("msgget:");
		exit(-1);
	}

	msg.msg_type = 1;
	strcpy(msg.buf, "The messge 1");
	send = msgsnd(get, &msg, MSGSIZE, 0);
	if(send < 0)
	{
		perror("msgsnd:");
		exit(-1);
	}

	msg.msg_type = 2;
	strcpy(msg.buf, "The messge 2");
	send = msgsnd(get, &msg, MSGSIZE, 0);
	if(send < 0)
	{
		perror("msgsnd:");
		exit(-1);
	}

	msg.msg_type = 3;
	strcpy(msg.buf, "The messge 3");
	send = msgsnd(get, &msg, MSGSIZE, 0);
	if(send < 0)
	{
		perror("msgsnd:");
		exit(-1);
	}

	msg.msg_type = 4;
	strcpy(msg.buf, "The messge 4");
	send = msgsnd(get, &msg, MSGSIZE, 0);
	if(send < 0)
	{
		perror("msgsnd:");
		exit(-1);
	}
	return 0;
}

收消息:

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


#define MSGSIZE (sizeof(msgT) - sizeof(long))
typedef struct msg
{
		long msg_type;
		char buf[128];
}msgT;

int main(int argc, const char *argv[])
{
		key_t key;
		int get, re, count = 0;
		msgT msg;

		key = ftok(".", 100);
		if(key < 0)
		{
				perror("fork:");
				exit(-1);
		}

		get = msgget(key, IPC_CREAT|0666);
		if(get < 0)
		{
				perror("msgget:");
				exit(-1);
		}

		while(1)
		{
				re = msgrcv(get, &msg, MSGSIZE, 0, 0);
				if(re < 0)
				{
						perror("msgrcv:");
						exit(-1);
				}
				count++;
				if(count == 4)
					break;
				printf("msgrcv %s\n", msg.buf);
		}

		msgctl(get, IPC_RMID, NULL);
		return 0;
}


二、信号灯/信号量

1、概念

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

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

  • 初始化
  • P 操作(申请资源)
  • V 操作(释放资源)

2、信号量-P/V操作

P(S) 含义如下:

if (信号量的值大于0) 
{  
		申请资源的任务继续运行;
       信号量的值减一;
} 
else 
{   
		申请资源的任务阻塞;
} 

V(S) 含义如下:

信号量的值加一;
if (有任务在等待资源)
{   
	唤醒等待的任务,让其继续运行 
}

3、Posix 信号量

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

Posix 有名信号灯和无名信号灯使用:
在这里插入图片描述


三、P/V操作函数

信号灯P操作(信号获取)

int sem_wait(sem_t *sem);

成功时返回0失败时返回EOF(-1)

  • sem : 指向要操作的信号量对象

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


信号灯V操作(信号释放)

int  sem_post(sem_t  *sem);
  • 成功时返回0失败时返回EOF(-1)

  • sem : 指向要操作的信号量对象

释放资源,如果没有线程阻塞在该sem上,表示没有线程等待该资源,这时该函数就对信号量的值进行增1操作,表示同类资源多增加了一个。

如果至少有一个线程阻塞在该sem上,表示有线程等待资源,信号量为0,这时该函数保持信号量为0不变,并使某个阻塞在该sem上的线程从sem_wait函数中返回

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


四、有名信号灯

有名信号灯打开:

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);

使用示例:
释放信号:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

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

#include <fcntl.h>           
#include <sys/stat.h>        
#include <semaphore.h>

void handle(int arg)
{
	sem_unlink("sem_write");
	exit(-1);
}
int main(int argc, const char *argv[])
{
	key_t key;
	int shmid;
	sem_t *sem_r, *sem_w;
	void *shmaddr;

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

	sigaction(SIGINT, &act, NULL);

	key = ftok(".", 100);
	if(key < 0)
	{
		perror("key:");
		exit(-1);
	}

	shmid = shmget(key, 256, 0666|IPC_CREAT);
	if(shmid < 0)
	{
		perror("shmid:");
		exit(-1);
	}

	shmaddr = shmat(shmid, NULL, 0);
	if(shmaddr == NULL)
	{
		perror("shmaddr:");
		exit(-1);
	}

	sem_w = sem_open("sem_write", O_CREAT|O_RDWR, 0666, 1);
	sem_r = sem_open("sem_read", O_CREAT|O_RDWR, 0666, 0);

	while(1)
	{
		sem_wait(sem_w);
		printf(">");
		fgets(shmaddr, 5, stdin);
		sem_post(sem_r);
	}
	return 0;
}

得到信号:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

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

#include <fcntl.h>           
#include <sys/stat.h>        
#include <semaphore.h>

void handle(int arg)
{
	sem_unlink("sem_read");
	exit(-1);
}

int main(int argc, const char *argv[])
{
	key_t key;
	int shmid;
	sem_t *sem_r, *sem_w;
	void *shmaddr;

	struct sigaction act; 
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT, &act, NULL);
	
	key = ftok(".", 100);
	if(key < 0)
	{
		perror("key:");
		exit(-1);
	}

	shmid = shmget(key, 256, 0666|IPC_CREAT);
	if(shmid < 0)
	{
		perror("shmid:");
		exit(-1);
	}

	shmaddr = shmat(shmid, NULL, 0);
	if(shmaddr == NULL)
	{
		perror("shmaddr:");
		exit(-1);
	}

	sem_w = sem_open("sem_write", O_CREAT|O_RDWR, 0666, 1);
	sem_r = sem_open("sem_read", O_CREAT|O_RDWR, 0666, 0);

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

五、无名信号灯

无名信号灯初始化:

#include  <semaphore.h>
int sem_init(sem_t *sem, int shared, unsigned int value);

成功时返回0失败EOF

参数:

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

无名信号灯销毁:

int sem_destroy(sem_t* sem);

使用示例:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

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

#include <fcntl.h>           
#include <sys/stat.h>        
#include <semaphore.h>
#include<pthread.h>

sem_t sem_r, sem_w;
void *shmaddr;

void handle(int arg)
{
		sem_destroy(&sem_r);
		sem_destroy(&sem_w);
		exit(-1);
}

void *thread1(void *arg)
{
		while(1)
		{
				sem_wait(&sem_r);
				printf("%s\n", (char *)shmaddr);
				sem_post(&sem_w);
		}
		exit(0);
}
int main(int argc, const char *argv[])
{
		key_t key;
		int shmid;

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

		sigaction(SIGINT, &act, NULL);

		key = ftok(".", 100);
		if(key < 0)
		{
				perror("key:");
				exit(-1);
		}

		shmid = shmget(key, 256, 0666|IPC_CREAT);
		if(shmid < 0)
		{
				perror("shmid:");
				exit(-1);
		}

		shmaddr = shmat(shmid, NULL, 0);
		if(shmaddr == NULL)
		{
				perror("shmaddr:");
				exit(-1);
		}

		sem_init(&sem_r, 0 ,0);
		sem_init(&sem_w, 0 ,1);

		pthread_t tid;
		pthread_create(&tid, NULL, thread1, NULL);

		while(1)
		{
				sem_wait(&sem_w);
				printf(">");
				fgets(shmaddr, 5, stdin);
				sem_post(&sem_r);
		}
		return 0;
}

六、System V 信号灯

1、System V IPC - 信号灯特点

  • System V 信号灯一个或多个计数信号灯的集合
  • 同时操作集合中的多个信号灯
  • 申请多个资源时避免死锁

2、System V信号灯使用步骤

  • 打开/创建信号灯 : semget
  • 信号灯初始化 : semctl
  • P/V操作 : semop
  • 删除信号灯 : semctl

3、信号灯创建/打开 – semget

#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);

成功时返回信号灯的id失败时返回-1
功能:创建/打开信号灯

参数:

  • keyftok产生的key值(和信号灯关联的key值)
  • nsems信号灯集中包含的信号灯数目
  • semflg信号灯集访问权限,通常为IPC_CREAT |0666

4、信号灯初始化 – semctl

#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd,);

成功时返回0失败时返回EOF
功能:信号灯集合的控制(初始化/删除)

参数:

  • semid信号灯集ID
  • semnum: 要操作的集合中的信号灯编号
  • cmd
    • GETVAL获取信号灯的值,返回值是获得值
    • SETVAL设置信号灯的值,需要用到第四个参数:共用体
    • IPC_RMID:从系统中删除信号灯集合

使用示例:假设信号灯集合中包含两个信号灯;第一个初始化为2,第二个初始化为0

union  semun  myun;
myun.val = 2;
if (semctl(semid, 0, SETVAL, myun) < 0) 
{
	perror(“semctl”);     
	exit(-1);
}
myun.val = 0;
if (semctl(semid, 1, SETVAL, myun) < 0) 
{
	perror(“semctl”);     
	exit(-1);
}

5、信号灯P/V操作 – semop

#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);

成功时返回0失败时返回-1
功能:对信号灯集合中的信号量进行P - V操作

参数:

  • semid信号灯集ID
  • sops : 描述对信号灯操作的结构体(数组)
  • nops: 要操作的信号灯的个数1个
struct  sembuf 
 {
     short  sem_num;
     short  sem_op;
     short  sem_flg;
 };
  • semnum : 信号灯编号
  • sem_op : -1-P操作 1-V操作
  • sem_flg : 0 / IPC_NOWAIT

使用示例:

#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

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

#define SEM_READ   0
#define SEM_WRITE  1

union semun 
{
   int val;
};               

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);
}

int main()
{
    key_t key;
    char *shmaddr;
    int semid,shmid;
    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 semun 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);
        semctl(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);

        }
    }
}

到这里就结束啦!
在这里插入图片描述

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

修成真

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值