您的位置:新葡亰496net > 电脑系统 > 消息队列,Linux下进程间通信

消息队列,Linux下进程间通信

发布时间:2019-11-10 07:18编辑:电脑系统浏览(125)

    写在前面

    不得不说,Deadline果真是第一生产力。不过做出来的东西真的是不堪入目,于是又花了一早上重写代码。


    Linux下进程间通信大致有以下几种:

    Linux网络编程--进程间通信(一),linux网络编程

    进程间通信简介(摘自《Linux网络编程》p85)

      AT&T 在 UNIX System V 中引入了几种新的进程通讯方式,即消息队列( MessageQueues),信号量( semaphores)和共享内存( shared memory),统称为 System V IPC。在Linux 系统编程中,它们有着广泛的应用。
      System V IPC 的一个显著的特点,是它的具体实例在内核中是以对象的形式出现的,我们称之为 IPC 对象。每个 IPC 对象在系统内核中都有一个唯一的标识符。通过标识符内核可以正确的引用指定的 IPC 对象.。需要注意的是,标识符的唯一性只在每一类的 IPC 对象内成立。比如说,一个消息队列和一个信号量的标识符可能是相同的,但绝对不会出现两个有相同标识符的消息队列。

      标识符只在内核中使用, IPC 对象在程序中是通过关键字( key)来访问的。和 IPC 对象标识符一样,关键字也必须是唯一的。而且,要访问同一个 IPC 对象, Server 和 Client必须使用同一个关键字。因此,如何构造新的关键字使之不和已有的关键字冲突,并保证Server 和 Client 使用的关键字是相同的,是建立 IPC 对象时首先要解决的一个问题。(具体在后边的msg通信中详解)

    通信方法还有:半双工管道pipe,命名管道fifo,消息队列,信号量,共享内,socket套接字等,下面一一介绍:

    ①半双工管道:

      int pipe(int filedes[2]);

      管道是将两个进程之间的标准输入输出相互对接的机制

      linux命令中使用的管道 |  : ls -l | grep *.c  //显示文件(输入端)-(|)-(输出端)>找到.c结尾文件

    实现:因为半双工缘故,所以只能实现一段输入,一段输出,而不能双向通信。所以:实现为,通过管道连接进程,一端开放读文件描述,一端开放写文件描述

    //管道的性质就是,一个进程的输出作为另一个进程的输入
    //那么我们可以关闭一个进程读端使之作为输入端,
    //另一个进程关闭写端,读取数据,接收数据作为管道输出端
    
    //FIFO命名管道
    //文件系统中,命名管道是特殊文件的方式存在的
    //不同进程可以通过命名管道共享数据
    
    //命名管道一直是阻塞方式的,且必须是显示的通过open建立连接到管道的通道
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    
    #include<sys/types.h>
    
    int main()
    {
        int result = 1;
        int fd[2];
        pid_t pid;
        int *write_fd = &fd[1];        //写文件描述
        int *read_fd = &fd[0];        //读文件描述
    
        int nbytes;
        char str[] = "管道,你好n";    
        char readBuffer[80];
        memset(readBuffer,0,sizeof(readBuffer));
    
        result = pipe(fd);        //创建管道
        if(-1==result)
        {
            printf("管道创建失败!n");
            return -1;
        }
    
        pid = fork();            //进程创建分叉程序
        if(-1 == pid)
        {
            printf("fork失败");
            return -1;
        }
    
        if(0==pid)            //子进程关闭读端,写入字符
        {
            close(*read_fd);
            result = write(*write_fd,str,strlen(str));
            printf("写入%d个数据n",result);
        }
        else                //父进程关闭写端,读取数据
        {
            close(*write_fd);
            nbytes = read(*read_fd,readBuffer,sizeof(readBuffer));
            printf("接收到%d个数据,内容为%s",nbytes,readBuffer);
        }
        return 0;
    }
    

    ②命名管道

      int mkfifo(const char* pathname,mode_t mode);

      类似于普通管道,只是

      a.在文件系统中以设备特殊文件的形式存在

      b.不同进程之间可以通过命名管道共享数据

    操作区别于普通管道:FIFO中必须显式通过open建立连接到管道的通道,且总是处于阻塞状态的

    ③消息队列

      消息队列是内核地址空间的内部链表,通过内核在各个进程之间传递内容。每个消息队列通过唯一IPC标识符标识,不同队列相对独立。

      

    //file: msg.h
    /* message buffer for msgsnd and msgrcv calls */
    struct msgbuf {
        __kernel_long_t mtype;          /* type of message */
        char mtext[1];                  /* message text */
    };
    
    /* Obsolete, used only for backwards compatibility and libc5 compiles */
    struct msqid_ds {
        struct ipc_perm msg_perm;
        struct msg *msg_first;        /* first message on queue,unused  */
        struct msg *msg_last;        /* last message in queue,unused */
        __kernel_time_t msg_stime;    /* last msgsnd time */
        __kernel_time_t msg_rtime;    /* last msgrcv time */
        __kernel_time_t msg_ctime;    /* last change time */
        unsigned long  msg_lcbytes;    /* Reuse junk fields for 32 bit */
        unsigned long  msg_lqbytes;    /* ditto */
        unsigned short msg_cbytes;    /* current number of bytes on queue */
        unsigned short msg_qnum;    /* number of messages in queue */
        unsigned short msg_qbytes;    /* max number of bytes on queue */
        __kernel_ipc_pid_t msg_lspid;    /* pid of last msgsnd */
        __kernel_ipc_pid_t msg_lrpid;    /* last receive pid */
    };
    
    //filename
    /* Obsolete, used only for backwards compatibility and libc5 compiles */
    struct ipc_perm
    {
        __kernel_key_t    key;  //函数msgget()使用的键值  
        __kernel_uid_t    uid;  //用户UID
        __kernel_gid_t    gid;  //用户GID
        __kernel_uid_t    cuid;  //创建者UID
        __kernel_gid_t    cgid;  //创建者GID
        __kernel_mode_t    mode;   //权限
        unsigned short    seq;  //序列号
    };
    

      内核中的消息队列

    图片 1

    注:结构list_head 形成一个链表,结构msg_msg之中的m_list使得消息形成链表,查找,插入时,对m_list域进行偏移找到位置

    相关函数:

      键值构建 key_t ftok(const char* pathname,int proj_id);

      获取消息 int msgget(key_t key,int msgflg);

      发送消息 int msgsnd(int msqid, const void * msgp,size_t msgsz,int msgflg);

      接收消息 ssize_t msgrcv(int msqid, void * msgp, size_t msgsz, long msgtype, int msgflg);

      消息控制 int msgctl(int msqid, int cmd, struct msqid_ds *buf);  //向内核发送cmd命令判断进行何种操作

    一个简单例子

    ④信号量

      信号量是一种计数器,用来控制对多个进程共享的资源所进行的访问。常用作锁机制(生产者消费者模型是个典型使用)

      信号量结构

    //filename sys/sem.h
    /* arg for semctl system calls. */
    union semun {
        int val;            /* value for SETVAL */
        struct semid_ds *buf;    /* buffer for IPC_STAT & IPC_SET */
        unsigned short *array;    /* 数组结构 */
        struct seminfo *__buf;    /* 信号量内部结构 */
        void *__pad;
    };
    

      相关函数 

      新建信号量 int semget(key_t key, int nsems, int semflg);

      //key 来自于ftok()

      信号量操作函数 int semop(int semid,struct sembuf* sops, unsigned nsops);

      //信号量的P,V操作通过向已经建立好的信号量发送命令完成

      控制信号量参数

      int semctl(int semid, int semnum ,int cmd,.....);

      //用于在信号量集合上执行控制操作

    #include<stdio.h>
    #include<unistd.h>
    #include<sys/ipc.h>
    #include<sys/sem.h>
    #include<sys/types.h>
    
    typedef int sem_t;
    union semun
    {
        int val;
        struct semid_ds * buf;
        unsigned short *array;
    }arg;
    
    sem_t CreateSem(key_t key, int value)
    {
        union semun sem;
        sem_t semid;
        sem.val = value;
    
        semid = semget(key,0,IPC_CREAT);
        if(-1 == semid)
        {
            printf("create semaphore errorn");
            return -1;
        }
    
        semctl(semid,0,SETVAL,sem);
    
        return semid;
    }
    
    int Sem_P(sem_t semid)
    {
    struct sembuf sops = {0, 1,IPC_NOWAIT};
    return (semop(semid,&sops,1));
    }
    
    int Sem_V(sem_t semid)
    {
        struct sembuf sops = {0,-1,IPC_NOWAIT};
        return (semop(semid,&sops,1));
    }
    
    void SetvalueSem(sem_t semid , int value)
    {
        union semun sem;
        sem.val = value;
        semctl(semid,0,SETVAL,sem);
    }
    
    int GetvalueSem(sem_t semid)
    {
        union semun sem;
        return semctl(semid,0,GETVAL,sem);
    }
    
    void DestroySem(sem_t semid)
    {
        union semun sem;
        sem.val = 0;
        semctl(semid,0,IPC_RMID,sem);
    }
    int main()
    {
        key_t key;
        int semid;
        char i;
        int value = 0;
        key = ftok("/ipc/sem",'a');
    
        semid = CreateSem(key,100);
        for( i = 0;i <= 3;  i)
        {
            Sem_P(semid);
            Sem_V(semid);    
        }
        value = GetvalueSem(semid);    
    
        DestroySem(semid);    
        return 0;
    }
    

    ⑤共享内存(最快捷的方法)没有中间过程,管道等

      在多个进程之间共享内存区域的一种进程间通信方式,在多个进程之间对内存段进行映射的方式实现内存共享。

        相关函数

      创建共享内存函数 int shmget(key_y key, size_t size, int shmflg);

      获得共享内存地址void * shmat(int shmid,const void* shmaddr, int shmflg);

      删除共享内存函数 int shmdt(const void*消息队列,Linux下进程间通信。 shmadddr);

      共享内存控制函数 int shmctl(int shmid ,int cmd, struct shmid_ds * buf);

    ⑥信号

      用于在一个或多个进程之间传递异步信号。

      相关函数

      信号截取 sighandler signal(int signum ,sighandler handler);

      发送信号 int kill(pid_t pid, int sig);

           int raise(int sig);

      

      

      

      

    进程间通信简介(摘自《Linux网络编程》p85) ATT 在 UNIX System V 中引入了几种新的进程通讯...

    IPC进程间通信(Inter-Process Communication)就是指多个进程之间相互通信,交换信息的方法。Linux IPC基本上都是从Unix平台上继承而来的。主要包括最初的Unix IPC,System V IPC以及基于Socket的IPC。另外,Linux也支持POSIX IPC。

    下面来说说如何用不用消息队列来进行进程间的通信,消息队列与命名管道有很多相似之处。有关命名管道的更多内容可以参阅我的另一篇文章:Linux进程间通信 -- 使用命名管道

    实验内容

    进程通信的邮箱方式由操作系统提供形如 send()和 receive()的系统调用来支持,本实验要求学生首先查找资料了解所选用操作系统平台上用于进程通信的系统调用具体形式,然后使用该系统调用编写程序进行进程间的通信,要求程序运行结果可以直观地体现在界面上。在此基础上查找所选用操作系统平台上支持信号量机制的系统调用具体形式,运用生产者与消费者模型设计实现一个简单的信箱,该信箱需要有创建、发信、收信、撤销等函数,至少能够支持两个进程互相交换信息,比较自己实现的信箱与操作系统本身提供的信箱,分析两者之间存在的异同。
    

    1.socket

    System V,BSD,POSIX

    一、什么是消息队列

    消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。  每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。

    Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。

    背景知识

    2.管道包括匿名管道适用于父子进程,命名管道)

        System V是Unix操作系统最早的商业发行版之一。它最初由AT&T(American Telephone & Telegraph)开发,最早在1983年发布。System V主要发行了4个版本,其中SVR4(System V Release 4)是最成功的版本。BSD(Berkeley Software Distribution,有时也被称为Berkeley Unix)是加州大学于1977至1995年间开发的。在19世纪八十年代至九十年代之间,System V和BSD代表了Unix的两种主要的操作风格。它们的主要区别如下:

    二、在Linux中使用消息队列

    Linux提供了一系列消息队列的函数接口来让我们方便地使用它来实现进程间的通信。它的用法与其他两个System V PIC机制,即信号量和共享内存相似。

    1、msgget()函数

    该函数用来创建和访问一个消息队列。它的原型为:

    int msgget(key_t, key, int msgflg);

    与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。

    它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1.

    2、msgsnd()函数

    该函数用来把消息添加到消息队列中。它的原型为:

    int msgsend(int msgid, const void *msg_ptr, size_消息队列,Linux下进程间通信。t msg_sz, int msgflg);

    msgid是由msgget函数返回的消息队列标识符。

    msg_ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样: 

    struct my_message {
        long int message_type;
        /* The data you wish to transfer */
    };
    

    msg_sz 是msg_ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员变量的长度。

    msgflg 用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。

    如果调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1.

    3、msgrcv()函数

    该函数用来从一个消息队列获取消息,它的原型为

    int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);

    msgid, msg_ptr, msg_st 的作用也函数msgsnd()函数的一样。

    msgtype 可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。

    msgflg 用于控制当队列中没有相应类型的消息可以接收时将发生的事情。

    调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1。

    4、msgctl()函数

    该函数用来控制消息队列,它与共享内存的shmctl函数相似,它的原型为:

    int msgctl(int msgid, int command, struct msgid_ds *buf);

    command是将要采取的动作,它可以取3个值,

    • IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
    • IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
    • IPC_RMID:删除消息队列

    buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员: 

    struct msgid_ds
    {
        uid_t shm_perm.uid;
        uid_t shm_perm.gid;
        mode_t shm_perm.mode;
    };
    

    成功时返回0,失败时返回-1.

      消息队列

    • 什么是消息队列
      消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
      Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。

    • Linux中如何使用消息队列
      Linux提供了一系列消息队列的函数接口来让我们方便地使用它来实现进程间的通信。它的用法与其他两个System V PIC机制,即信号量和共享内存相似。

      • msgget()函数
        该函数用来创建和访问一个消息队列。它的原型为:
        int msgget(key_t key, int msgflg);
        它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1.
      • msgsnd()函数
        该函数用来把消息添加到消息队列中。它的原型为:
        int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
        如果调用成功,消息数据的一份副本将被放到消息队列中,并返回0,失败时返回-1.
      • msgrcv()函数
        该函数用来从一个消息队列获取消息,它的原型为:
        int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
        调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1。
      • msgctl()函数
        该函数用来控制消息队列,它与共享内存的shmctl函数相似,它的原型为:
        int msgctl(int msgid, int command, struct msgid_ds *buf);
        成功时返回0,失败时返回-1.

    3.信号量

        系统                      System V           BSD
        root脚本位置            /etc/init.d/       /etc/rc.d/
        默认shell                 Bshell             Cshell
        文件系统数据            /etc/mnttab     /etc/mtab
        内核位置                  /UNIX             /vmUnix
        打印机设备                lp                  rlp
        字符串函数                memcopy       bcopy
        终端初始化设置文件    /etc/initab       /etc/ttys
        终端控制                  termio            termios

    三、使用消息队列进行进程间通信

    马不停蹄,介绍完消息队列的定义和可使用的接口之后,我们来看看它是怎么让进程进行通信的。由于可以让不相关的进程进行行通信,所以我们在这里将会编写两个程序,msgreceive()和msgsned()来表示接收和发送信息。根据正常的情况,我们允许两个程序都可以创建消息,但只有接收者在接收完最后一个消息之后,它才把它删除。

    接收信息的程序源文件为msgreceive.c的源代码为:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/msg.h>
    #include <errno.h>
    
    struct msg_st
    {
     long int msg_type;
     char text[BUFSIZ];
    };
    
    int main(int argc, char **argv)
    {
     int msgid = -1;
     struct msg_st data;
     long int msgtype = 0; // 注意1
    
     // 建立消息队列
     msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
     if (msgid == -1)
     {
      fprintf(stderr, "msgget failed width error: %dn", errno);
      exit(EXIT_FAILURE);
     }
    
     // 从队列中获取消息,直到遇到end消息为止
     while (1)
     {
      if (msgrcv(msgid, (void *)&data, BUFSIZ, msgtype, 0) == -1)
      {
       fprintf(stderr, "msgrcv failed width erro: %d", errno);
      }
    
      printf("You wrote: %sn", data.text);
    
      // 遇到end结束
      if (strncmp(data.text, "end", 3) == 0)
      {
       break;
      }
     }
    
     // 删除消息队列
     if (msgctl(msgid, IPC_RMID, 0) == -1)
     {
      fprintf(stderr, "msgctl(IPC_RMID) failedn");
     }
    
     exit(EXIT_SUCCESS);
    }
    

    发送信息的程序的源文件msgsend.c的源代码为:

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/msg.h>
    #include <errno.h>
    
    #define MAX_TEXT 512
    
    struct msg_st
    {
     long int msg_type;
     char text[MAX_TEXT];
    };
    
    int main(int argc, char **argv)
    {
     struct msg_st data;
     char buffer[BUFSIZ];
     int msgid = -1;
    
     // 建立消息队列
     msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
     if (msgid == -1)
     {
      fprintf(stderr, "msgget failed error: %dn", errno);
      exit(EXIT_FAILURE);
     }
    
     // 向消息队里中写消息,直到写入end
     while (1)
     {
      printf("Enter some text: n");
      fgets(buffer, BUFSIZ, stdin);
      data.msg_type = 1; // 注意2
      strcpy(data.text, buffer);
    
      // 向队列里发送数据
      if (msgsnd(msgid, (void *)&data, MAX_TEXT, 0) == -1)
      {
       fprintf(stderr, "msgsnd failedn");
       exit(EXIT_FAILURE);
      }
    
      // 输入end结束输入
      if (strncmp(buffer, "end", 3) == 0)
      {
       break;
      }
    
      sleep(1);
     }
    
     exit(EXIT_SUCCESS);
    }
    

    运行结果如下:

    图片 2

      信号量

    • 什么是信号量
      为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
    • Linux的信号量机制
      Linux提供了一组精心设计的信号量接口来对信号量进行操作,它们不只是针对二进制信号量,下面将会对这些函数进行介绍,但请注意,这些函数都是用来对成组的信号量值进行操作的。它们声明在头文件sys/sem.h中。
      • semget()函数
        它的作用是创建一个新信号量或取得一个已有信号量,原型为:
        int semget(key_t key, int num_sems, int sem_flags);
        成功返回一个相应信号标识符(非零),失败返回-1.
      • semop()函数
        它的作用是改变信号量的值,原型为:
        int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
      • semctl()函数
        该函数用来直接控制信号量信息,它的原型为:
        int semctl(int sem_id, int sem_num, int command, ...);

    4.共享内存

        Linux系统的操作风格往往介于这两种风格之间。

    四、例子分析——消息类型

      共享内存

    • 什么是共享内存
      顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc()分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
      特别提醒:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如前面说到的信号量。
    • 共享内存的使用
      与信号量一样,在Linux中也提供了一组函数接口用于使用共享内存,而且使用共享共存的接口还与信号量的非常相似,而且比使用信号量的接口来得简单。它们声明在头文件 sys/shm.h 中。
      • shmget()函数
        该函数用来创建共享内存,它的原型为:
        int shmget(key_t key, size_t size, int shmflg);
        成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.
      • shmat()函数
        第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:
        void *shmat(int shm_id, const void *shm_addr, int shmflg);
        成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.
      • shmdt()函数
        该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:
        int shmdt(const void *shmaddr);
        调用成功时返回0,失败时返回-1.
      • shmctl()函数
        与信号量的semctl()函数一样,用来控制共享内存,它的原型如下:
        int shmctl(int shm_id, int command, struct shmid_ds *buf);

    5.消息队列

        POSIX(Portable Operating System Interface [for Unix])是由IEEE(Institute of Electrical and Electronics Engineers,电子电气工程协会)开发的。现有的大部分Unix都遵循POSIX标准,而Linux从一开始就遵循POSIX标准。

    这里主要说明一下消息类型是怎么一回事,注意msgreceive.c文件main()函数中定义的变量msgtype(注释为注意1),它作为msgrcv()函数的接收信息类型参数的值,其值为0,表示获取队列中第一个可用的消息。再来看看msgsend.c文件中while循环中的语句data.msg_type

    1(注释为注意2),它用来设置发送的信息的信息类型,即其发送的信息的类型为1。所以程序msgreceive()能够接收到程序msgsend()发送的信息。

    如果把注意1,即msgreceive.c文件main()函数中的语句由long int msgtype = 0;改变为long int msgtype = 2;会发生什么情况,msgreceive()将不能接收到程序msgsend()发送的信息。因为在调用msgrcv()函数时,如果msgtype(第四个参数)大于零,则将只获取具有相同消息类型的第一个消息,修改后获取的消息类型为2,而msgsend()发送的消息类型为1,所以不能被msgreceive()程序接收。重新编译msgreceive.c文件并再次执行,其结果如下:

    图片 3

    我们可以看到,msgreceive并没有接收到信息和输出,而且当msgsend输入end结束后,msgreceive也没有结束,通过jobs命令我们可以看到它还在后台运行着。

      参考资料

      以上资料全部来源于以下网站:

    • Linux进程间通信(五):信号量 semget()、semop()、semctl()
    • Linux进程间通信(六):共享内存 shmget()、shmat()、shmdt()、shmctl()
    • Linux进程间通信(七):消息队列 msgget()、msgsend()、msgrcv()、msgctl()

    1.socket

    最初的Unix IPC

    五、消息队列与命名管道的比较

    消息队列跟命名管道有不少的相同之处,通过与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发送数据用write(),接收数据用read(),则在消息队列中,发送数据用msgsnd(),接收数据用msgrcv()。而且它们对每个数据都有一个最大长度的限制。

    与命名管道相比,消息队列的优势在于:

    1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。

    2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。

    3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

     

     

    参考:

    《Linux高性能服务器编程》

    《UNIX网络编程_卷2_进程间通信》

      实验结果

    • 消息队列
      图片 4
    • 信号量 共享内存
      图片 5

    socket可以用于本地进程间通信也可以用于远程间,是最常用的。

    1、信号

      完整代码

      Linux-interProcessCommunication
      如果对你有帮助点个star吧(●'◡'●)

    2.管道--匿名管道

        信号是Unix/Linux系统在一定条件下生成的事件。信号是一种异步通信机制,进程不需要执行任何操作来等待信号的到达。信号异步通知接收信号的进程发生了某个事件,然后操作系统将会中断接收到信号的进程的执行,转而去执行相应的信号处理程序。

      总结

    • 不足

      • 没有图形化界面
      • 用信号量和共享内存实现的进程通信只能发送数字消息
      • 创建共享内存空间时,设置权限为了省事设置为0666( 每个进程可读和可写),应该要设置user只能读而不能写,other只能写而不能读
      • 消息队列没有测试msgrcv()函数通过改变msgtype参数来改变接收优先级。
        > msgtype 可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。

      • ...

    • 两种方式实现进程间通信的异同

      • 异:消息队列不需要信号量来控制同步和互斥问题,并且可以很方便的改变接收优先级,而共享内存则只能简单的接收按时间排序的消息。
      • 同:一个进程接收到的消息都和另一个进程发送的相同。
    • 心得
      • 由于Linux提供的信号量接口函数都是针对一组信号量进行操作的,因此参数中大部分都需要指定对一组中的哪一个信号量进行操作
      • 共享内存只存放消息缓存区,至于信箱头的那些值仍然存放在各自进程中。

    如有不足,欢迎指正!

    一般用pipe创建,在父子间通信。在父进程中关闭读/写管道,在子进程中关闭写/读管道。

        (1)注册信号处理函数
            #include <signal.h>
            /*typedef void (*sighandler_t)(int);  sighandler_t signal(int signum,sighandler_t handler);*/
            * void (*signal(int signum, void (*handler)(int)))(int);  //SIG_IGN && SIG_DFL
            * int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

    2.管道--命名管道

        (2)发送信号
            #include <signal.h>
            * int kill(pid_t pid,int sig); //#include <sys/types.h> 
            * int raise(int sig);            //kill(getpid(),sig);
            * unsigned int alarm(unsigned int seconds); //(#include <unistd.h>) seconds秒后,向进程本身发送SIGALRM信号。

    一般用mkfifo来创建。如果没有说明管道是非阻塞的,那么一个为读打开的管道将阻塞直到有进程为写打开此FIFO。同样如果为写打开的管道将阻塞直到有进程为读打开此FIFO。

        (3)信号集
            信号集被定义为:typedef struct {unsigned long sig[_NSIG_WORDS];} sigset_t;
            * int sigaddset(sigset_t *set,int sig);
            * int sigemptyset(sigset_t *set);

    3.信号量

    2、管道(Pipe)

    一个进程可以用kill发送信号至另外一个进程。

        管道用来连接不同进程之间的数据流。

    4.共享内存

        (1)在两个程序之间传递数据的最简单的方法是使用popen()和pclose()函数:
            #include <stdio.h>
            FILE *popen(const char *command, const char *open_mode);
            int pclose(FILE *stream);
        popen()函数首先调用一个shell,然后把command作为参数传递给shell。这样每次调用popen()函数都需要启动两个进程;但是由于在Linux中,所有的参数扩展(parameter expansion)都是由shell执行的,这样command中包含的所有参数扩展都可以在command程序启动之前完成。

    使用函数:shmget、shmat、shmctl、shmdt

        (2)pipe()函数:
            #include <unistd.h>
            int pipe(int pipefd[2]);
        popen()函数只能返回一个管道描述符,并且返回的是文件流(file stream),可以使用函数fread()和fwrite()来访问。pipe()函数可以返回两个管道描述符:pipefd[0]和pipefd[1],任何写入pipefd[1]的数据都可以从pipefd[0]读回;pipe()函数返回的是文件描述符(file descriptor),因此只能使用底层的read()和write()系统调用来访问。pipe()函数通常用来实现父子进程之间的通信。

    用shmget函数得到一块内存,并用shmat设置为允许本进程使用这块共享内存。使用完以后用shmdt删除内存块。

        (3)命名管道:FIFO
            #include <sys/types.h>
            #include <sys/stat.h>
            int mkfifo(const char *fifo_name, mode_t mode);
        前面两种管道只能用在相关的程序之间,使用命名管道可以解决这个问题。在使用open()打开FIFO时,mode中不能包含O_RDWR。mode最常用的是O_RDONLY,O_WRONLY与O_NONBLOCK的组合。O_NONBLOCK影响了read()和write()在FIFO上的执行方式。

    5.消息队列

        PS:要想查看库函数用法,最可靠的资料来自Linux manual page:

    使用函数:msgget、msgsnd、msgrcv、msgctl

        $sudo apt-get install manpages-dev

    使用步骤同上。  

        $man 3 function_name

    1.socket 2.管道包括匿名管道适用于父子进程,命名管道) 3.信号量 4.共享内存 5.消息队列 1.socket socket可...

     

    System V IPC

        System V IPC指的是AT&T在System V.2发行版中引入的三种进程间通信工具:(1)信号量,用来管理对共享资源的访问 (2)共享内存,用来高效地实现进程间的数据共享 (3)消息队列,用来实现进程间数据的传递。我们把这三种工具统称为System V IPC的对象,每个对象都具有一个唯一的IPC标识符(identifier)。要保证不同的进程能够获取同一个IPC对象,必须提供一个IPC关键字(IPC key),内核负责把IPC关键字转换成IPC标识符。   

        System V IPC具有相似的语法,一般操作如下:

        (1)选择IPC关键字,可以使用如下三种方式:

           a)IPC_PRIVATE。由内核负责选择一个关键字然后生成一个IPC对象并把IPC标识符直接传递给另一个进程。
           b)直接选择一个关键字。
           c)使用ftok()函数生成一个关键字。

        (2)使用semget()/shmget()/msgget()函数根据IPC关键字key和一个标志flag创建或访问IPC对象。如果key是IPC_PRIVATE;或者key尚未与已经存在的IPC对象相关联且flag中包含IPC_CREAT标志,那么就会创建一个全新的IPC对象。

        (3)使用semctl()/shmctl()/msgctl()函数修改IPC对象的属性。

        (4)使用semctl()/shmctl()/msgctl()函数和IPC_RMID标志销毁IPC实例。

        System V IPC为每个IPC对象设置了一个ipc_perm结构体并在创建IPC对象的时候进行初始化。这个结构体中定义了IPC对象的访问权限和所有者:

        struct ipc_perm{
           uid_t uid;   //所有者的用户id
           gid_t gid;   //所有者的组id
           uid_t cuid;  //创建者的用户id
           gid_t cgid;  //创建者的组id
           mode_t mode; //访问模式
           …
        };

        shell中管理IPC对象的命令是ipcs、ipcmk和ipcrm。

    1、信号量(Semaphores)

        System V的信号量集表示的是一个或多个信号量的集合。内核为每个信号量集维护一个semid_ds数据结构,而信号量集中的每个信号量使用一个无名结构体表示,这个结构体至少包含以下成员:
        struct{
            unsigned short semval;//信号量值,总是>=0
            pid_t sempid;  //上一次操作的pid
           …
        };

        #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/sem.h>
        (1)创建或访问信号量
            * int semget(key_t key,int nsems,int flag); 
        nsems指定信号量集中信号量的个数,如果只是获取信号量集的标识符(而非新建),那么nsems可以为0。flag的低9位作为信号量的访问权限位,类似于文件的访问权限;如果flag中同时指定了IPC_CREAT和IPC_EXCL,那么如果key已与现存IPC对象想关联的话,函数将会返回EEXIST错误。例如,flag可以为IPC_CREAT|0666。

        (2)控制信号量集
            * int semctl(int semid,int semnum,int cmd,union semun arg);
        对semid信号量集合执行cmd操作;cmd常用的两个值是:SETVAL初始化第semnum个信号量的值为arg.val;IPC_RMID删除信号量。

        (3)对一个或多个信号量进行操作
            * int semop(int semid,struct sembuf *sops,unsigned nsops);
            * struct sembuf{
                  unsigned short sem_num;  //信号量索引
                  short   sem_op;     //对信号量进行的操作,常用的两个值为-1和 1,分别代表P、V操作
                  short   sem_flag;   //比较重要的值是SEM_UNDO:当进程结束时,相应的操作将被取消;同时,如果进程结束时没有释放资源的话,系统会自动释放
               };

    2、共享内存

        共享内存允许两个或多个进程共享一定的存储区,因为不需要拷贝数据,所以这是最快的一种IPC。

        #include <sys/ipc.h>
        #include <sys/shm.h>
        (1)创建或访问共享内存
            * int shmget(key_t key,size_t size,int shmflg);

        (2)附加共享内存到进程的地址空间
            * void *shmat(int shmid,const void *shmaddr,int shmflg);//shmaddr通常为NULL,由系统选择共享内存附加的地址;shmflg可以为SHM_RDONLY

        (3)从进程的地址空间分离共享内存
            * int shmdt(const void *shmaddr); //shmaddr是shmat()函数的返回值

        (4)控制共享内存
            * int shmctl(int shmid,int cmd,struct shmid_ds *buf);
            * struct shmid_ds{
                  struct ipc_perm shm_perm;
                  …
              }; 
        cmd的常用取值有:(a)IPC_STAT获取当前共享内存的shmid_ds结构并保存在buf中(2)IPC_SET使用buf中的值设置当前共享内存的shmid_ds结构(3)IPC_RMID删除当前共享内存

    3、消息队列

        消息队列保存在内核中,是一个由消息组成的链表。

        #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/msg.h>
        (1)创建或访问消息队列
        * int msgget(key_t key,int msgflg);

        (2)操作消息队列
            * int msgsnd(int msqid,const void *msg,size_t nbytes,int msgflg);
        msg指向的结构体必须以一个long int成员开头,作为msgrcv()的消息类型,必须大于0。nbytes指的是msg指向结构体的大小,但不包括long int部分的大小
            * ssize_t msgrcv(int msqid,void *msg,size_t nbytes,long msgtype,int msgflg);
        如果msgtype是0,就返回消息队列中的第一个消息;如果是正整数,就返回队列中的第一个该类型的消息;如果是负数,就返回队列中具有最小值的第一个消息,并且该最小值要小于等于msgtype的绝对值。

        (3)控制消息队列
            * int msgctl(int msqid,int cmd,struct msqid_ds *buf);
            * struct msqid_ds{
                  struct ipc_perm msg_perm;
                  …
               };

    Socket
      套接字(Socket)是由Berkeley在BSD系统中引入的一种基于连接的IPC,是对网络接口(硬件)和网络协议(软件)的抽象。它既解决了无名管道只能在相关进程间单向通信的问题,又解决了网络上不同主机之间无法通信的问题。

      套接字有三个属性:域(domain)、类型(type)和协议(protocol),对应于不同的域,套接字还有一个地址(address)来作为它的名字。

      域(domain)指定了套接字通信所用到的协议族,最常用的域是AF_INET,代表网络套接字,底层协议是IP协议。对于网络套接字,由于服务器端有可能会提供多种服务,客户端需要使用IP端口号来指定特定的服务。AF_UNIX代表本地套接字,使用Unix/Linux文件系统实现。

      IP协议提供了两种通信手段:流(streams)和数据报(datagrams),对应的套接字类型(type)分别为流式套接字和数据报套接字。流式套接字(SOCK_STREAM)用于提供面向连接、可靠的数据传输服务。该服务保证数据能够实现无差错、无重复发送,并按顺序接收。流式套接字使用TCP协议。数据报套接字(SOCK_DGRAM)提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP协议。

      一种类型的套接字可能可以使用多于一种的协议来实现,套接字的协议(protocol)属性用于指定一种特定的协议。

    [ ](http://www.ericbess.com/ericblog/2008/03/03/wp-codebox/#examples)

    总结:

     

     

     

    System V IPC API

     

    1,消息队列

    int ftok(const char *pathname, int prj_id);

    int msgget(key_t key,int msgflag);

    int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);

    int msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);

     

    2,信号量

    int semget(key_t key,int nsems,int semflag);

    int semctl(int semid,int semnum,int cmd,…);

    int semop(int semid,struct sembuf *sops,unsigned nsops,struct timespec *timeout);

     

    3,共享内存

    int shmget(key_t key,size_t size,int shmflag);

    int shmctl(int shmid,int cmd,struct shmid_ds *buf);

      

    POSIX IPC 

    本文由新葡亰496net发布于电脑系统,转载请注明出处:消息队列,Linux下进程间通信

    关键词:

上一篇:新葡亰496net入门操作,功底知识

下一篇:没有了