进程间的IPC通信机制

一、介绍

进程与进程间的用户空间相互独立,内核空间共享。

1.传统的进程间通信机制

        a.无名管道 pipe

        b.有名管道 fifo

        c.信号         signal

2.system V中的IPC对象

        a.消息队列 message queue

        b.共享内存 shared memory

        c.信号灯集 semaphoare

3.可用于跨主机传输的通信机制

        a.套接字 socket

二、管道

1.管道可以看成是一个特殊文件,一般文件存储在外存中,而管道内容存储在内存中

2.管道遵循先进先出原则

3.管道的读操作是一次性的,内容被读出后就会从管道中删除

4.管道是一种半双工的通信方式

5.管道只能使用文件IO函数,因为需要直接操作内核空间,如open,close,read,write,但不能使用lseek

6.管道的大小为64k

2.1无名管道

无名管道即在文件系统(用户系统)不可见的管道文件

无名管道不可以用open打开,因为不知道路径以及名字

无名管道只能用于具有亲缘关系的进程间通信。由于无名管道在文件系统中不可见,两个无关的进程,无法拿到同一根管道的读写段,只有具有亲缘关系的进程,在父进程中创建一根管道,拿到读写端后,调用fork函数,创建出来的子进程也会有该管道的读写端。

从管道中读取数据:
1.读写端均存在时,当管道中没有数据时,read会阻塞

2.当管道的写端不存在时,若管道中有数据,会先将数据读取完毕,没有数据时,read函数不会阻塞,直接返回0

向管道中写入数据:

1.读写段均存在时,write会阻塞

2.当管道的读端不存在时,调用write函数,尝试向管道中写入数据会导致管道破裂。

#include <head.h>
int main(int argc, char const *argv[])
{
    // 管道的创建必须放在fork前
    // 若放在fork后,会导致父子进程各自创建一个内管道,无法通信
    int pfd[2] = {0};
    if (pipe(pfd) < 0) // pf[0]为读的文件描述符,pf[1]为写的文件描述符
    {
        perror("pipe");
        return -1;
    }
    printf("管道创建成功\n");

    pid_t pid = fork();
    if (pid > 0)
    {
        // 父进程发数据给子进程
        char buf[128] = "";
        while (1)
        {
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf) - 1] = 0; // 从终端获取数据,将最后获取到的\n变成\0

            // 将数据写入到管道中
            if (write(pfd[1], buf, sizeof(buf)) < 0)
            {
                perror("write");
                return -1;
            }
            printf("写入成功\n");
        }
    }
    else if (pid == 0)
    {
        // 子进程接收父进程发送过来的数据
        char buf[128] = "";
        int res = 0;
        while (1)
        {
            // 管道中没有数据时,read会阻塞
            res = read(pfd[0], buf, sizeof(buf));
            printf("读取成功\n");
            printf("%s\n", buf);
        }
    }
    else
    {
        perror("fork");
        return -1;
    }
    return 0;
}

 2.2有名管道

写端

#include <head.h>
int main(int argc, char const *argv[])
{
    umask(0);
    // 创建有名管道
    if (mkfifo("./myfifo", 0664) < 0)
    {
        if (errno != 17)
        { // 文件已存在的错误是一个合法的错误,需要排除,代码允许正常运行
            perror("mkfifo");
            return -1;
        }
    }
    printf("有名管道创建成功\n");

    // 以只写的方式打开有名管道
    int fd = open("./myfifo", O_WRONLY); // 当只有一个写端时,open函数会阻塞
    if (fd < 0)
    {
        perror("open");
        return -1;
    }
    printf("open succcess\n");

    char buf[128] = "";
    while (1)
    {
        printf("请输入>>>");
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = 0;
        if (write(fd, buf, sizeof(buf)) < 0)
        {
            perror("write");
            return -1;
        }
        if (strcmp(buf, "quit") == 0)
            break;
        printf("写入成功\n");
    }
    close(fd);
    return 0;
}

读端

#include <head.h>
int main(int argc, char const *argv[])
{
    umask(0);
    // 创建有名管道
    if (mkfifo("./myfifo", 0664) < 0)
    {
        if (errno != 17)
        { // 文件已存在的错误是一个合法的错误,需要排除,代码允许正常运行
            perror("mkfifo");
            return -1;
        }
    }
    printf("有名管道创建成功\n");

    // 以只读的方式打开有名管道
    int fd = open("./myfifo", O_RDONLY); // 当只有一个读端时,open函数会阻塞
    if (fd < 0)
    {
        perror("open");
        return -1;
    }
    printf("open succcess\n");

    char buf[128] = "";
    int res = 0;
    while (1)
    {
        bzero(buf, sizeof(buf));
        res = read(fd, buf, sizeof(buf));
        if (res < 0)
        {
            perror("read");
            return -1;
        }
        else if ((res == 0) || strcmp(buf, "quit") == 0)
        {
            printf("写端退出\n");
            break;
        }
        printf("buf=%s\n", buf);
    }
    close(fd);
    return 0;
}

三、信号

原理

信号是一种异步通信的方式

异步:任务与任务之间无关系,根据CPU轮询机制来运行多个任务

同步:任务与任务之间有先后关系,必须要等任务A结束2后才能执行任务B

1.signal

#include <head.h>

void handler(int sig)
{
    printf("sig=%d\n", sig);
    return;
}
int main(int argc, char const *argv[])
{
    // 捕获2)SIGINT信号
    if (signal(2, handler) == SIG_ERR)
    //第一个参数:指定要捕获的信号,天对应的编号或宏
    //第二个参数:可以填SIG_IGN:忽略信号
                    //SIG_DFL:执行默认操作
                    //捕获信号信号:填写函数指针变量
    {
        perror("signal");
        return -1;
    }
    printf("捕获信号成功\n");
    while (1)
    {
        printf("主函数\n");
        sleep(1);
    }
    return 0;
}

 

练习:用信号的方式回收僵尸进程

#include <head.h>
int count = 0;

void handler(int sig)
{
    while (waitpid(-1, NULL, WNOHANG) > 0)
        ;
}
int main(int argc, char const *argv[])
{
    // 捕获17)SIGCHLD
    __sighandler_t s = signal(17, handler);
    if (SIG_ERR == s)
    {
        perror("signal");
        return -1;
    }
    int i = 0;
    while (i < 100)
    {
        int res = fork();
        if (res == 0)
            exit(0);
        i++;
    }
    while (1)
        sleep(1);

    return 0;
}

2.kill

当收到quit时,父子进程全部退出

#include <head.h>
int main(int argc, char const *argv[])
{
    pid_t pid = fork();
    if (pid > 0)
    {
        while (1)
        {
            printf("父进程 %d %d\n", getpid(), pid);
            sleep(1);
        }
    }
    else if (pid == 0)
    {
        char buf[128] = "";
        bzero(buf, sizeof(buf));
        while (1)
        {
            printf("请输入>>>");
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf) - 1] = 0;
            printf("buf=%s", buf);
            if (strcmp(buf, "quit") == 0)
            {
                break;
            }
        }
        // 子进程给父进程发信号,请求父进程退出
        kill(getppid(), 9); // 9号是杀死信号
    }
    else
    {
        perror("fork");
        return -1;
    }

    return 0;
}

3.alarm

设置3秒的定时器

#include <head.h>
void callback(int sig)
{
    printf("超时了\n");
    alarm(3);
    return;
}
int main(int argc, char const *argv[])
{
    // 捕获14号信号
    if (signal(14, callback) == SIG_ERR)
    {
        perror("signal");
        return -1;
    }
    alarm(3);
    while (1)
    {
        printf("signal....\n");
        sleep(1);
    }
    return 0;
}

四、消息队列

消息队列按照先进先出的原则,但也可以限制消息类型读取

消息队列独立于进程,等进程结束后,消息队列以及其中的内容不会删除,除非重启操作系统或手动删除

发送数据

#include <head.h>
struct msgbuf
{
    long mtype;      // 消息类型,必须大于0
    char mtext[128]; // 消息内容
};
int main(int argc, char const *argv[])
{
    // 创建key值
    key_t key = ftok("/home/ubuntu/", 1);
    // 第二个参数:填非0
    if (key < 0)
    {
        perror("ftok");
        return -1;
    }

    // 创建消息队列
    int msqid = msgget(key, IPC_CREAT | 0664);
    if (msqid < 0)
    {
        perror("msgget");
        return -1;
    }
    struct msgbuf sndbuf;
    while (1)
    {
        printf("请输入人消息类型>>>");
        scanf("%ld", &sndbuf.mtype);
        getchar();

        if (sndbuf.mtype == 0)
        { // 若输入类型为0,退出循环
            break;
        }
        printf("请输入消息内容>>>");
        fgets(sndbuf.mtext, sizeof(sndbuf.mtext), stdin);
        sndbuf.mtext[strlen(sndbuf.mtext) - 1] = 0;

        // 向消息队列中发送数据
        if (msgsnd(msqid, &sndbuf, sizeof(sndbuf.mtext), 0) < 0)
        {
            perror("msgsnd");
            return -1;
        }
        printf("发送成功\n");
    }
    // 删除消息队列
    if (msgctl(msqid, IPC_RMID, NULL) < 0)
    {
        perror("msgctl");
        return -1;
    }
    printf("删除消息队列成功\n");
    return 0;
}

 

接收数据

#include <head.h>
struct msgbuf
{
    long mtype;      // 消息类型,必须大于0
    char mtext[128]; // 消息内容
};
int main(int argc, char const *argv[])
{
    // 创建key值
    key_t key = ftok("/home/ubuntu/", 1);
    // 第二个参数:填非0
    if (key < 0)
    {
        perror("ftok");
        return -1;
    }

    // 创建消息队列
    int msqid = msgget(key, IPC_CREAT | 0664);
    if (msqid < 0)
    {
        perror("msgget");
        return -1;
    }
    struct msgbuf recvbuf;
    int res = 0;
    while (1)
    {
        // 从指定的消息队列中读取数据
        // 读取消息队列中的第一条消息,先进先出
        res = msgrcv(msqid, &recvbuf, sizeof(recvbuf.mtext), 0, IPC_NOWAIT);
        if (res < 0)
        {
            perror("msgrcv");
            return -1;
        }
        printf("接收到的消息为%s\n", recvbuf.mtext);
    }
    // 删除消息队列
    if (msgctl(msqid, IPC_RMID, NULL) < 0)
    {
        perror("msgctl");
        return -1;
    }
    printf("删除消息队列成功\n");
    return 0;
}

五、共享内存

共享内存是最高效的进程间通信方式

多个进程可以同时访问共享内存,修改其中的内容,所以共享内存其实是临界资源,需要注意进程间的同步互斥

共享内存独立于进程,等进程结束后,共享内存以及其中的内容不会删除,除非重启操作系统或者手动删除

发送数据

#include <head.h>
int main(int argc, char const *argv[])
{
    // 创建key值
    key_t key = ftok("./", 10);
    if (key < 0)
    {
        perror("ftok");
        return -1;
    }
    printf("key=%#x\n", key);

    // 创建共享内存,获得shmid号
    int shmid = shmget(key, 32, IPC_CREAT | 0664);
    if (shmid < 0)
    {
        perror("shmget");
        return -1;
    }
    printf("shmid=%d\n", shmid);
    // 映射共享内存到用户空间
    void *addr = shmat(shmid, NULL, 0);
    if (addr == (void *)-1)
    {
        perror("shmat");
        return -1;
    }

    // 先往共享内存中存储一个int类型的数据
    *(int *)addr = 10;

    // 再往int类型数据后面存储一个字符串
    char *ptr = (char *)addr + 4;

    strcpy(ptr, "hello world");

    return 0;
}

接收数据

#include <head.h>
int main(int argc, char const *argv[])
{
    // 创建key值
    key_t key = ftok("./", 10);
    if (key < 0)
    {
        perror("ftok");
        return -1;
    }

    // 创建共享内存,获得shmid号
    int shmid = shmget(key, 32, IPC_CREAT | 0664);
    if (shmid < 0)
    {
        perror("shmget");
        return -1;
    }

    // 映射共享内存到用户空间
    void *addr = shmat(shmid, NULL, 0);
    if (addr == (void *)-1)
    {
        perror("shmat");
        return -1;
    }

    printf("%d\n", *(int *)addr);
    printf("%s\n", (char *)((int *)addr + 1));

    return 0;
}

  • 22
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值