C语言 多进程编程(五)消息队列

简介: 本文介绍了Linux系统中多进程通信之消息队列的使用方法。首先通过`ftok()`函数生成消息队列的唯一ID,然后使用`msgget()`创建消息队列,并通过`msgctl()`进行操作,如删除队列。接着,通过`msgsnd()`函数发送消息到消息队列,使用`msgrcv()`函数从队列中接收消息。文章提供了详细的函数原型、参数说明及示例代码,帮助读者理解和应用消息队列进行进程间通信。

多进程(五)

进程间通信

消息队列

消息队列是一种进程间通信机制,它允许两个或多个进程之间进行通信。

消息队列的实现依赖于操作系统提供的消息队列机制,它可以实现不同进程之间的数据交换。

IPC : Inter-Process Communication (进程间通讯)

System V是早期的UNIX系统,曾经被成为AT & T System V,是unix操作系统中比较重要的一个分支
现在的Linux操作系统也支持System V IPC

System V IPC 对象共有三种:

消息队列  16K大小

共享内存 

信号量 

System V IPC是由内核维护的若干个对象,通过ipcs命令查询

img_41.png

每个IPC对象都有自己的唯一ID,可以通过ftok()函数生成IPC对象的ID
消息队列是属于 sytem ipc 的⼀种, 由内核维护与管理 可以通过 ipcs -q 查看

ftok()函数

函数头文件:

#include <sys/ipc.h>

函数原型:

key_t ftok(const char *pathname, int proj_id);

参数说明:

  • pathname: 要生成IPC对象的路径名
  • proj_id: 项目ID,用于区分不同IPC对象

  • 每个存在的文件都有一个id,叫做inode节点号,可以通过ll 命令查询

  • inode节点号 + proj_id(低8bit) 生成key_t类型的值,作为IPC对象的ID
  • key_t类型的值可以用ftok()函数生成,也可以用mkkey()函数生成

函数返回值:

  • 成功: 返回一个key_t类型的整数,该整数是IPC对象的ID
  • 失败: 返回-1,并设置errno

创建消息队列

函数头文件:

#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>

函数原型:

int msgget(key_t key, int msgflg);

参数说明:

  • key: 要生成IPC对象的ID
    • IPC_PRIVATE: 创建一个新的IPC对象,key值由系统自动分配,该对象只能被调用进程及其子进程访问
  • msgflg: 标志位,用于设置消息队列的访问模式,可取值如下:
    • IPC_CREAT: 如果key对应的消息队列不存在,则创建该消息队列
    • IPC_EXCL: 如果key对应的消息队列已经存在,则返回错误
    • 0: 打开已存在的消息队列
    • 权限控制标志: 如0666,表示创建的消息队列具有读写权限

函数返回值:

  • 成功: 返回消息队列的ID
  • 失败: 返回-1,并设置errno

创建消息队列示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//创建消息队列

#define MSG_PATH "."
#define MSG_ID 88
int main(){
   
   
    key_t key;//消息队列的key
    key= ftok(MSG_PATH,MSG_ID);//通过文件路径和ID生成key
    if(key==-1){
   
   
        printf("ftok()");
        exit(EXIT_FAILURE);
    }

    int msgid= msgget(key,IPC_CREAT|0666);//创建消息队列
    if(msgid==-1){
   
   
        printf("msgget()");
        exit(EXIT_FAILURE);
    }
    printf("Message Queue ID: %d\n",msgid);
    return 0;
}

运行结果:

img_42.png

msgctl 函数

功能: 操作消息队列

函数头文件:

#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/msg.h>

函数原型:

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

参数说明:

  • msqid: 要操作的消息队列ID
  • cmd: 操作命令,可取值如下:
    • IPC_STAT: 获取消息队列的状态信息 //和struct msqid_ds *buf参数一起使用
    • IPC_SET: 设置消息队列的状态信息 //和struct msqid_ds *buf参数一起使用
    • IPC_RMID: 删除消息队列 //使用这个命令时,第三个参数为NULL//只有消息队列的创建者和所有者以及root用户可以删除消息队列
  • buf: 消息队列属性结构体对象指针,用于设置或获取消息队列的状态信息,

函数返回值:

  • 成功: 返回0
  • 失败: 返回-1,并设置errno

消息队列属性结构体定义如下:

struct msqid_ds
{
   
   
#ifdef __USE_TIME_BITS64
# include <bits/types/struct_msqid64_ds_helper.h>
#else
  struct ipc_perm msg_perm;    /* 描述操作权限的结构 */

  struct ipc_perm
{
   
   
  __key_t __key;                /* Key.  */
  __uid_t uid;                    /* 所有者的用户 ID.  */
  __gid_t gid;                    /* 所有者组 ID.  */
  __uid_t cuid;                    /* 创作者的用户 ID.  */
  __gid_t cgid;                    /* 创作者的组 ID.  */
  __mode_t mode;                /* 读/写权限.  */
  unsigned short int __seq;            /* 序列号.  */
  unsigned short int __pad2;  
  __syscall_ulong_t __glibc_reserved1; 
  __syscall_ulong_t __glibc_reserved2;
};


# if __TIMESIZE == 32
  __time_t msg_stime;        //上次发送消息的时间
  unsigned long int __msg_stime_high; 
  __time_t msg_rtime;        //上次接收消息的时间
  unsigned long int __msg_rtime_high;
  __time_t msg_ctime;    //消息队列的创建时间
  unsigned long int __msg_ctime_high;
# else
  __time_t msg_stime;        //上次发送消息的时间
  __time_t msg_rtime;        //上次接收消息的时间
  __time_t msg_ctime;        //消息队列的创建时间
# endif
  __syscall_ulong_t __msg_cbytes;  //消息队列中消息的字节数
  msgqnum_t msg_qnum;        //消息队列中消息的数量
  __pid_t msg_lspid;        //最后发送消息的进程ID
  __syscall_ulong_t __glibc_reserved4; //保留
  __syscall_ulong_t __glibc_reserved5;//保留
#endif
};

示例:在上⼀个示例的基础上,加上删除队列的代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//创建消息队列

#define MSG_PATH "."
#define MSG_ID 88
int main(){
   
   
    key_t key;//消息队列的key
    key= ftok(MSG_PATH,MSG_ID);//通过文件路径和ID生成key
    if(key==-1){
   
   
        printf("ftok()");
        exit(EXIT_FAILURE);
    }

    int msgid= msgget(key,IPC_CREAT|0666);//创建消息队列
    if(msgid==-1){
   
   
        printf("msgget()");
        exit(EXIT_FAILURE);
    }
    printf("Message Queue ID: %d\n",msgid);

    int ret= msgctl(msgid,IPC_RMID,NULL);//删除消息队列
    if(ret==-1){
   
   
        printf("msgctl()");
        exit(EXIT_FAILURE);
    }
    printf("消息队列已删除.\n");


    return 0;
}

发送消息

发送消息队列的函数是msgsnd()
msgsnd函数是用于向System V消息队列发送消息的一个系统调用。消息队列是一种由操作系统提供的进程间通信(IPC)机制,允许一个进程发送消息并且另一个进程接收消息。以下是msgsnd函数的详细说明和用法。

函数头文件:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

函数原型:

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

参数说明:

  • msqid: 要发送的消息队列ID
  • msgp: 要发送的消息内容指针
  •    struct msgbuf {
         long mtype;       /* 消息的类型 必须大于 0 */
         char mtext[1];    /* 消息正文 可以自定义 */
     };
    
  • msgsz: 要发送的消息内容长度

  • msgflg: 标志位,用于设置消息发送的模式,可取值如下:

    • IPC_NOWAIT: 若消息队列已满,则立即返回错误 ,返回-1,并设置errno为EAGAIN
    • 0: 若消息队列已满,则阻塞等待直到消息队列空闲
    • 对发送消息来说,有意义的flags标志为IPC_NOWAIT,
    • 在消息队列没有足够的空间容纳要发送的数据时,设置了该标志,
    • 则msgsnd()函数立刻出错返回,
    • 否则发送消息的进程被阻塞,直至消息队列有空间或队列被删除时返回。

函数返回值:

  • 成功: 返回0
  • 失败: 返回-1,并设置errno
  • img_43.png

示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_PATH "/home/gopher"
#define MSG_ID 88

//消息队列发送  MessageQueues2中接收
#define MSG_SZ 100
struct msgbuf{
   
   //消息队列结构
    long mtype;//消息类型
    char mtext[MSG_SZ];//消息内容
};
int main(){
   
   



    key_t key;//消息队列的key
    //通过文件路径和ID生成key,
    key= ftok(MSG_PATH,MSG_ID);
    if(key==-1){
   
   
        printf("ftok()");
        exit(EXIT_FAILURE);
    }
    printf("key: %d\n",key);

    //使用key 创建消息队列
    int msgid= msgget(key,IPC_CREAT|0666);
    if(msgid==-1){
   
   
        printf("msgget()");
        exit(EXIT_FAILURE);
    }
    printf("消息队列ID: %d\n",msgid);

    //准备消息模板
    struct msgbuf msg;//消息队列结构
    msg.mtype=101;//消息类型
    strcpy(msg.mtext,"Hello,world!");//消息内容
    //msgsnd函数第一个参数是消息队列ID,第二个参数是消息队列结构的指针,第三个参数是消息长度,第四个参数是消息类型
    int ret= msgsnd(msgid,(const void*)&msg,strlen(msg.mtext)+1,0);// 0: 若消息队列已满,则阻塞等待直到消息队列空闲
    if(ret==-1){
   
   
        printf("msgsnd()");
        exit(EXIT_FAILURE);
    }

    return 0;
}

常见错误

EINVAL: 无效的消息队列标识符或无效的消息大小。

EIDRM: 消息队列已被标记为删除。

EINTR: 调用被信号中断。

EAGAIN: 消息队列满,并且指定了IPC_NOWAIT标志。

接收消息

msgrcv函数是用于在System V消息队列中接收消息的函数。msgrcv函数从消息队列中读取消息,并从队列中删除该消息。以下是msgrcv函数的语法及其详细说明。

函数原型:


#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

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

参数说明:

msqid: 消息队列标识符,通常由msgget函数返回。

msgp: 指向用户定义的消息缓冲区的指针。结构体中至少应包含一个long mtype成员,用于指定消息的类型。其余部分可根据需要定义为消息数据。
        - struct msgbuf {
   
   
           long mtype;       /* 消息的类型 必须大于 0 */
           char mtext[1];    /* 消息正文 可以自定义 */
       };

msgsz: 指定消息数据部分的最大字节数(不包括mtype成员的大小)。

msgtyp: 指定要接收的消息类型。如果msgtyp为零,则接收队列中的第一个消息。
        0 :接收消息队列中第一条消息
        >0 : 接收指定类型的第一条消息
        <0 :一般不使用,了解即可,表示接收消息队列中第一条类型最小的小于msgtyp的绝对值的消息
            3-2-5-500-200-8
            读取时,类型传 -200
            读取的顺序  2-3-5 

msgflg: 操作标志,可以是以下值的按位或:
        标志位 0 阻塞接收
      IPC_NOWAIT: 如果没有合适的消息可供接收,函数立即返回而不是阻塞。
      MSG_EXCEPT: 接收不等于msgtyp的第一个消息。
      MSG_NOERROR: 如果消息过长,将其截断。

返回值:

  • 成功: 返回实际接收的消息的字节数。
  • 失败: 返回-1,并设置errno。

常见错误

EINVAL: 无效的消息队列标识符。

EINTR: 调用被信号中断。

E2BIG: 消息太长并且未指定MSG_NOERROR标志。

ENOMSG: 没有符合msgtyp条件的消息,并且未指定IPC_NOWAIT标志。

示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_PATH "/home/gopher"
#define MSG_ID 88

//消息队列接收
#define MSG_SZ 100
struct msgbuf{
   
   //消息队列结构
    long mtype;//消息类型
    char mtext[MSG_SZ];//消息内容
};
int main(){
   
   



    key_t key;//消息队列的key
    //通过文件路径和ID生成key,
    key= ftok(MSG_PATH,MSG_ID);
    if(key==-1){
   
   
        printf("ftok()");
        exit(EXIT_FAILURE);
    }
    printf("key: %d\n",key);
    //使用key 创建消息队列
    int msgid= msgget(key,IPC_CREAT|0666);
    if(msgid==-1){
   
   
        printf("msgget()");
        exit(EXIT_FAILURE);
    }
    printf("消息队列ID: %d\n",msgid);
    //准备消息模板
    struct msgbuf msg;//消息队列结构
    msg.mtype=101;//消息类型
    ssize_t nbytes;//接收到的字节数

    nbytes= msgrcv(msgid,(void*)&msg,MSG_SZ,101,0);//接收消息  //0接收第一条消息
    //MSG_SZ为msg能接受的最大字节数
    if(nbytes==-1){
   
   
        printf("msgrcv()");
        exit(EXIT_FAILURE);
    }
    printf("消息类型: %ld\n",msg.mtype);
    printf("已收到消息: %s\n",msg.mtext);

    return 0;
}
相关文章
|
10天前
|
安全 开发者 Python
揭秘Python IPC:进程间的秘密对话,让你的系统编程更上一层楼
【9月更文挑战第8天】在系统编程中,进程间通信(IPC)是实现多进程协作的关键技术。IPC机制如管道、队列、共享内存和套接字,使进程能在独立内存空间中共享信息,提升系统并发性和灵活性。Python提供了丰富的IPC工具,如`multiprocessing.Pipe()`和`multiprocessing.Queue()`,简化了进程间通信的实现。本文将从理论到实践,详细介绍各种IPC机制的特点和应用场景,帮助开发者构建高效、可靠的多进程应用。掌握Python IPC,让系统编程更加得心应手。
13 4
|
14天前
|
网络协议 C语言
C语言 网络编程(十三)并发的TCP服务端-以进程完成功能
这段代码实现了一个基于TCP协议的多进程并发服务端和客户端程序。服务端通过创建子进程来处理多个客户端连接,解决了粘包问题,并支持不定长数据传输。客户端则循环发送数据并接收服务端回传的信息,同样处理了粘包问题。程序通过自定义的数据长度前缀确保了数据的完整性和准确性。
|
14天前
|
C语言
C语言 网络编程(八)并发的UDP服务端 以进程完成功能
这段代码展示了如何使用多进程处理 UDP 客户端和服务端通信。客户端通过发送登录请求与服务端建立连接,并与服务端新建的子进程进行数据交换。服务端则负责接收请求,验证登录信息,并创建子进程处理客户端的具体请求。子进程会创建一个新的套接字与客户端通信,实现数据收发功能。此方案有效利用了多进程的优势,提高了系统的并发处理能力。
|
14天前
|
缓存 Linux C语言
C语言 多进程编程(六)共享内存
本文介绍了Linux系统下的多进程通信机制——共享内存的使用方法。首先详细讲解了如何通过`shmget()`函数创建共享内存,并提供了示例代码。接着介绍了如何利用`shmctl()`函数删除共享内存。随后,文章解释了共享内存映射的概念及其实现方法,包括使用`shmat()`函数进行映射以及使用`shmdt()`函数解除映射,并给出了相应的示例代码。最后,展示了如何在共享内存中读写数据的具体操作流程。
|
14天前
|
Linux C语言
C语言 多进程编程(七)信号量
本文档详细介绍了进程间通信中的信号量机制。首先解释了资源竞争、临界资源和临界区的概念,并重点阐述了信号量如何解决这些问题。信号量作为一种协调共享资源访问的机制,包括互斥和同步两方面。文档还详细描述了无名信号量的初始化、等待、释放及销毁等操作,并提供了相应的 C 语言示例代码。此外,还介绍了如何创建信号量集合、初始化信号量以及信号量的操作方法。最后,通过实际示例展示了信号量在进程互斥和同步中的应用,包括如何使用信号量避免资源竞争,并实现了父子进程间的同步输出。附带的 `sem.h` 和 `sem.c` 文件提供了信号量操作的具体实现。
|
6天前
|
存储 Serverless C语言
【C语言基础考研向】11 gets函数与puts函数及str系列字符串操作函数
本文介绍了C语言中的`gets`和`puts`函数,`gets`用于从标准输入读取字符串直至换行符,并自动添加字符串结束标志`\0`。`puts`则用于向标准输出打印字符串并自动换行。此外,文章还详细讲解了`str`系列字符串操作函数,包括统计字符串长度的`strlen`、复制字符串的`strcpy`、比较字符串的`strcmp`以及拼接字符串的`strcat`。通过示例代码展示了这些函数的具体应用及注意事项。
|
9天前
|
存储 C语言
C语言程序设计核心详解 第十章:位运算和c语言文件操作详解_文件操作函数
本文详细介绍了C语言中的位运算和文件操作。位运算包括按位与、或、异或、取反、左移和右移等六种运算符及其复合赋值运算符,每种运算符的功能和应用场景都有具体说明。文件操作部分则涵盖了文件的概念、分类、文件类型指针、文件的打开与关闭、读写操作及当前读写位置的调整等内容,提供了丰富的示例帮助理解。通过对本文的学习,读者可以全面掌握C语言中的位运算和文件处理技术。
|
9天前
|
存储 C语言
C语言程序设计核心详解 第七章 函数和预编译命令
本章介绍C语言中的函数定义与使用,以及预编译命令。主要内容包括函数的定义格式、调用方式和示例分析。C程序结构分为`main()`单框架或多子函数框架。函数不能嵌套定义但可互相调用。变量具有类型、作用范围和存储类别三种属性,其中作用范围分为局部和全局。预编译命令包括文件包含和宏定义,宏定义分为无参和带参两种形式。此外,还介绍了变量的存储类别及其特点。通过实例详细解析了函数调用过程及宏定义的应用。
|
14天前
|
Linux C语言
C语言 多进程编程(三)信号处理方式和自定义处理函数
本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
|
14天前
|
C语言
C语言 字符串操作函数
本文档详细介绍了多个常用的字符串操作函数,包括 `strlen`、`strcpy`、`strncpy`、`strcat`、`strncat`、`strcmp`、`strncpy`、`sprintf`、`itoa`、`strchr`、`strspn`、`strcspn`、`strstr` 和 `strtok`。每个函数均提供了语法说明、参数解释、返回值描述及示例代码。此外,还给出了部分函数的自实现版本,帮助读者深入理解其工作原理。通过这些函数,可以轻松地进行字符串长度计算、复制、连接、比较等操作。