• Linux进程间通信 -- 消息队列


    0. 前言

       进程是一个独立的资源管理单元,不同进程间的资源是独立的,不能在一个进程中访问另一个进程的用户空间和内存空间。但是,进程不是孤立的,不同进程之间需要信息的交互和状态的传递,因此需要进程间数据的传递、同步和异步的机制。

        当然,这些机制不能由哪一个进程进行直接管理,只能由操作系统来完成其管理和维护,Linux提供了大量的进程间通信机制,包括同一个主机下的不同进程和网络主机间的进程通信,如下图所示:
    mark

    • 同主机间的信息交互
    • 无名管道
      特点:多用于亲缘关系进程间通信,方向为单向;为阻塞读写;通信进程双方退出后自动消失
      问题:多进程用同一管道通信容易造成交叉读写的问题
    • 有名管道
      FIFO(First In First Out),方向为单向(双向需两个FIFO),以磁盘文件的方式存在;通信双方一方不存在则阻塞
    • 消息队列
      可用于同主机任意多进程的通信,但其可存放的数据有限,应用于少量的数据传递
    • 共享内存
      可实现同主机任意进程间大量数据的通信,但多进程对共享内存的访问存在着竞争
    • 同主机进程间同步机制:信号量(Semaphore)
    • 同主机进程间异步机制:信号(Signal)
    • 网络主机间数据交互:Socket(套接字)

    1. IPC

    IPC, Inter-Process Communication,进程间通信,包括消息队列、信号量和共享内存三种机制。
    IPC使用前必须要先创建,每种IPC都有其创建者、所有者和访问权限。
    使用ipcs可以查看系统中的IPC工具:

    [niesh@niesh ~]$ ipcs
    
    --------- 消息队列 -----------
    键        msqid      拥有者  权限     已用字节数 消息
    
    ------------ 共享内存段 --------------
    键        shmid      拥有者  权限     字节     nattch     状态
    0x00000000 131072     niesh      600        524288     2          目标
    0x00000000 163841     niesh      600        4194304    2          目标
    
    --------- 信号量数组 -----------
    键        semid      拥有者  权限     nsems
    
    
    • key:
      用于创建ID值(ID值由一个进程创建的话,由于进程资源的私有性,另一个进程无法获取到该ID);采用统一key值创建的ID是相同的;
    • id:
      IPC机制的唯一标识

    1). 获取key值 - ftok():

    • 作用
      获取key值

    • 头文件

        #include <sys/ipc.h>
      
    • 函数原型

        key_t ftok(const char *pathname, int proj_id)
      
    • 参数

    pathname:文件名
    proj_id: 作为key值的组成部分,用到了低8位

    • 返回值
      成功:key值
    bit 描述
    31-24 proj_id & 0xFF (低8位)
    23-16 stat(pathname).st_dev & 0xFF (低8位)
    15-0 stat(pathname).st_ino & 0xFFFF (低16位)

    失败:-1

    几个结构体需要详细了解:

    • struct msqid_ds: 消息队列数据结构
    • struct msg: 单个消息的数据结构
    • struct msgbuf: 用户自定义消息缓冲区
    • struct msginfo:

    2. 消息队列

    1). 常用数据结构

    mark

    ①. struct msqid_ds

    /* FILE: /usr/include/linux/msg.h   */
    
    /* 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 */
    };
    
    
    /* FILE: /usr/include/bits/ipc.h    */
    
    /* Data structure used to pass permission information to IPC operations.  */
    struct ipc_perm
      {
        __key_t __key;          /* Key.  */
        __uid_t uid;            /* Owner's user ID.  */
        __gid_t gid;            /* Owner's group ID.  */
        __uid_t cuid;           /* Creator's user ID.  */
        __gid_t cgid;           /* Creator's group ID.  */
        unsigned short int mode;        /* Read/write permission.  */
        unsigned short int __pad1;
        unsigned short int __seq;       /* Sequence number.  */
        unsigned short int __pad2;
        __syscall_ulong_t __unused1;
        __syscall_ulong_t __unused2;
      };
    
    

    ②. struct msg

    /*  FILE: /usr/src/kernels/3.10.0-327.el7.x86_64/include/linux/msg.h   */
    
    /* one msg_msg structure for each message */
    struct msg_msg {
    	struct list_head m_list;
    	long m_type;
    	size_t m_ts;		/* message text size */
    	struct msg_msgseg* next;
    	void *security;
    	/* the actual message follows immediately */
    };
    

    ③. struct msgbuf (编程时,必须自己实现,因为mtext大小未定义)

    /*FILE: /usr/include/linux/msg.h    */
    
    /* message buffer for msgsnd and msgrcv calls */
    struct msgbuf {
        long mtype;         /* type of message */
        char mtext[1];      /* 信息实体(用户可自定义大小) */
    };
    
    

    ④. struct msginfo

    /* FILE: /usr/include/linux/msg.h   */
    
    /* buffer for msgctl calls IPC_INFO, MSG_INFO */
    struct msginfo 
    {
        int msgpool; /* Size in kibibytes of buffer pool
                        used to hold message data;
                        unused within kernel */
        int msgmap;  /* Maximum number of entries in message
                        map; unused within kernel */
        int msgmax;  /* Maximum number of bytes that can be
                       written in a single message */
        int msgmnb;  /* Maximum number of bytes that can be
                       written to queue; used to initialize
                       msg_qbytes during queue creation (msgget(2)) */
        int msgmni;  /* Maximum number of message queues */
        int msgssz;  /* Message segment size;
                      unused within kernel */
        int msgtql;  /* Maximum number of messages on all queues
                      in system; unused within kernel */
        unsigned short int msgseg;  /* Maximum number of segments;
                                      unused within kernel */
    };
    
    
    

    2). 消息队列的操作

    ①. 创建消息队列 - msgget():

    • 作用
      创建消息队列

    • 头文件

       #include <sys/msg.h>
      
    • 函数原型

       int msgget(key_t key, int msgflg)
      
    • 参数

    key: 有函数 ftok 返回的key值
    msgflg: 消息队列的访问权限

    msgflg num description
    IPC_CREAT 0x1000 若key不存在,则创建;存在,则返回ID
    IPC_EXCL 0x2000 若key存在,返回失败
    IPC_NOWAIT 0x4000 若需要等待,直接返回错误
    • 返回值
      成功:消息队列的ID
      失败:-1

    ②. 消息队列属性控制 - msgctl():

    • 作用
      设置/获取消息队列的属性值

    • 头文件

        #include <sys/msg.h>
      
    • 函数原型

        int msgctl(int msqid, int cmd, struct msqid_ds *buf)
      
    • 参数

    msqid: 消息队列ID
    cmd: 要执行的操作

    micro number description
    IPC_RMID 0 删除消息队列
    IPC_SET 1 设置ipc_perm的参数
    IPC_STAT 2 从内核结构体msqid复制信息到msgqid_ds
    IPC_INFO 3 获取限制信息到msginfo结构体
    MSG_INFO 12 同IPC_INFO,但会返回msginfo.msgpool/msgmap/msgtql
    • 返回值
      成功: 0(IPC_RMID/IPC_SET/IPC_STAT), 消息队列数组索引的最大值(IPC_INFO/MSG_INFO)
      失败:-1

    ③. 发送/接收消息队列 - msgsnd()/msgrcv():

    • 作用
      发送消息到消息队列(添加到尾端)/接收消息

    • 头文件

        #include <sys/msg.h>
      
    • 函数原型

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

    .

        ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)
    
    • 参数

    msqid: 消息队列的ID值,msgget()的返回值
    msgp: struct msgbuf,(发送/接收数据的暂存区,由用户自定义大小)
    msgsz: 发送/接收消息的大小(范围:0~MSGMAP)
    msgflg: 当达到系统为消息队列所指定的界限时,采取的操作(一般设置为0)
    msgtyp:

    msgtyp description
    = 0 读取队列中的第一个消息
    > 0 读取消息队列的第一条 msgbuf.mtype=msgtype的消息
    < 0 读取第一条 lowest msgbuf.mtype < abs(msgtyp) 的消息
    • 返回值
      成功: 0   (for msgsnd());  实际写入到mtext的字符个数  (for msgrcv())
      失败:-1

    3. 示例代码

    本程序主要是实现两个进程通过消息队列发送信息:

    • server:
    1. 等待接收客户端发送的数据,若时间超出600s,则自动exit;
    2. 当收到信息后,打印接收到的数据;并原样的发送给客户端,由客户端显示
    • client:
    1. 启动两个进程(父子进程),父进程用于发送数据,子进程接收由server发送的数据;
    2. 发送数据:由使用者手动输入信息,回车后发送;当写入“end~”后,退出本进程
    3. 接收数据:接收由Server端发送的数据信息,并打印

    代码如下:
    1. Client

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/msg.h>
    #include <sys/ipc.h>
    #include <signal.h>
    
    #define BUF_SIZE 128
    
    //Rebuild the strcut (must be)
    struct msgbuf
    {
        long mtype;
        char mtext[BUF_SIZE];
    };
    
    
    int main(int argc, char *argv[])
    {
        //1. creat a mseg queue
        key_t key;
        int msgId;
        
        printf("THe process(%s),pid=%d started~
    ", argv[0], getpid());
    
        key = ftok(".", 0xFF);
        msgId = msgget(key, IPC_CREAT|0644);
        if(-1 == msgId)
        {
            perror("msgget");
            exit(EXIT_FAILURE);
        }
    
        //2. creat a sub process, wait the server message
        pid_t pid;
        if(-1 == (pid = fork()))
        {
            perror("vfork");
            exit(EXIT_FAILURE);
        }
    
        //In child process
        if(0 == pid)
        {
            while(1)
            {
                alarm(0);
                alarm(100);     //if doesn't receive messge in 100s, timeout & exit
                struct msgbuf rcvBuf;
                memset(&rcvBuf, '', sizeof(struct msgbuf));
                msgrcv(msgId, &rcvBuf, BUF_SIZE, 2, 0);                
                printf("Server said: %s
    ", rcvBuf.mtext);
            }
            
            exit(EXIT_SUCCESS);
        }
    
        else    //parent process
        {
            while(1)
            {
                usleep(100);
                struct msgbuf sndBuf;
                memset(&sndBuf, '', sizeof(sndBuf));
                char buf[BUF_SIZE] ;
                memset(buf, '', sizeof(buf));
                
                printf("
    Input snd mesg: ");
                scanf("%s", buf);
                
                strncpy(sndBuf.mtext, buf, strlen(buf)+1);
                sndBuf.mtype = 1;
    
                if(-1 == msgsnd(msgId, &sndBuf, strlen(buf)+1, 0))
                {
                    perror("msgsnd");
                    exit(EXIT_FAILURE);
                }
                
                //if scanf "end~", exit
                if(!strcmp("end~", buf))
                    break;
            }
            
            printf("THe process(%s),pid=%d exit~
    ", argv[0], getpid());
        }
    
        return 0;
    }
    
    
    

    2. Server

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/msg.h>
    #include <sys/ipc.h>
    #include <signal.h>
    
    #define BUF_SIZE 128
    
    //Rebuild the strcut (must be)
    struct msgbuf
    {
        long mtype;
        char mtext[BUF_SIZE];
    };
    
    
    int main(int argc, char *argv[])
    {
        //1. creat a mseg queue
        key_t key;
        int msgId;
        
        key = ftok(".", 0xFF);
        msgId = msgget(key, IPC_CREAT|0644);
        if(-1 == msgId)
        {
            perror("msgget");
            exit(EXIT_FAILURE);
        }
    
        printf("Process (%s) is started, pid=%d
    ", argv[0], getpid());
    
        while(1)
        {
            alarm(0);
            alarm(600);     //if doesn't receive messge in 600s, timeout & exit
            struct msgbuf rcvBuf;
            memset(&rcvBuf, '', sizeof(struct msgbuf));
            msgrcv(msgId, &rcvBuf, BUF_SIZE, 1, 0);                
            printf("Receive msg: %s
    ", rcvBuf.mtext);
            
            struct msgbuf sndBuf;
            memset(&sndBuf, '', sizeof(sndBuf));
    
            strncpy((sndBuf.mtext), (rcvBuf.mtext), strlen(rcvBuf.mtext)+1);
            sndBuf.mtype = 2;
    
            if(-1 == msgsnd(msgId, &sndBuf, strlen(rcvBuf.mtext)+1, 0))
            {
                perror("msgsnd");
                exit(EXIT_FAILURE);
            }
                
            //if scanf "end~", exit
            if(!strcmp("end~", rcvBuf.mtext))
                 break;
        }
            
        printf("THe process(%s),pid=%d exit~
    ", argv[0], getpid());
    
        return 0;
    }
    
    
    
  • 相关阅读:
    PHP数组函数(5)
    python3-开发面试题(python)6.24基础篇(3)
    python3开发进阶-Django框架的ORM常用字段和参数
    python3-开发面试题(python)6.23基础篇(2)
    python3 中 and 和 or 运算规律
    python3开发进阶-Django框架中的ORM的常用操作的补充(F查询和Q查询,事务)
    python3-开发面试题(python)6.22基础篇(1)
    python3开发进阶-Django框架中的ORM的常用(增,删,改,查)操作
    python3-os模块中的os.walk(目录树生成器)
    python3开发进阶-Django框架的详解
  • 原文地址:https://www.cnblogs.com/Jimmy1988/p/7699351.html
Copyright © 2020-2023  润新知