Linux进程间通信(IPC)-消息队列(转)

转:http://blog.sina.com.cn/s/blog_7f98bac10100s91d.html

进程间通信——消息队列

 

进程间通信——消息队列

System V IPC包括三种进程通信的方式:消息队列、信号量、共享内存,是一种较老的方式。

管道和FIFO是基于文件系统的,而System V IPC是基于系统内核的,用ipcs可以查看系统当前的IPC对象的状态。

  消息队列是一个存放在内核中的消息链表,由消息队列标识符标识。它克服了信号传递信息量少、管道所传送的是无格式字节流以及缓冲区大小受到限制的缺点。由于消息队列存放在内核中,所以只有在内核重启(OS重启)的时候或者显式的删除一个消息队列时,它才能够真正的被删除。

  消息队列与管道的区别:最主要的区别是管道通信是要求两个进程之间要有亲缘关系,
而消息队列当中,通信的两个进程之间可以是完全无关的进程。

操作消息队列时,用到的重要的数据结构:

(1)消息缓冲结构(定义在linux/msg.h中)

struct messagebuf{

long messageType; //消息类型,正整数 

char messageText; //消息内容,可以为任意类型,由用户自定义

}消息的大小是受限制的,由<linux/msg.h>中的MSGMAX限制;

(2)msqid_ds 此结构体保存消息队列当前的状态信息;

(3)ipc_perm 保存消息队列的重要信息(包括键值、用户id、组id等)。

 

操作消息队列的一些函数:

(1)key_t ftok(const char *pathname,int proj_id); //获取键值(每个消息队列在系统范围内对应唯一的键值)proj_id 的取值范围是1-255。

(2)int msgget(key_t key,int msgflg);       //创建消息队列

key 调用ftok获得,msgflg 可能的取值为IPC_CREAT,IPC_EXCL

(3)int msgsnd(int msgid,struct msgbuf *msgbuf,size_t msgsize,int msgflag); //发送

msgid 消息队列标识符                msgbuf 指向要发送的消息

msgsize 消息的大小                  msgflag 操作标志位

(4)int msgrcv(int msgid,struct msgbuf *msgbuf,size_t msgsize,long int msgtype,int msgflag);//接收    接收消息时需要一个同类型的消息缓冲结构、要接收消息队列的标识符、要接收消息的类型

msgtype用来指定要接收的消息,分三种情况:

等于 0 返回消息队列中的第一个消息

大于0  返回消息队列中类型为msgtype的第一个消息

小于0 返回消息队列中类型值小于等于msgtype绝对值的消息中类型值最小的第一条消息

(5)获取和设置消息队列的属性

int msgctl(int msgid,int cmd,struct msqid_ds *buf);

cmd 有三种操作:IPC_STAT 获取消息队列当前的状态信息,保存到buf指向的空间

                IPC_SET  设置消息队列的属性

                IPC_RMID 从内核中删除msgid标识的消息队列

练习:

1.利用read/write 访问生成了具有读/写权限的5 个消息队列,并用ipcs 命令(通过一个管道)显示消息队列的状态,然后删除该消息队列。

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

#define BUF_SIZE 1024
#define PATH_NAME "."
#define PROJ_ID 32
#define MAX_SIZE 5120

int main(void)
{
    struct mymsgbuf
    {
        long msgtype;
        char message[BUF_SIZE];
    }msgbuf;

    int queueid,i;
    int msglen;
    key_t key;
    FILE *fp;
    char string[BUF_SIZE];

    //获取键值
    if((key = ftok(PATH_NAME,PROJ_ID)) == -1)
    {
        perror("ftok error!\n");
        exit(1);
    }
    //创建消息队列或者获取消息队列标识符
    if((queueid = msgget(key,IPC_CREAT|0660)) == -1)
    {
        perror("msgget error!\n");
        exit(1);
    }
   
    //发送5条消息到消息队列
    for(i = 0; i < 5; i++)
    {
        msgbuf.msgtype = i+1;
        strcpy(msgbuf.message,"Goodnight,see you later!");
        msglen = sizeof(struct mymsgbuf) - 4;

        if(msgsnd(queueid,&msgbuf,msglen,0) == -1)
        {
            perror("msgsnd error!\n");
            exit(1);
        }
    }

    //调用popen函数,开启了一个进程
    if((fp = popen("ipcs","r"))== NULL)
    {
        perror("popen error!\n");
        exit(1);
    }
   
    //调用read读出管道中的数据
    if(read(fileno(fp),string,MAX_SIZE) == -1)
    {
        perror("read error!\n");
        exit(1);
    }
    else
        printf("%s\n",string);

    //读取消息队列
    for(i = 1; i < 6; i++ )
    {
        if((msgrcv(queueid,&msgbuf,BUF_SIZE,i,0)) == -1)
        {
            perror("msgrcv error!\n");
            exit(1);
        }else
            printf("Get message :%s,message type is %ld\n",msgbuf.message,msgbuf.msgtype);
    }

    //删除消息队列   
    if((msgctl(queueid,IPC_RMID,NULL)) == -1)
    {
        perror("delete msgctl failed!\n");
        exit(1);
    }

    //处理popen开启的进程
    pclose(fp);
}

 


2.通过消息队列实现两个进程之间的通信。
题目描述:编写两个程序,其中,一个进程从消息队列接收消息,另外一个进程发送消息。
编写两个程序,分别对应”send.c”和”receive.c”,其中”send.c”是发送消息,而”receive.c”是从消息队列当中接收消息。每一条消息是用户输入的一个任意字符串,字符串”end”表示输入结束。消息队列的关键字可以设为1234。

sen.c

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/stat.h>

#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "/tmp"
#define SEND_MSG 1
#define RECEIVE_MSG 2

int main(void)
{
    struct mymsgbuf
    {
        long msgtype;
        char message[BUF_SIZE];
    }msgbuf;

    int queueid;
    int msglen;


    if((queueid = msgget(1234,IPC_CREAT|0660)) == -1)
    {
        perror("msgget error!\n");
        exit(1);
    }

    while(1)
    {
        printf("send:");
        fgets(msgbuf.message,BUF_SIZE,stdin);//从标准输入读取字符存至message
       
        if(strncmp("end",msgbuf.message,3) == 0)
        {
            msgctl(queueid,IPC_RMID,NULL);
            break;
        }
        msgbuf.message[strlen(msgbuf.message)-1] = '\0';
        msgbuf.msgtype = SEND_MSG;

        if(msgsnd(queueid,&msgbuf,strlen(msgbuf.message)+1,0) == -1)
        {
            perror("Send msgsnd error!\n");
            exit(1);
        }

        if(msgrcv(queueid,&msgbuf,BUF_SIZE,RECEIVE_MSG,0) == -1)  //缓冲区的大小,请求读取消息的类型
        {
            perror("Send msgrcv error\n");
            exit(1);
        }

        printf("receive: %s\n",msgbuf.message);
    }

    exit(0);
}

receive.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/stat.h>

#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "/tmp"
#define SEND_MSG 1
#define RECEIVE_MSG 2

int main(void)
{
    struct mymsgbuf
    {
        long msgtype;
        char message[BUF_SIZE];
    }msgbuf;

    int queueid;
    int msglen;


    if((queueid = msgget(1234,IPC_CREAT|0660)) == -1)
    {
        perror("msgget eror!\n");
        exit(1);
    }

    while(1)
    {
        if(msgrcv(queueid,&msgbuf,BUF_SIZE,SEND_MSG,0) == -1)
        {
            perror("Send msgrcv error!\n");
            exit(1);
        }

        printf("send:%s\n",msgbuf.message);
        printf("receive:");
        fgets(msgbuf.message,BUF_SIZE,stdin);

        if(strncmp("end",msgbuf.message,3) == 0)
        {
            msgctl(queueid,IPC_RMID,NULL);
            break;
        }
        msgbuf.message[strlen(msgbuf.message)-1]='\0';
        msgbuf.msgtype = RECEIVE_MSG;

        if(msgsnd(queueid,&msgbuf,strlen(msgbuf.message)+1,0) == -1)
        {
            perror("reveive msgsnd error!\n");
            exit(1);
        }
    }

    exit(0);
}

这两个程序基本和linux c实战上的server.c 和client.c一样,send和receive可任意发送消息,当任意一方输入end时结束会话。至于从队列中,按照正常的顺序、阻塞方式反复接收消息不太明白这几种方式是怎么实现的。

popen 

进程I/O函数,与pclose函数一起使用,头文件#include <stdio.h>

函数原型:
FILE * popen ( const char * command , const char * type );   

int pclose ( FILE * stream );

说明:  
popen() 函数通过创建一个管道,调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程。这个进程必须由 pclose() 函数关闭,而不是 fclose() 函数。pclose() 函数关闭标准 I/O 流,等待命令执行结束,然后返回 shell 的终止状态。如果 shell 不能被执行,则 pclose() 返回的终止状态与 shell 已执行 exit 一样。 
 type 参数只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 "r" 则文件指针连接到 command 的标准输出;如果 type 是 "w" 则文件指针连接到 command 的标准入。  
   
command 参数是一个指向以 NULL 结束的 shell 命令字符串的指针。 这行命令将被传到 bin/sh 并使用-c 标志,shell 将执行这个命令。  
  popen 的返回值是个标准 I/O 流,必须由 pclose 来终止。
前面提到这个流是单向的。所以向这个流写内容相当于写入该命令的标准输入;命令的标准输出和调用 popen 的进程相同。与之相反的,从流中读数据相当于读取命令的标准输出;命令的标准输入和调用 popen 的进程相同。

   返回值: 如果调用 fork() 或 pipe() 失败,或者不能分配内存将返回NULL,否则返回标准 I/O 流。

测试执行完popen后有几个进程:

#include<stdio.h>

int main(void)
{
    FILE *fp;

    fp = popen("ipcs","r");

    system("ps");
    pclose(fp);
    system("ps");
}

运行结果:

PID TTY          TIME CMD
 4451 pts/0    00:00:00 bash
 4545 pts/0    00:00:00 a
 4546 pts/0    00:00:00 sh <defunct>     //僵尸进程
 4547 pts/0    00:00:00 sh
 4549 pts/0    00:00:00 ps
  PID TTY          TIME CMD
 4451 pts/0    00:00:00 bash
 4545 pts/0    00:00:00 a
 4550 pts/0    00:00:00 sh
 4551 pts/0    00:00:00 ps

 

posted @ 2013-10-01 23:06  kaijia9  阅读(204)  评论(0)    收藏  举报