目录

1.消息队列(有点类似于管道):

1.1 IPC对象:

1.2.键值——对IPC对象进行区分

1.3进程间通信

1.4命令

1.5函数接口

2.共享内存Share memory——进程间通信最高效的方式

2.1函数接口

2.2示例

3.信号灯(线程间通信用信号量,进程间通信用信号灯)

3.1信号灯是多个信号量组成的数组,也称为有名信号量

3.2 函数接口


1.消息队列(有点类似于管道):

1.1 IPC对象:

        消息队列,共享内存,信号灯都是基于IPC对象的东西进行通信。

        用于通信的Linux内核内存文件(掉电会丢失)

1.2.键值——对IPC对象进行区分

        键值即IPC对象的名称,一个键值对应一个IPC对象,一个IPC对象可以创建三个结构(1个消息队列、1个共享内存、1个信号灯)(如果两个信号要通信,则必须使用同1个消息队列,或同1个共享内存,或同1个信号灯

1.3进程间通信

        进程间通信使用同一消息队列 -> 同一IPC对象 ->同一键值->同-pathname和prej_id

1.4命令

        1.ipcs:查看IPC对象的信息(包括:消息队列、共享内存、信号灯)

        2.删除IPC对象

           ipcrm -Q/M/S KEY 【大写加键值】(Q/M/S 分别对应消息队列、共享内存、信号灯)
           ipcrm -q/m/s (msgid/shmid/semid) 【小写加ID】(Q/M/S 分别对应消息队列、共享内存、信号灯)

1.5函数接口

消息队列实现进程间通信的核心流程

步骤:

        ftok生成键值key → megget创建消息队列 → megrcv收/megsnd发消息→msgctl销毁消息队列

ftok

头文件:#include <sys/types.h>

               #include <sys/ipc.h>

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

功能:  根据pathname和proj_id生成唯一的IPC对象的临时键值,用于后续申请共享内存

参数:  pathname:文件路径(任意文件的路径 + 名称,只要该文件不会被删除重建即可;)

             proj_id:项目ID(整型数字,一般用 ASCII 码的单字符表示,与 pathname 运算生成唯一键值。)

返回值:

        成功返回key_t类型的唯一键值

        失败返回-1

示例:

//生成键值
#include"head.h"
int main(void)
{
    key_t key;
    key = ftok("./",'a');
    
    if(-1 = key)
    {
        perror("fail to ftok");
        return -1;        
    }
    printf("key = %d\n",key);

    return 0;
}

megget

头文件:#include <sys/types.h>

               #include <sys/ipc.h>

               #include <sys/msg.h>

原型: int msgget(key_t key, int msgflg);

功能:  创建一个消息队列(类似管道)

参数:

        key:IPC对象的名称

        msgflg:
                IPC_CREAT        不存在就创建(要或上0664,给权限)
                IPC_EXCL           存在报错(一般不用)

返回值:

        成功:返回消息队列ID号

        失败:返回-1

示例:

//创建消息队列
#include "head.h"

int main(void)
{
    key_t key;
    int msgid = 0;
    int ret = 0;

    key = ftok("/", 'a');
    if (-1 == key)
    {
        perror("fail to ftok");
        return -1;
    }

    printf("key = %#x\n", key);

    msgid = msgget(key, IPC_CREAT | 0664);
    if (-1 == msgid)
    {
        perror("fail to msgget");
        return -1;
    }

    printf("msgid = %d\n", msgid);

    return 0;
}

megsnd

头文件:#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:发送消息空间的首地址

        msgsz:发送消息的长度

        msgflg:发送消息的属性,默认0

返回值:

        成功:返回0

        失败:返回-1

消息的结构:

struct msgbuf{
    long mtype;/*message type, must be >0 *///消息类型
    char mtext[1];/* message data*///消息内容
}

示例:


    sendmsg.mtype = 100L;
    sprintf(sendmsg.mtext, "hello world");
    ret = msgsnd(msgid, &sendmsg, sizeof(sendmsg) - sizeof(long), 0);
    if (-1 == ret)
    {
        perror("fail to msgsnd");
        return -1;
    }

megrcv

头文件:#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:消息队列ID号

        msgp:发送消息空间的首地址

        msgsz:发送消息的长度

        msgflg:发送消息的属性,默认0

返回值:

        成功:返回接收到数据的字节数(不包括消息的类型,仅仅是消息内容所占字节)

        失败:返回-1

示例:

    ret = msgrcv(msgid, &recvmsg, sizeof(recvmsg) - sizeof(long), 100L, 0);
    if (-1 == ret)
    {
        perror("fail to msgrcv");
        return -1;
    }

    printf("实际接收 %d 字节:%s\n", ret, recvmsg.mtext);

msgctl

头文件:#include <sys/types.h>

               #include <sys/ipc.h>

               #include <sys/msg.h>

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

功能: 向消队列发送命令

参数:

        msqid:消息队列ID号

        cmd:命令

                IPC_RMID
        buf:NULL

返回值:

        成功:返回接收到数据的字节数

        失败:返回-1

示例:

    msgctl(msgid, IPC_RMID, NULL);

如下代码演示了消息队列(Message Queue)​ 的基本用法:

进程①:send.c(发送方)——创建队列 + 发送消息

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

// 消息结构体定义
// 注意:第一个字段必须是long类型,用于消息类型标识
typedef struct msg {
    long mtype;           // 消息类型,必须 > 0
    char mtext[256];      // 消息正文,最大255字符 + '\0'
} msg_t;

int main(void) {
    key_t key;            // 消息队列键值
    int msgid = 0;        // 消息队列标识符
    msg_t sendmsg;        // 发送消息缓冲区
    int ret = 0;          // 函数返回值
    
    // 1. 生成消息队列键值
    // 参数:路径名(必须存在且可访问),项目ID(0-255)
    // 同一通信组必须使用相同的键值
    key = ftok("/tmp", 'a');  // 使用/tmp目录更可靠
    if (-1 == key) {
        perror("生成键值失败");
        return EXIT_FAILURE;
    }
    printf("消息队列键值: 0x%08X\n", key);
    
    // 2. 创建或获取消息队列
    // IPC_CREAT: 不存在则创建
    // 0666: 所有用户可读写
    msgid = msgget(key, IPC_CREAT | 0666);
    if (-1 == msgid) {
        perror("创建/获取消息队列失败");
        return EXIT_FAILURE;
    }
    printf("消息队列ID: %d\n", msgid);
    
    // 3. 准备并发送消息
    sendmsg.mtype = 100L;  // 消息类型设为100
    snprintf(sendmsg.mtext, sizeof(sendmsg.mtext), 
             "进程[%d]发送的消息", getpid());
    
    // 发送消息
    // 参数说明:
    // msgid: 消息队列ID
    // &sendmsg: 消息缓冲区指针
    // sizeof(sendmsg.mtext): 发送消息正文长度
    // 0: 标志位(0表示阻塞发送,IPC_NOWAIT表示非阻塞)
    ret = msgsnd(msgid, &sendmsg, strlen(sendmsg.mtext) + 1, 0);
    if (-1 == ret) {
        perror("发送消息失败");
        
        // 尝试删除消息队列(可选)
        if (errno == EACCES || errno == EIDRM) {
            msgctl(msgid, IPC_RMID, NULL);
        }
        return EXIT_FAILURE;
    }
    
    printf("消息发送成功 (类型: %ld, 内容: %s)\n", 
           sendmsg.mtype, sendmsg.mtext);
    
    // 4. 等待用户确认,然后退出
    // 在实际应用中,发送方通常不负责删除消息队列
    // 删除队列的责任由接收方或专门的清理进程承担
    printf("按Enter键退出发送方...\n");
    getchar();  // 等待用户输入,便于观察
    
    return EXIT_SUCCESS; //使用信号处理实现优雅退出
}

进程②:recv.c(接收方)——获取队列 + 接收消息 + 删除队列

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

// 消息结构体(必须与发送方一致)
typedef struct msg {
    long mtype;
    char mtext[256];
} msg_t;

// 全局变量,用于信号处理
static int msgid = -1;

// 信号处理函数:优雅地清理资源
void cleanup(int sig) {
    if (msgid != -1) {
        printf("\n接收到信号%d,清理消息队列...\n", sig);
        
        // 删除消息队列
        if (msgctl(msgid, IPC_RMID, NULL) == 0) {
            printf("消息队列已删除\n");
        } else {
            perror("删除消息队列失败");
        }
    }
    exit(EXIT_SUCCESS);
}

int main(void) {
    key_t key;
    msg_t recvmsg;
    int ret = 0;
    
    // 注册信号处理函数,用于优雅退出
    signal(SIGINT, cleanup);   // Ctrl+C
    signal(SIGTERM, cleanup);  // kill命令
    
    // 1. 生成与发送方相同的键值
    key = ftok("/tmp", 'a');  // 必须与发送方相同
    if (-1 == key) {
        perror("生成键值失败");
        return EXIT_FAILURE;
    }
    printf("消息队列键值: 0x%08X\n", key);
    
    // 2. 获取已存在的消息队列(不创建)
    // 这里不使用IPC_CREAT,因为队列应由发送方创建
    // 如果队列不存在,会失败
    msgid = msgget(key, 0666);
    if (-1 == msgid) {
        perror("获取消息队列失败 (队列可能不存在)");
        fprintf(stderr, "请先运行发送方程序\n");
        return EXIT_FAILURE;
    }
    printf("消息队列ID: %d\n", msgid);
    printf("等待接收消息... (按Ctrl+C退出)\n");
    
    // 3. 循环接收消息
    while (1) {
        // 接收消息
        // 参数说明:
        // msgid: 消息队列ID
        // &recvmsg: 接收缓冲区
        // sizeof(recvmsg.mtext)-1: 最大接收长度(留1字节给'\0')
        // 100L: 只接收类型为100的消息(0表示接收任意类型)
        // 0: 标志位(0表示阻塞接收)
        ret = msgrcv(msgid, &recvmsg, sizeof(recvmsg.mtext) - 1, 100L, 0);
        
        if (-1 == ret) {
            // 检查是否是信号中断
            if (errno == EINTR) {
                printf("接收被信号中断\n");
                continue;
            }
            perror("接收消息失败");
            break;
        }
        
        // 确保字符串以'\0'结尾
        recvmsg.mtext[ret] = '\0';
        
        printf("接收成功!\n");
        printf("  消息类型: %ld\n", recvmsg.mtype);
        printf("  消息长度: %d 字节\n", ret);
        printf("  消息内容: %s\n", recvmsg.mtext);
        
        // 根据消息内容决定是否退出
        if (strstr(recvmsg.mtext, "exit") != NULL) {
            printf("收到退出指令,准备退出...\n");
            break;
        }
    }
    
    // 4. 清理资源
    printf("正在清理...\n");
    if (msgid != -1) {
        msgctl(msgid, IPC_RMID, NULL);
        printf("消息队列已删除\n");
    }
    
    return EXIT_SUCCESS;
}

makefile:

CC = gcc
CFLAGS = -Wall -Wextra -O2

all: msg_send msg_recv

msg_send: send.c
	$(CC) $(CFLAGS) -o $@ $<

msg_recv: recv.c
	$(CC) $(CFLAGS) -o $@ $<

clean:
	rm -f msg_send msg_recv

bash:

# 编译两个程序
gcc -o msg_send send.c
gcc -o msg_recv recv.c

# 终端1:运行接收方
./msg_recv

# 终端2:运行发送方
./msg_send

bash补充:

        shell有很多种,bash是shell的一种。(shell有:Bash,sh (Bourne Shell),
zsh,fish,csh/tcsh等等)

        BashBourne-Again SHell的缩写,它是Linux和Unix系统中最常用的命令行解释器,也是大多数Linux发行版的默认shell。

        Bash的本质:用户与内核的翻译官

        示例:

// C语言中调用Bash命令
#include <stdlib.h>

int main() {
    // 通过system()函数调用Bash命令
    system("ls -l");  // 实际上是通过Bash执行ls
    return 0;
}

示意图:

①按照队列排好队

②管道无消息类型的概念,所以谁去接都可以,但是消息队列有类型就不行,首先得类型匹配【所以消息队列比管道功能更强】

2.共享内存Share memory——进程间通信最高效的方式

2.1函数接口

共享内存的核心流程

步骤:ftok生成键值 → shmget申请共享内存(内核内存区域(像字符数组)) → shmat映射共享内存到本地 → 读写数据 → shmdt撤销映射 → shmctrl删除共享内存

shmget

头文件: #include <sys/ipc.h>

               #include <sys/shm.h>

原型:  int shmget(key_t key, size_t size, int shmflg);

功能:  创建共享内存(使用唯一键值 key 向内核提出共享内存使用申请。)

参数:

        key:键值

        size:共享内存的大小

        shmflg:标志(申请的共享内存访问权限(八进制表示),搭配宏使用:)

                IPC_CREAT        不存在创建(第一个申请时使用)

                IPC_EXCL:检测共享内存是否存在,需与 IPC_CREAT 配合使用。

返回值:

        成功:返回共享内存的ID

        失败:返回-1

示例;

key_t key;
int shmid = 0;
key = ftok("/",'c');

if(-1 == key)
{
    perror("fail to ftok");
    return -1;
}
    shmid = shmget(key,4096,IPC CREAT  0664);
    if(-1 == shmid)
{
    perror("fail to shmget");
    return -1;
}

shmat

头文件: #include <sys/types.h>

               #include <sys/shm.h>

原型:  void *shmat(int shmid, const void *shmaddr, int shmflg);

功能:  将地址映射到共享内存空间(将指定 shmid 对应的共享内存映射到本地内存,让进程能直接访问。)

参数:

        shmid:共享内存ID号

        shmaddr:ddr:映射到共享内存中的地址(本地可用的地址,不确定则设为 NULL(由系统自动分配))

                NULL:让系统选择合适的地址映射

        shmflg:属性,默认为0

                0:表示对共享内存有读写权限;
                SHM RDONLY:表示仅只读权限。

返回值:

        成功:返回映射到共享内存空间的地址

        失败:返回 (void*)-1。

shmdt

原型:int shmdt(const void *shmaddr);

功能:解除映射(将本地内存与共享内存断开映射关系。

shmctrl

头文件: #include <sys/types.h>

               #include <sys/shm.h>

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

功能:  向共享内存发送命令(修改共享内存属性,或删除指定的共享内存对象。)

参数:

        shmid:共享内存ID号

        cmd:命令

                IPC_RMID 删除

        buf:NULL(修改共享内存属性,或删除指定的共享内存对象。)

返回值:

        成功:返回0

        失败:返回-1

2.2示例

练习:利用共享内存实现两个进程间的通信,write.c负责从终端接收数据写入共享内存中,read.c负责打印共享内存中的数据

进程1:send.c(发送方)——获取队列 + 接收消息 + 删除队列

进程②:recv.c(接收方)——获取队列 + 接收消息 + 删除队列

3.信号灯(线程间通信用信号量,进程间通信用信号灯)

3.1信号灯是多个信号量组成的数组,也称为有名信号量

3.2 函数接口

步骤:ftok生成键值 → semget创建信号灯 → semctl初始化信号灯 → semop申请和释放

semget

头文件: #include <sys/types.h>

                #include <sys/ipc.h>

                #include <sys/sem.h>

原型: int semget(key_t key,int nsems,int semflg);

功能:  创建信号量数组

参数:

        key:PIC对象名称

        nsems:信号量的个数

        semflg:属性

                IPC_CREAT

返回值:

        成功:返回信号灯的ID

        失败:返回-1

semctl

头文件: #include <sys/types.h>

                #include <sys/ipc.h>

                #include <sys/sem.h>

原型:int semctl(int semid,int semnum,int cmd,...);

功能:  向信号灯发送命令

参数:

        semid:信号量的ID号

        semnum:信号量的下标

        cmd:

                IPC_RMID        删除信号灯
                SETVAL        设置信号量的初值

//共用体
union semun
{
int              val;  /* Value for SETVAL */
struct semid_ds *buf;  /*Buffer for IPC_STAT,IPC_SET */
unsigned short *array; /* Array fOr GETALL, SETALL */
struct seminfo *buf;   /*Buffer for IPC INFO(Linux-specific)*/ 
}

返回值:

        成功:返回非负值

        失败:返回-1

semop

头文件: #include <sys/types.h>

                #include <sys/ipc.h>

                #include <sys/sem.h>

原型:int semop(int semid,struct sembuf *sops, size_t nsops);

功能:  对信号量完成一系列操作(结构体数组)

参数:

        semid:信号量的ID号

        sops:对信号量操作列表的首地址

        nsops:对信号量操作的长度

//
unsigned short sem_num; /* semaphore number */
short sem_op;           /* semaphore operation */
short sem_flg;          /* operation flags */

返回值:

        成功:返回0

        失败:返回-1

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐