应用篇十四--消息队列与信号灯
一、消息队列
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_PRIVATE
或ftok
msgflg
标志位:IPC_CREAT|0666
,IPC_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
:消息队列idmsgp
: 消息缓冲区地址
size
:消息正文长度
msgflg
:标志位0
或IPC_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
功能:创建/打开信号灯
参数:
key
:ftok
产生的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);
}
}
}
到这里就结束啦!