关于SystemV消息队列
操作函数:
int msgctl(int msqid, int cmd, struct msqid_ds*buf);
操作选项:
//创建函数
#include
#include
#include
#include
#include
#include
#include
#include
#define MSG_MODE S_IROTH | S_IWOTH
int main(int ac, char *av[])
{
int c, flag, mqid;
key_t key;
if(ac < 2){
fprintf(stderr, "Usage : /path/to/file\n");
exit(-1);
}
flag = MSG_MODE | IPC_CREAT;
while((c = getopt(ac, av,"e")) != -1)
{
switch(c)
{
case 'e':
flag |= IPC_EXCL;
break;
}
}
if(optind != ac-1)
exit(-1);
key = ftok(av[optind], 0);
mqid = msgget(key, flag);
if(mqid < 0){
perror("msgget error");
exit(-1);
}
return 0;
}
//消息发送函数
#include
#include
#include
#include
#include
#include
#include
#include
struct msgbuf{
long mtype;
char mtext[0];
};
int main(int ac, char *av[])
{
int mqid;
int type, len;
struct msgbuf *buf;
if(ac != 4){
fprintf(stderr, "Usage : msgs ");
exit(-1);
}
len = atoi(av[2]);
type = atoi(av[3]);
mqid = msgget(ftok(av[1], 0),0);
if(mqid < 0){
perror("msgget error ");
exit(-1);
}
buf =calloc(sizeof(long)+len, sizeof(char));
buf->mtype = type;
if(msgsnd(mqid, buf, len, 0)< 0){
perror("msgsnderror");
exit(-1);
}
return 0;
}
//消息接收
#include
#include
#include
#include
#include
#include
#include
#include
struct msgbuf{
long mtype;
char mtext[0];
};
int main(int ac, char *av[])
{
int c, flag, mqid;
long type;
ssize_t n;
struct msgbuf *buf;
type = flag = 0;
while((c=getopt(ac, av,"nt:")) != -1)
{
switch(c)
{
case'n': //添加非阻塞标志
flag |= IPC_NOWAIT;
break;
case't': //指定要取出的消息的type
type = atol(optarg);
break;
}
}
if(optind != ac-1)
exit(-1);
mqid =msgget(ftok(av[optind],0), 0);
if(mqid < 0){
perror("msgget error");
exit(-1);
}
buf = (struct msgbuf*)malloc(8192+sizeof(long));
n = msgrcv(mqid, buf,8192+sizeof(long), type, flag);
printf("read %d bytes, type= %d\n", n, buf->mtype);
return 0;
}
//队列删除
#include
#include
#include
#include
#include
#include
#include
#include
int main(int ac, char *av[])
{
int mqid;
if(ac != 2){
fprintf(stderr, "Usage : msgrmid ");
exit(-1);
}
mqid = msgget(ftok(av[1], 0),0);
if(mqid < 0){
perror("msgget error");
exit(-1);
}
msgctl(mqid, IPC_RMID, NULL);
return 0;
}
除了使用msgctl执行队列的删除操作, 我们还可以使用shell命令
ipcrm 来删除System V IPC
示例1: 使用一个消息队列实现服务器
在关于FIFO的章节中, 已经谈到用FIFO实现一个服务器与多个本地客户端交互的模型, 当时使用了一个固定的服务器FIFO, 以及每个客户端自身携带一个FIFO. 在本章消息队列章节中, 我们如果使用消息队列实现此种模型, 只需要使用一个消息队列就足矣.
因为消息队列中的每个消息都有一个type, 且我们可以根据type随机取出其中的某个消息. 不像FIFO或是Posix消息队列一样. 我们就可以通过这一点来用一个消息队列实现服务器与客户端的交互
实现思路:
给每个方向的消息使用不同的消息类型
类型为1的消息是从客户到服务器的消息.
所有其他消息的类型都为服务器到客户端的消息, 每个消息的类型皆对应于每个客户端的PID
这样一来, 服务器每次msgrcv类型为1的消息 ; 客户端每次msgrcv类型为自身PID的消息
示例2: 使用多个消息队列实现服务器
上面, 我们使用一个消息队列实现了服务器与客户端的通信, 现在, 我们让每个客户端通过IPC_PRIVATE生成拥有自己的消息队列.
实现思路:
既然现在每个客户都有了自己的消息队列, 那么我们就要服务器在每次处理完消息后打开客户端的消息队列来传输信息.
既然我们可以通过shell命令ipcs来获取System V IPC的msqid来对消息队列进行处理, 那么这里我们也只需要客户端将此msqid传过来, 服务器就可以不用使用ftok, msgget了, 直接发送就行
Msqid可以放在消息里, 然后在fork时父进程传入子进程处理
这里还使用fork并发实现了此服务器版本
具体实现略过
消息队列与select, poll
如果服务器不仅要处理网络中的请求, 还要处理IPC连接.因为网络的特性, 我们可以使用select和poll来处理,那么在处理IPC时能否使用select或poll呢?
答案是否定的, 因为System V IPC由各自的标识符而不是描述符标识
如果我们有这种需求的话, 就要用到管道+子进程了
之所以需要子进程, 先回忆Posix中这种情况是如何处理的? 当时并没有使用子进程, 而是直接信号+管道,但是System V不支持通知操作, 因此必须阻塞在空消息队列上等待消息的到来, 于是我们使用了子进程.
在一个消息准备好被处理时, msgrcv返回,子进程接着就从中读出消息, 进而将得到的消息通过管道发送给父进程, 父进程select返回从管道读处数据,很明显, 消息被传递了三次. 如果想要避免这种复杂的传输, 那么在子进程与父进程之间创建一段共享内存区也许是不错的选择, 管道则是用来通知
当然, 如果System V支持窥探消息, 例如recv, recvmsg支持的MSG_PEEK的能力, 这样子进程就能在消息到来后侦测到它,从而通过管道提醒父进程去读取, 但System V不支持这个特性
关于MSG_PEEK
另外, 关于MSG_PEEK这个属性, 下面做一个简单的介绍:
比如,server向client发送数据"String1\r\nString2",要求"\r\n"之前的数据String1在第一次recv中接收,剩下的recv调用读取String2部分的数据。因为tcp是stream协议,且String1数据不是定长的,不知到长度, 所以没有办法保证一次recv调用能准确读到String1结尾,除非一次读取一个字符。此时,recv的MSG_PEEK参数就发挥作用。
recv的原型是
ssize_t recv(int s, void *buf, size_t len, int flags);
通常flags都设置为0,此时recv函数读取tcp buffer中的数据到buf中,并从tcp buffer中移除已读取的数据。把flags设置为MSG_PEEK,仅把tcp buffer中的数据读取到buf中,并不把已读取的数据从tcp buffer中移除,再次调用recv仍然可以读到刚才读到的数据。
针对上面的场景,recv(fd, buf,nbuf, MSG_PEEK) 查看数据,查看"\r\n"的位置pos,再recv(fd, buf, pos+2, 0) 读取(并移除)数据。
当然,真正的recv之前调用使用MSG_PEEK导致额外的一次函数调用。