Linux——进程间通信

目录

一、管道

1、什么是管道

2、匿名管道

3、管道读写规则

4、管道特点

二、命名管道

1、匿名管道与命名管道的区别

2、实例代码

三、共享内存

1、shmget函数

2、shmat函数

3、shmdt函数

4、shmctl函数


进程间通信的目的

        ·数据传输:一个进程需要将它的数据发送给另一个进程

        ·资源共享:多个进程之间共享同样的资源

        ·通知事件:一个进程需要向另一个或一组进程发送消息,通知它发生了某种时间

        ·进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变

        ·进程间通信的本质:让不同的进程看到相同的一份资源

一、管道

1、什么是管道

        ·管道是Unix中最古老的进程间通信的形式

        ·我们把从一个进程链接到另一个进程的一个数据流称为一个“管道”

2、匿名管道

int pipe(int fd[2])

参数

fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端

站在文件描述符的角度,理解管道

实例代码

#include <iostream>
#include <assert.h>
#include <unistd.h>
#include <string>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;


int main()
{
    //1、创建管道
    int pipefd[2] = {0}; //pipefd[0]:读端,pipefd[1]:写端
    int n = pipe(pipefd);
    assert(n != -1);
    (void)n;

    //2、创建子进程
    pid_t id = fork();
    assert(id != -1);
    if(id == 0)
    {
        //子进程
        //3、构建单向通信的信道,父进程写入,子进程读取
        //3.1关闭子进程不需要的fd
        close(pipefd[1]);
        char buffer[1024];
        while(true)
        {
            ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);
            if(s > 0)
            {
                buffer[s] = 0;
                cout <<"chile get a message[" << getpid() << "] Father# " << buffer << endl;
            }
        }
        exit(0);
    }
    //父进程
    //3、构建单向通信的信道
    //3.1关闭父进程不需要的fd
    close(pipefd[0]);
    string message = "我是父进程,我正在给你发消息";
    int count = 0;
    char send_buffer[1024];
    while(true)
    {
        //3.2构建一个变化的字符串
        snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);
        //3.3写入
        write(pipefd[1], send_buffer, strlen(send_buffer));
        //3.4故意sleep
        sleep(1);
    }
    pid_t ret = waitpid(id, nullptr, 0);
    assert(ret < 0);
    (void)ret;
    close(pipefd[1]);
    return 0;
}

 总结管道的特点

        ·管道是用来进行具有血缘关系的进程进行进程间通信——常用于父子进程

        ·管道具有通过让进程间协同,提供了访问控制

        ·管道提供的是面向流式的通信服务

        ·管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的

        ·管道是单向通信的,就是半双工的一种特殊情况

3、管道读写规则

·当没有数据可读时

        O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止

        O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN

·当管道满的时候

        O_NONBLOCK disable:write调用阻塞,直到有进程读走数据

        O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

·如果所有管道写段对应的文件描述符被关闭,则read返回0

·如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出

·当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性

·当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性

4、管道特点

·只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可以应用该管道。

·一般而言,进程退出,管道释放,所以管道的生命周期随进程

·一般而言,内核会对管道操作进行同步与互斥

·管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

二、命名管道

·管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信

·如果我们想在不相关的进程之间交换雏菊,可以使用FIFO文件来做这项工作,它被称之为命名管道。

·命名管道是一种特殊类型的文件,管道文件可以被打开,但不会将内存数据刷新到磁盘

1、匿名管道与命名管道的区别

·匿名管道由pipe函数创建并打开

·命名管道由mkfifo函数创建,打开用open

·命名管道与匿名管道之间唯一的区别在它们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义

2、实例代码

客户端向服务端发送消息

#ifndef _COMM_H_
#define _COMM_H_

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"

using namespace std;

#define MODE 0666
#define SIZE 128

string ipcPath = "./fifo.ipc";

#endif
#include "comm.hpp"
#include <sys/wait.h>

static void getMessage(int fd)
{
    char buffer[SIZE];
    while(true)
    {
        memset(buffer, '\0', sizeof(buffer));
        ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
        if(s > 0)
        {
            cout << "[" << getpid() << "]" << "client say: " << buffer << endl;
        }
        else if(s == 0)
        {
            cerr << "[" << getpid() << "]" << "read end of file, client quit, sever quit too!" << endl;
            break;
        }
        else
        {
            perror("read");
            break;
        }
    }
}

int main()
{
    //创建管道文件
    if(mkfifo(ipcPath.c_str(), MODE) < 0)
    {
        perror("mkfifo");
        exit(1);
    }

    Log("创建管道文件成功", Debug) << " step 1" << endl;

    //正常的文件操作
    int fd = open(ipcPath.c_str(), O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        exit(2);
    }
    Log("打开管道文件成功", Debug) << " step 2" << endl;

    int num = 3;
    for(int i = 0; i < num; i++)
    {
        pid_t id = fork();
        if(id == 0)
        {
            //正常通信的代码
            getMessage(fd);
            exit(1);
        }
        
    }
    for(int i = 0; i < num; i++)
    {
        waitpid(-1, nullptr, 0);
    }

    //关闭文件
    close(fd);
    Log("删除管道文件成功", Debug) << " step 3" << endl;

    unlink(ipcPath.c_str()); //通信完毕就删除文件
    Log("删除管道文件成功", Debug) << " step 4" << endl;

    return 0;
}
#include "comm.hpp"

int main()
{
    //获取管道文件
    int fd = open(ipcPath.c_str(), O_WRONLY);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }

    //ipc过程
    string buffer;
    while(true)
    {
        cout << "Please Enter Message Line :>";
        std::getline(std::cin, buffer);
        if(buffer.compare("quit") == 0)
            break;
        write(fd, buffer.c_str(), buffer.size());
    }

    close(fd);
    return 0;
}

三、共享内存

共享内存位于堆栈之间

共享内存函数

1、shmget函数

功能:用来创建共享内存

原型

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

参数

        key:这个共享段名字

        size:共享内存大小

        shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

返回值:成功返回一个非负整数,即该共享内存段的标识码,失败返回-1

2、shmat函数

功能:将共享内存段链接到进程地址空间

原型:

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

参数:

        shmid:共享内存标识

        shmaddr:指定链接的地址

        shmflg:它的两个肯能取值是SHM_RND和SHM_RDONLY

返回值:成功返回一个指针,指向共享内存第一个节,失败返回-1

3、shmdt函数

功能:将共享内存段与当前进程脱离

原型:

        int shmdt(const void* shmaddr);

参数:

        shmaddr:由shmat所返回的指针

返回值:成功返回0,失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段

4、shmctl函数

功能:用于控制共享内存

原型:

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

参数:

        shmid:由shmget返回的共享内存标识码

        cmd:将要采取的动作

        buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

返回值:成功返回0,失败返回-1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值