一、消息队列
1. 定义
unix早期通信机制之一的信号能够传送的信息量有限,管道则只能传送无格式的字节流,这无疑会给应用程序开发带来不便。消息队列(也叫做报文队列)则克服了这些缺点。
2. 发展
消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式。
进程可以向中按照一定的规则添加新消息;另一些进程则可以从消息队列中读走消息。
3. 分类
目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息队列,系统V消息队列目前被大量使用。
4. 持续性:系统V消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。
5. 键值:消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,必须提供该消息队列的键值。如下:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok (char*pathname, char proj)
功能:返回文件名对应的键值。
pathname:文件名;proj:项目名(随意,不为0即可)
6. 打开/创建消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg)
key:键值,由ftok获得。
msgflg:标志位。
返回值:与健值key相对应的消息队列描述字。
IPC_CREAT:创建新的消息队列
vIPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。
vIPC_NOWAIT:读写消息队列要求无法得到满足时,不阻塞。
在以下两种情况下,将创建一个新的消息队列:
1)如果没有与健值key相对应的消息队列,并且msgflg中包含了IPC_CREAT标志位。
2)key参数为IPC_PRIVATE。
创建代码如下:
int open_queue(key_t keyval)
{
int qid;
if((qid=msgget(keyval,IPC_CREAT))==-1)
{
return(-1);
}
return(qid);
}
7. 发送消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid,struct msgbuf*msgp,int msgsz,int msgflg)
功能:向消息队列中发送一条消息。
msqid:已打开的消息队列id;
msgp:存放消息的结构;
msgsz:消息数据长度;
msgflg:发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。
struct msgbuf
{
long mtype; /* 消息类型 > 0 */
char mtext[1]; /* 消息数据的首地址 */
};
8. 接收消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg)
功能:从msqid代表的消息队列中读取一个msgtyp类型的消息,并把消息存储在msgp指向的msgbuf结构中。在成功地读取了一条消息以后,队列中的这条消息将被删除。
int read_message(int qid,long type,struct mymsgbuf*qbuf)
{
int result,length;
length=sizeof(struct mymsgbuf)-sizeof(long);
if((result=msgrcv(qid,qbuf,length,type,0))==-1)
return(-1);
return(result);
}
msg.c代码如下:
#include <sys/types.h>
#include <sys/msg.h>
#include <unistd.h>
struct msg_buf
{
int mtype;
char data[255];
};
int main()
{
key_t key;
int msgid;
int ret;
struct msg_buf msgbuf;
key=ftok("/tmp/2",'a');
printf("key=[%x]\n",key);
msgid=msgget(key,IPC_CREAT|0666);
if(msgid==-1)
{
printf("create error\n");
return -1;
}
msgbuf.mtype=getpid();
strcpy(msgbuf.data,"test haha");
ret=msgsnd(msgid,&msgbuf,sizeof(msgbuf.data),IPC_NOWAIT);
if(ret==-1)
{
printf("send message error\n");
return -1;
}
memset(&msgbuf,0,sizeof(msgbuf));
ret=msgrcv(msgid,&msgbuf,sizeof(msgbuf.data),getpid(),IPC_NOWAIT);
if(ret==-1)
{
printf("recv message error\n");
return -1;
}
printf("recv msg=[%s]\n",msgbuf.data);
}
整个程序可以分为三个大的部分:消息队列的创建、消息发送、消息接收(在此程序中,消息的发送和接收在同一个进程中)
1)创建
key=ftok("/tmp/2",'a');获取键值之后,试图利用这个键值将消息队列打开,卡带key指向的消息队列有两种情况:第一种情况是消息队列已经存在,可以打开它,返回打开消息队列的描述符msgid;第二种情况是这个消息队列并不存在,由于有IPC_CREAT这个参数可以创建消息队列,同样打开消息队列的描述符msgid。创建好之后,就有了msgid。
2)发送
msgsnd()函数来完成。第一个参数msgid,第二个参数是要发送的消息队列保存在那一个结构体中,这里保存在msgbuf中(这是自己定义的结构)。不过msgbuf已经保存好了数据,通过使用msgbuf.mtype=getpid(),把进程id作为消息的类型(取值自己决定,只要发送和接收的类型值一致即可);strcpy()函数给data中放入了数据。
3)接收
如果是另一个进程接收这个消息,还要使用ftol()、msgget()函数打开这个消息队列。
发送和接收消息体必须是一致的。
二、信号量
1.信号量(又名:信号灯)与其他进程间通信方式不大相同,主要用途是保护临界资源。进程可以根据它判定是否能够访问某些共享资源。除了用于访问控制外,还可用于进程同步。
2. 分类
1)二值信号灯:信号灯的值只能取0或1,类似于互斥锁。 但两者有不同:信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
2)计数信号灯:信号灯的值可以取任意非负值。
3. 创建/打开
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
key:键值,由ftok获得
nsems:指定打开或者新创建的信号灯集中将包含信号灯的数目
semflg:标识,同消息队列
4. 操作
int semop(int semid, struct sembuf *sops, unsigned nsops)
功能:对信号量进行控制。
semid:信号量集的ID
sops:是一个操作数组,表明要进行什么操作
nsops:sops所指向的数组的元素个数。
struct sembuf
{
unsigned short sem_num; /* semaphore
index in array */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
sem_num:要操作的信号量在信号量集中的编号,第一个信号的编号是0。
sem_op:如果其值为正数,该值会加到现有的信号量值中,通常用于释放信号量;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对
值,通常用于获取信号量;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。
Sem_flg:信号操作标志,可能的选择有两种:
1)IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
2)IPC_UNDO:程序结束时(不论正常或不正常)释放信号量,这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。