零基础Linux_17(进程间通信)VSCode环境安装+进程间通信介绍+pipe管道mkfifo

news2024/11/24 13:51:59

目录

1. VSCode环境安装

1.1 使用VSCode

1.2 远程链接到Linux机器

1.3 VSCode调试

2. 进程间通讯介绍

2.1 进程间通讯的概念和意义

2.2 进程间通讯的策略和本质

3. 管道

3.1 管道介绍

3.2 匿名管道介绍

3.3 匿名管道示例代码

3.3.1 建立管道的pipe

3.3.2 匿名管道完整代码

3.3.3 匿名管道模拟进程池

3.4 命名管道介绍

3.4.1 命名管道概念

3.4.2 mkfifo和unlink小实验

3.5 命名管道示例代码

3.5.2 命名管道示例完整代码:

4. 笔试选择题:

答案及解析

本篇完。


1. VSCode环境安装

下载可以在官网直接下载,慢的话可以直接复制这个链接:

https://vscode.cdn.azure.cn/stable/30d9c6cd9483b2cc586687151bcbcd635f373630/VSCodeUserSetup-x64-1.68.1.exe

安装很简单,可以自己安装一下,不行就搜一搜。

1.1 使用VSCode

VSCode至少是一个编辑器,和Vim一样。

刚打开应该是英文的,点击左边应用搜索chinese,直接安装简体中文就行。

在桌面创建一个空文件夹,然后在VSCode中点击打开文件夹,选择桌面,选择刚创建的文件夹打开就行。然后这里选择新建文件:

这里新建test.c文件,此时右下角可能会有安装C/C++的插件等,安装就行。

(左下角设置可以修改字体大小)写一段代码,Ctrl s保存:

再新建一个test.cpp文件,写一段代码并Ctrl s保存,然后顺便创建一个文件夹试试:

 也可以创建其它语言的文件,此时桌面刚才新建的文件夹就有了在VSCode创建的东西:

运行是运行不了的,这里远程链接我们前面使用的Linux机器(也可以用其它方法)

1.2 远程链接到Linux机器

在应用里搜索Remote并安装,滑下来你可以看到步骤:

这里按F1键,选中Remote-SSH Add,输入ssh 你的用户名@你的公网IP(xshell登录可以看)

 

回车后按第一行就配置好了,这里可以关掉重新启动VSCode,然后左边的远程资源管理器就出现了你刚才链接的机器。

这里右键机器,然后选择上面一行的在当前窗口链接,然后选择Linux,输入刚才用户名密码

 然后这里打钩了就是链接好了:

这里打开xshll,在平时写代码的路径创建一个TestVSCode目录:

 点击左上角的资源管理器,新建文件夹,默认就是我们在xshell的路径:

 选择TestVSCode目录,确定,选择Linux,然后输密码就行,

这里连接好了,新建一个test.cpp文件,写点代码,Ctrl S保存:

 此时在xshell就自动同步我们的文件和代码了,编译运行试试:

你也可以在VSCode下 Ctrl ~ 调出终端输入命令:

 

 成功成功。

1.3 VSCode调试

C++相关的插件刚才应该也一带安装了,没安装的这里打钩都能安装了:

 这里安装下GDB Debug,然后在左边类似小爬虫的图标上点击这里:

然后选择GDB Debug调试器:

这个小方括号里面的内容都可以删掉了,然后点击左边的添加配置,选择第一行什么gdb 启动:

然后只需要改这一行:"program": "输入程序名称,例如 ${workspaceFolder}/a.out",

改成:"program": "${workspaceFolder}/test_gdb",

Ctrl 保存,在终端生成test_gdb调试文件,运行:

 然后在点小爬虫左上角的开始调试(F5)就行,快捷键都和VS2022差不多的:

断点也能直接打,左边变量啥的都能看到,但是VSCode的调试有时会很卡,所以不常用,

据此,VSCode的简单使用就讲完了。

2. 进程间通讯介绍

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

2.1 进程间通讯的概念和意义

什么是进程间通信?:

进程间通信是两个或者多个进程之间进行通信,行为如下:

  • 数据传输:一个进程需要将它的数据发送给另一个进程。
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

为什么要进行通信?:

我们在之前讲过 "进程之间是具有独立性" 的,如果进程间想交互数据,成本会非常高。

因为独立性之本质即 "封闭",进程们你封闭你的我封闭我的,那么进程间的交流可谓是窒碍难行。

  • 进程间的通信说白了就是 "数据交互",在操作系统中,进程是独立运行的程序,它们之间需要相互协作完成任务。进程间通信的目的是为了实现进程之间的数据共享、协作和同步,从而提高系统的效率和可靠性。

很多场景下我们需要多进程进行协同处理一件事情。

不要以为,进程独立了就是彻底独立,有时我们需要双方能够进行一定程序的信息交互。

2.2 进程间通讯的策略和本质

如何进行进程通信?:

当前主要是通过三种策略来实现进程间通信的,每一种策略下都有很多种通信方式:

① 管道:通过文件系统通信。
匿名管道
命名管道


② System Ⅴ:聚焦在本地通信。
共享内存
消息队列
信号量


③ POSIX:让通信可以跨主机。
共享内存
消息队列
信号量
互斥量
条件变量
读写锁

进程通信的本质是什么?:

我们知道,进程是相互独立的,所以进程之间的通信成本肯定不低。

为了进程在通信的时候,既能满足进程之间的独立性,又能够到达通信的目的,那么进程之间通信的地点就不能在两个进程中。

一个进程将自己的数据交给另一个进程,并且还要等待另一个进程的应答,这样一来,这个进程将不独立了,受到了另一个进程的影响,与进程的独立性矛盾。
所以,两个进程进行通信的地点必须是由第三方提供的,第三方只能是操作系统。操作系统提供的这个地点被我们称为:公共资源。

公共资源有了,还必须让要通信的进程都看到这一份公共资源,此时要通信的进程将有了通信的前提。之后就是进程通信,也就是访问这块公共资源的数据。

之所以有不同的通信方式,是因为公共资源的种类不一,如果公共资源是一块内存,那么通信方式就叫做共享内存,如果公共资源是一个文件,也就是struct file结构体,那么就叫做管道。

3. 管道

3.1 管道介绍

什么是管道?管道是 Unix 系统中最古老的 IPC 形式,

一个进程连接到另一个进程的数据流称为管道 (Pipe)。

我们来回忆一下文件系统:

父进程打开一个文件,操作系统在内存上创建一个struct file结构体对象,里面包含文件的各种属性,以及对磁盘文件的操作方法。每个struct file对象中还有一个内核缓冲区,这个缓冲区中可以存放数据。当子进程创建的时候,父进程的文件描述符表会被子进程继承下去,所以此时子进程在相同的fd处也指向父进程打开的文件。文件描述符表一个进程维护一个,但是struct file结构体对象在内存中只有一个,由操作系统维护。此时,父子进程将看到了同一份公共资源,也就是操作系统在内存中维护的struct file对象,并且父子进程也都和这份资源建立了连接。此时父子进程通信的基础有了,它们就可以通信了。

父进程向文件中写内容,写完后继续干自己的事,并不破坏父进程的独立性。
子进程向文件中读内容,读完后继续干自己的事,并不破坏子进程的独立性。

这样一读一写,父子进程将完成了一次进程间通信。

而我们又知道,对文件进行IO操作时,由于需要访问硬盘,所以速度非常的慢,而且我们发现,父子间进行通信,磁盘中文件的内容并不重要,重要的是父进程写了什么,子进程又读到了什么。

此时操作系统为了提高效率,就关闭了内存中struct file和硬盘中文件进行IO的通道。
父进程写数据写到了struct file的内核缓冲区中。
子进程读数据从struct file的内核缓冲区中读取。
此时,父子间通信仍然正常进行,并且效率还非常的高,而且还没有影响进程的独立性。而这种不进行IO的文件叫做内存级文件。

这种由文件系统提供公共资源的进程间通信,就叫做管道。

两个进程就通过管道建立起了连接,并且可以进程进程之间的通信。而管道又分为匿名管道和命名管道。

3.2 匿名管道介绍

  • 匿名管道:顾名思义,就是没有名字的文件(struct file)。
  • 匿名管道只能用于父子进程间通信,或者由一个父进程创建的兄弟进程之间进行通信。

现在我们知道了匿名管道就是没有名字的文件,通过管道进行通信时,只需要通信双方打开同一个文件就可以。

我们通过系统调用open打开文件的时候,会指定打开方式,是读还是写。

当父进程以写方式打开一个文件的时候,创建的子进程会继承父进程的一切。
此时子进程也是以写的方式打开的这个文件。
既然是通信,势必有一方在写,一方在读,而现在父子双方都是以写的方式打开,它们怎么进行通信呢?:父进程以读和写的方式打开同一份文件两次。

这样一来,创建子进程后,父子进程都可以对管道进行读和写,它们就可以进行通信了,上面的问题就解决了。之所以命名为管道,那么就有和管道类似的性质。在生活中,我们对水管,它的流向只能是单向的,管道也一样,通过管道建立的通信只能进行单向数据通信

如上图,假设父进程对管道写,子进程对管道读。

  • 为了防止父进程对管道进行误读,以及子进程对管道进行误写,破坏通信规则。
  • 将父进程的读端关闭,将子进程的写端关闭,使用系统调用close(fd)。

此时,父子进程之间的单向数据通信就建立起来了,下一步就可以进行通信了。如果想进行双向通信,可以建立两个管道。

3.3 匿名管道示例代码

示例:从键盘读取数据,写入管道,读取管道,写到屏幕。

这里在rtx2目录下新建一个linux_17目录,在里面新建一个pipe目录,在VSCode写代码:

前面弄好了VSCode(没弄好用Vim写也行),打开VSCode打开上面的pipe文件夹,链接,输普通用户的密码,写个Makefile:

3.3.1 建立管道的pipe

上面都是理论上的,具体到代码中是如何建立管道的呢?

既然是操作系统中的文件系统提供的公共资源,当然是用系统调用来建立管道了。

 原型 int pipe(int pipefd[2]);

#include <unistd.h>

功能:创建一个匿名管道

形参:int pipefd[2]是一个输出型参数,一个数组,该数组只有两个元素,下标分别为0和1。

下标为0的元素表示的是管道读端的文件描述符fd。

下标为1的元素表示的是管道写端的文件描述符fd。

这里巧记:pipefd[0(嘴巴,读书)]: 读端 , pipefd[1(钢笔,写)]: 写端

返回值:返回0,管道创建成功。返回-1,管道创建失败,并将错误码自动写入errno中。

使用系统调用pipe,直接就会得到两个fd,并且放入父进程的文件描述符表中,不用打开内存级文件两次。那么,父进创建管道以后,得到的两个文件描述符是多少呢?根据前面所学,是3和4吗?我们代码中来看,mypipe.cpp:

#include <iostream>
#include <cerrno> // C++包C语言头文件常用的方法,和.h效果一样
#include <cstring>
#include <unistd.h> // pipe

using namespace std;

int main()
{
    int pipefd[2];
    int ret = pipe(pipefd); // 一.创建管道
    if(ret < 0)
    {
        cerr << errno << ": " << strerror(errno) << endl;
    }
    cout << "pipefd[0]: " << pipefd[0] << endl; // 3
    cout << "pipefd[1]: " << pipefd[1] << endl; // 4

    return 0;
}

可以看到,创建管道后返回的两个fd值,果然是3和4,因为0,1,2分别被stdin,stdout,stderr占用。知道了如何使用系统调用创建管道以后,接下来就创建子进程,然后关闭不需要的端口了,原理已经清楚,直接看代码。

#include <iostream>
#include <cerrno> // C++包C语言头文件常用的方法,和.h效果一样
#include <cstring>
#include <cassert>
#include <unistd.h> // pipe + close

using namespace std;

int main()
{
    int pipefd[2];
    int ret = pipe(pipefd); // 一.创建管道
    if(ret < 0)
    {
        cerr << errno << ": " << strerror(errno) << endl;
    }
    // cout << "pipefd[0]: " << pipefd[0] << endl; // 3
    // cout << "pipefd[1]: " << pipefd[1] << endl; // 4

    pid_t id = fork(); // 二.创建子进程
    assert(id != -1);
    if (id == 0) // 子进程,读,关闭写
    {
        close(pipefd[1]);

        // 通信

        close(pipefd[0]);
        exit(0);
    }
    close(pipefd[0]); // 父进程,写,关闭读

    // 通信

    close(pipefd[1]);
    return 0;
}

此时在代码层面上, 父子双方就已经建立了连接了,接下来就是通信数据了。

子进程读,代码:

父进程写,代码:

3.3.2 匿名管道完整代码

#include <iostream>
#include <cerrno> // C++包C语言头文件常用的方法,和.h效果一样
#include <cstring>
#include <cassert>
#include <unistd.h> // pipe + close + read + write
#include <sys/types.h> // waitpid两个头文件
#include <sys/wait.h>

using namespace std;

int main()
{
    int pipefd[2];
    int ret = pipe(pipefd); // 一.创建管道
    if(ret < 0)
    {
        cerr << errno << ": " << strerror(errno) << endl;
    }
    // cout << "pipefd[0]: " << pipefd[0] << endl; // 3
    // cout << "pipefd[1]: " << pipefd[1] << endl; // 4

    pid_t id = fork(); // 二.创建子进程
    assert(id != -1);
    if (id == 0) // 子进程,读,关闭写
    {
        close(pipefd[1]);
        // 三. 子进程读
        char buffer[1024 * 8];
        while (true)
        {
            // sleep(10);
            ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1); // read读
            if (s > 0) // 写入的一方,fd没有关闭,如果有数据,就读,没有数据就等
            {
                buffer[s] = 0;
                cout << "child get a message[" << getpid() << "] Father# " << buffer << endl;
            }
            else if (s == 0) // 写入的一方,fd关闭, 读取的一方,read会返回0,表示读到了文件的结尾
            {
                cout << "writer quit(father), me quit" << endl;
                break;
            }
        }
        close(pipefd[0]);
        exit(0);
    }

    close(pipefd[0]); // 父进程,写,关闭读
    // 四. 父进程写
    string message = "我是父进程,我正在给你发消息";
    int count = 0;
    char send_buffer[1024 * 8];
    while (true)
    {
        //构建一个变化的字符串
        snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d",message.c_str(), getpid(), count++);
        write(pipefd[1], send_buffer, strlen(send_buffer)); // 写入
        sleep(1);
        cout << count << endl;
        if (count == 5) 
        {
            cout << "writer quit(father)" << endl;
            break;
        }
    }
    close(pipefd[1]);
    pid_t ret_id = waitpid(id, nullptr, 0);
    cout << "id : " << id << " ret_id: " << ret << endl;
    assert(ret_id > 0); // 断言只在debug起效
    (void)ret_id; // 只是证明ret_id被使用过

    return 0;
}

这就实现了简单的匿名管道。

可以自己改代码试一试管道的读取特征:

场景特征
读取慢,写入快写入端阻塞在write处
读取快,写入慢读取端阻塞在read处
读取端关闭操作系统终结写端
写入端关闭读取端read返回0

管道之所以有这样的读取特征,其实是为了对管道中的数据进行保护,这种方式称为互斥,后面会详细讲解这一概念。

匿名管道本身也有它自己的特征,如下:

  • 管道的生命周期随进程的结束而结束,当所有进程都关闭该管道的文件描述符时,管道被销毁。
  • 管道可以用来进行具有血缘关系的进程之间进行通信,常用于父子进程通信。
  • 管道是半双工的通信方式(单向通信)
  • 管道是面向字节流的(在网络部分讲解)。
  • 管道有互斥与同步机制对共享资源进行保护(以后讲解)。

3.3.3 匿名管道模拟进程池

这里在linux_17目录下创建ProcessPool目录,在VSCode打开,在里面写代码,

直接放匿名管道模拟进程池的代码了,可以自己跟着注释读一遍,也可以跟着写一写:

Makefile:

ProcessPool:ProcessPool.cpp
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f ProcessPool

ProcessPool.cpp:

#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "Task.hpp" // 发任务的文件,.hpp -> .h和.cpp写一起

#define PROCESS_NUM 5 // 创建的子进程数目

using namespace std;

int waitCommand(int waitFd, bool& quit) //如果对方不发,我们就阻塞
{
    uint32_t command = 0; // uint32_t四个字节
    ssize_t s = read(waitFd, &command, sizeof(command)); // 期望读取四个字节
    if (s == 0) // 读到0让子进程退出
    {
        quit = true;
        return -1;
    }
    assert(s == sizeof(uint32_t)); // 不是四个字节就报错
    return command;
}

void sendAndWakeup(pid_t who, int fd, uint32_t command)   // 通过文件描述符,向哪一个文件发什么命令
{               // who给哪个进程,这个进程的id
    write(fd, &command, sizeof(command));
    cout << "main process: call process " << who << " execute " << desc[command] << " through " << fd << endl;
}

int main()
{
    // 代码中关于fd的处理,有一个小问题,不影响我们使用,但是你能找到吗??
    load();
    vector<pair<pid_t, int>> slots; // 存放子进程pid和子进程写端id(pipefd)
    vector<int> deleteFd; // 存放要删除的子进程写端fd(不删除也不会出问题)
    for (int i = 0; i < PROCESS_NUM; i++) // 先创建多个进程
    {
        int pipefd[2] = { 0 };
        int ret = pipe(pipefd); // 创建管道
        assert(ret == 0); // 等于0才创建成功
        (void)ret;

        pid_t id = fork();
        assert(id != -1);
        if (id == 0) // 子进程,进行读取
        {
            close(pipefd[1]); // 关闭写端
            for (int i = 0; i < deleteFd.size(); i++) // 关闭所以继承下来的写端fd
            {
                close(deleteFd[i]);
            }
            while (true)
            {
                // 等命令
                bool quit = false; // 默认不退出
                int command = waitCommand(pipefd[0], quit); // 如果对方不发,我们就阻塞
                if (quit) // 读到0就退出关闭所有进程
                {
                    break;
                }
                if (command >= 0 && command < handlerSize()) // 执行对应的命令
                {                                            // handlerSize任务方法的个数
                    callbacks[command]();
                }
                else
                {
                    cout << "非法command: " << command << endl;
                }
            }
            exit(1);
        }

        close(pipefd[0]); // 父进程,进行写入,关闭读端
        slots.push_back(pair<pid_t, int>(id, pipefd[1])); // 把此次循环得到的子进程id和子进程写端的id保存
        deleteFd.push_back(pipefd[1]); // 把要被继承下去的子进程写端fd保存起来
    }

    // 父进程均衡地派发任务(单机版的负载均衡)
    srand((unsigned long)time(nullptr) ^ getpid() ^ 2335643123L); // 仅仅让数据源更随机
    while (true)
    {
        // 选择一个任务
        int command = rand() % handlerSize();
        // 选择一个进程 ,采用随机数的方式,选择进程来完成任务,随机数方式的负载均衡
        int choice = rand() % slots.size();
        // 把任务给指定的进程
        sendAndWakeup(slots[choice].first, slots[choice].second, command);
        sleep(1);

         //int select; // 手动版
         //int command;
         //cout << "############################################" << endl;
         //cout << "#   1. show funcitons      2.send command  #" << endl;
         //cout << "############################################" << endl;
         //cout << "Please Select> ";
         //cin >> select;
         //if (select == 1)
         //{
         //    showHandler();
         //}
         //else if (select == 2)
         //{
         //    cout << "Enter Your Command: ";
         //    cin >> command; // 选择任务
         //    int choice = rand() % slots.size(); // 选择进程
         //    sendAndWakeup(slots[choice].first, slots[choice].second, command); // 把任务给指定的进程
         //}
         //else // 选择错误
         //{
         //}
    }

    for (const auto& slot : slots) // 关闭fd, 所有的子进程都会退出
    {
        close(slot.second);
    }

    for (const auto& slot : slots) // 回收所有的子进程信息
    {
        waitpid(slot.first, nullptr, 0);
    }
}

Task.hpp:

#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <unistd.h>
#include <functional>

typedef std::function<void()> func;

std::vector<func> callbacks; // 存放若干个回调
std::unordered_map<int, std::string> desc; // 查看有多少方法用的

void readMySQL()
{
    std::cout << "sub process[" << getpid() << " ] 执行访问数据库的任务\n" << std::endl;
}

void execuleUrl()
{
    std::cout << "sub process[" << getpid() << " ] 执行url解析\n" << std::endl;
}

void cal()
{
    std::cout << "sub process[" << getpid() << " ] 执行加密任务\n" << std::endl;
}

void save()
{
    std::cout << "sub process[" << getpid() << " ] 执行数据持久化任务\n" << std::endl;
}

void load() // 操作表,先插入描述再插入方法,下标就对齐了
{
    desc.insert({ callbacks.size(), "readMySQL: 读取数据库" });
    callbacks.push_back(readMySQL);

    desc.insert({ callbacks.size(), "execuleUrl: 进行url解析" });
    callbacks.push_back(execuleUrl);

    desc.insert({ callbacks.size(), "cal: 进行加密计算" });
    callbacks.push_back(cal);

    desc.insert({ callbacks.size(), "save: 进行数据的文件保存" });
    callbacks.push_back(save);
}

void showHandler() // 查看有多少方法
{
    for (const auto& iter : desc)
    {
        std::cout << iter.first << "\t" << iter.second << std::endl; // \t制表符
    }
}

int handlerSize() // 直接返回有多少个任务的方法
{
    return callbacks.size();
}

编译运行:

3.4 命名管道介绍

3.4.1 命名管道概念

命名管道:顾名思义,有名字的管道(内存级文件)。

根据前面的学习,我们知道,父子进程间使用匿名管道的方式进行通信,是通过子进程继承父进程的方式来实现,而且匿名匿名管道常用于父子进程直接,或者由血缘关系的进程直接。

那么,如果两个进程毫无关系呢?此时就不能继承了,那这两个进程如何建立连接呢?

还是采用管道的方式,但是这个管道是有名字的管道,这样一来,两个进程就可以打开同一个管道文件建立连接。

还是这张图,此时内存中的struct file在磁盘上有对应文件的,如上图中的fifo.ipc文件。

3.4.2 mkfifo和unlink小实验

创建命名管道指令,man mkfifo:

  • 指令:mkfifo 文件名
  • 功能:创建命名管道文件

FIFO(first in first out)因为管道是单向通信的,这里在linux_17目录下创建fifo目录,进入并建立一个命名管道:

此时就成功地建立了一个命名管道,可以发现它的(文件类型)权限前面的字母是p(pipe),而目录的文件类型是d(directory)。命名管道文件类型是p,而且该文件还有inode,说明在磁盘上是真实存在的。

当磁盘中有了命名管道文件以后,两个进程将可以通过这个管道文件进行通信了,步骤和匿名管道非常相似。一个进程以写方式打开管道文件,另一个进程以读端方式打开管道文件。此时两个进程将建立了连接,然后将可以进行通信了。我们知道,进程间通信的前提是,要通信的进程能够看到同一份公共资源,那么命名管道是如何做到这一点的呢?

让不同的进程打开指定路径下同一个管道文件。

往name_pipe写点东西:

此时发现类似堵塞住了? 此时处于的就是阻塞状态,它需要被另一个进程读取:

此时就相当于完成了两个进程之间的通讯。

你可以通过unlink或者rm删掉命名管道(效果是差不多的),man unlink

3.5 命名管道示例代码

可以在shell中通过命令的方式创建管道文件,两个进程直接去使用它。也可以像文件一样,在进程中创建管道文件,此时就需要用到系统调用。man 3 mkfifo:

  • 第一个形参:管道文件的名字
  • 第二个形参:创建管道文件的权限
  • 返回值:0表示创建成功,-1表示创建失败。

在进程中删除管道文件:man 2 unlink:

写个测试代码:

如果带上unlink:

此时命名管道在进程里打开,也在进程里关闭了。

3.5.2 命名管道示例完整代码:

在VSCode打开上面的fifo文件夹,在里面写代码,

Makefile:

.PHONY:all
all:client mutiServer

client:client.cpp
	g++ -o $@ $^ -std=c++11
mutiServer:server.cpp
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f client mutiServer

comm.hpp:(一些头文件和宏和一个路径)

#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 <sys/wait.h>
#include "Log.hpp"

using namespace std;

#define MODE 0666
#define SIZE 128

string ipcPath = "./fifo.ipc";

#endif

Log.hpp:(打印日志,不加也行)

#ifndef _LOG_H_
#define _LOG_H_

#include <iostream>
#include <ctime>

#define Debug   0
#define Notice  1
#define Warning 2
#define Error   3

const std::string msg[] ={"Debug", "Notice", "Warning", "Error"};

std::ostream& Log(std::string message, int level)
{
    std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
    return std::cout;
}

#endif

server.cpp:(服务端)

#include "comm.hpp"

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) // 文件结尾end of file
        {
            cerr << "[" << getpid() << "] " << "read end of file, clien quit, server quit too" << endl;
            break;
        }
        else // 读取失败
        {
            perror("read");
            break;
        }
    }
}

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

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

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

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

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

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

    return 0;
}

client.cpp:(用户端)

#include "comm.hpp"

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

    string buffer; // 2. ipc过程
    while (true)
    {
        cout << "Please Enter Message Line : ";
        getline(cin, buffer);
        write(fd, buffer.c_str(), buffer.size());
    }

    close(fd); // 3. 关闭
    return 0;
}

make之后先运行服务端再运行客户端,就能实现类似微信发信息的功能(客户端Ctrl C关闭):

这样就实现了命名管道的通信方式,建议自己写一遍代码测试一下。

4. 笔试选择题:

1. 以下描述正确的有:

A.进程之间可以直接通过地址访问进行相互通信

B.进程之间不可以直接通过地址访问进行相互通信

C.所有的进程间通信都是通过内核中的缓冲区实现的

D.以上都是错误的

2. 以下选项属于进程间通信的是()[多选]

A.管道

B.套接字

C.内存

D.消息队列

3.  下列关于管道(Pipe)通信的叙述中,正确的是() 

A.一个管道可以实现双向数据传输

B.管道的容量仅受磁盘容量大小限制

C.进程对管道进行读操作和写操作都可能被阻塞

D.一个管道只能有一个读进程或一个写进程对其操作

4. 以下关于管道的描述中,正确的是 [多选]

A.匿名管道可以用于任意进程间通信

B.匿名管道只能用于具有亲缘关系的进程间通信

C.在创建子进程之后也可以通过创建匿名管道实现父子进程间通信

D.必须在创建子进程之前创建匿名管道才能实现父子进程间通信

5. 以下关于管道的描述中错误的是  [多选]

A.可以通过int pipe(int pipefd[2])接口创建匿名管道,其中pipefd[0]用于从管道中读取数据

B.可以通过int pipe(int pipefd[2])接口创建匿名管道,其中pipefd[0]用于向管道中写入数据

C.若在所有进程中将管道的写端关闭,则从管道中读取数据时会返回-1;

D.管道的本质是内核中的一块缓冲区;

6. 以下关于管道描述正确的有:

A.命名管道可以用于同一主机上的任意进程间通信

B.向命名管道中写入的数据越多,则管道文件越大

C.若以只读的方式打开命名管道时,则打开操作会报错

D.命名管道可以实现双向通信


7. 以下关于管道描述正确的有:

A.命名管道和匿名管道的区别在于命名管道是通过普通文件实现的

B.命名管道在磁盘空间足够的情况下可以持续写入数据

C.多个进程在通过管道通信时,删除管道文件则无法继续通信

D.命名管道的本质和匿名管道的本质相同都是内核中的一块缓冲区

答案及解析

1. B

A错误: 进程之间具有独立性,拥有自己的虚拟地址空间,因此无法通过各自的虚拟地址进行通信(A的地址经过B的页表映射不一定映射在什么位置)

B正确

C错误: 除了内核中的缓冲区之外还有文件以及网络通信的方式可以实现D

2. ABD                

典型进程间通信方式:管道,共享内存,消息队列,信号量。 除此之外还有网络通信,以及文件等多种方式

C选项,这里的内存太过宽泛,并没有特指某种技术,错误。

3. C

A.一个管道可以实现双向数据传输

B.管道的容量仅受磁盘容量大小限制

C.进程对管道进行读操作和写操作都可能被阻塞

D.一个管道只能有一个读进程或一个写进程对其操作

4. ABD

A.匿名管道可以用于任意进程间通信

B.匿名管道只能用于具有亲缘关系的进程间通信

C.在创建子进程之后也可以通过创建匿名管道实现父子进程间通信

D.必须在创建子进程之前创建匿名管道才能实现父子进程间通信

5. BC

  • 管道本质是内核中的一块缓冲区,多个进程通过访问同一块缓冲区实现通信。
  • 使用int pipe(int pipefd[2])接口创建匿名管道,pipefd[0]用于从管道读取数据,pipefd[1]用于向管道写入数据。
  • 管道特性:半双工通信,自带同步与互斥,生命周期随进程,提供字节流传输服务。
  • 在同步的提现中,若管道所有写段关闭,则从管道中读取完所有数据后,继续read会返回0,不再阻塞;若所有读端关闭,则继续write写入会触发异常导致进程退出

根据以上管道理解分析:A正确,B错误,C错误,D正确

6. A

  • 匿名管道只能用于具有亲缘关系的进程间通信,命名管道可用于同一主机上的任意进程间通信
  • 管道的通信本质是通过内核中一块缓冲区(内存)时间数据传输,而命名管道的管道文件只是一个标识符,用于让多个进程能够访问同一块缓冲区
  • 管道是半双工通信,是可以选择方向的单向通信
  • 命名管道打开特性为,若以只读方式打开文件,则会阻塞,直到管道被以写的方式打开,反之亦然

7. D

A错误, 管道的本质是内核中的缓冲区,命名管道文件是缓冲区的标识

B错误, 管道在缓冲区写满后会写阻塞,跟磁盘空间并无关系

C错误, 管道的生命周期随进程,本质是内核中的缓冲区,命名管道文件只是标识,用于让多个进程找到同一块缓冲区,删除后,之前已经打开管道的进程依然可以通信

D正确

再就是自己实现匿名管道和命名管道。

本篇完。

下一篇:零基础Linux_18(进程间通信)共享内存+消息队列+信号量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1091894.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YOLOv5算法改进(10)— 如何去添加多层注意力机制(包括代码+添加步骤+网络结构图)

前言:Hello大家好,我是小哥谈。注意力机制是近年来深度学习领域内的研究热点,可以帮助模型更好地关注重要的特征,从而提高模型的性能。注意力机制可被应用于模型的不同层级,以便更好地捕捉图像中的细节和特征,这种模型在计算资源有限的情况下,可以实现更好的性能和效率。…

二十一、【文本工具组】

文章目录 横排文本工具字符选项卡段落文字 横排文本工具 需要注意的是一些不是免费的商业字体&#xff0c;一定不要拿去使用&#xff0c;否则后边很容易会受到法律索赔。 在制作海报等一些图形时&#xff0c;需要经常用到文本工具我们需要对文本进行变形&#xff0c;分段&…

Qt开发工程师成系统性长体系教程

QT跨平台开发工程师必备技术栈 基础原理-案例分析-项目实战&#xff0c;紧跟QT开发岗位技术需求. 一、Qt C 语言编程基础专栏 1.1 Qt C 语言编程基础 Visual Studio 2022安装C语言基础概述C指针与引用C类与对象(一)C类与对象(二)类的基它特性构造函数&析构函数&拷贝…

rhel8 nmcli学习

25.3.1 配置动态IP连接 25.3.1.1 配置IP 要使用 DHCP 分配网络时&#xff0c;可以使用动态IP配置添加网络配置文件&#xff0c;命令格式如下&#xff1a; # nmcli connection add type ethernet con-name connection-name ifname interface-name 例如创建名为net-test的动态…

虚拟机的发展史:从分时系统到容器化

一、前世 早期计算机的价格非常昂贵&#xff0c;一台计算机可能需要花费几十万甚至上百万美元。例如&#xff0c;ENIAC计算机&#xff0c;作为世界上第一台通用电子数字计算机&#xff0c;当时的造价约为48万美元。科学家或者工程师们需要计算机的能力&#xff0c;但是买不起整…

C. JoyboardCodeforces Round 902

C. Joyboard 样例1列表找规律&#xff1a; #include<iostream> #define int long long using namespace std; signed main() {int T;cin>>T;while(T--){int n,m,k;cin>>n>>m>>k;if(k1){cout<<1<<endl;}else if(k2){cout<<m…

vue2时间处理插件——dayjs

在vue时间处理上有很多的方法和实现&#xff0c;可以自己实现&#xff0c;但是效率不高&#xff0c;所以&#xff0c;在框架开发中我们一般不会手写&#xff0c;一般是使用集成的第三方插件来解决我们的问题&#xff0c;在vue3中大家一般都使用Moment.js来处理&#xff0c;所以…

Defects4j数据集安装及使用

一、Defects4j数据集安装 在Ubuntu系统上进行操作&#xff0c;具体的在&#xff1a;Defects4j数据集安装 二、Defects4j数据集的使用 1. 常用命令 Getting started ---------------- #### Setting up Defects4J 1. Clone Defects4J:- git clone https://github.com/rjust/d…

JOSEF约瑟 可调漏电继电器RT-LB230KS+Q-FL-100 导轨安装 配套零序互感器

一、产品用途及特点 RT-LB230KS漏电继电器&#xff08;以下简称继电器&#xff09;适用于交流电压为660V.至1140V电压系统中,频率为50Hz,电流15~4000A线路中做有无中性点漏电保护. 该继电器可与带分励脱扣器或失压脱扣器的断路器、交流接触器、磁力启动器等组成漏电保护装置&…

【ELK 使用指南】ELK + Filebeat 分布式日志管理平台部署

ELK和EFLK 一、前言1.1 日志分析的作用1.2 需要收集的日志1.3 完整日志系统的基本特征 二、ELK概述2.1 ELK简介2.2 为什么要用ELK?2.3 ELK的组件 三、ELK组件详解3.1 Logstash3.1.1 简介3.1.2 Logstash命令常用选项3.1.3 Logstash 的输入和输出流3.1.4 Logstash配置文件 3.2 E…

端到端的机器学习项目(Machine Learning 研习之六)

使用真实数据 当你在研习机器学习时&#xff0c;最好是使用真实世界中的数据&#xff0c;而不是采用人工数据。巧的是&#xff0c;数以千计的数据集可供选择&#xff0c;涵盖了各种领域。 流行的开放数据存储库&#xff1a; OpenML.orgKaggle.compaperswithcode.com UC Irvin…

阿里云服务器不能访问网络之安装mysql 提示连接超时

wget -i -c http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm 过了一段时间后提示 fail .......time out 链接超时 有可能你的服务器不能访问网络 因为宽带套餐 我购买的时候没有购 重新购买就行了

python爬取boss直聘数据(selenium+xpath)

文章目录 一、主要目标二、开发环境三、selenium安装和驱动下载四、主要思路五、代码展示和说明1、导入相关库2、启动浏览器3、搜索框定位创建csv文件招聘页面数据解析(XPATH)总代码效果展示 六、总结 一、主要目标 以boss直聘为目标网站&#xff0c;主要目的是爬取下图中的所…

【Candence报错】Discrepancy #i in TASK

问题描述 Candence LVS仿真提示网络不匹配 问题解决 检查原理图和Layout 注意&#xff1a;

穿越功耗墙,我们该从哪些方面提升“性能”?

目录 背景 功耗&#xff1a;CPU 的“人体极限” 并行优化&#xff0c;理解阿姆达尔定律 总结延伸 背景 我们知道 CPU 的性能时&#xff0c;我们提到了这样一个公式&#xff1a; 程序的 CPU 执行时间 指令数CPIClock Cycle Time 这么来看&#xff0c;如果要提升计算机的…

tracy 学习

https://github.com/wolfpld/tracy 适用于游戏和其他应用的实时、纳秒分辨率、远程控制、支持采样和帧率检测 Tracy 支持分析 CPU&#xff08;为 C、C 和 Lua 集成提供直接支持。同时&#xff0c;互联网上存在许多其他语言的第三方绑定&#xff0c;例如 Rust 、Zig、C # 、 OC…

【git】gitlab安装、备份

gitlab官网 官网&#xff1a;官网 中文官网&#xff1a;中文官网 作为一个英文不好的程序员&#xff0c;所以我都去中文网站去看了。下面也是带着大家去走走 安装gitlab 我不想写具体的安装方法&#xff0c;直接去逛网看下面是我的截图。步骤非常详细。 安装文档地址&…

Apacheb Shiro 1.2.4反序列化漏洞(CVE-2016-4437)

Apache Shiro 1.2.4反序列化漏洞&#xff08;CVE-2016-4437&#xff09; 1 在线漏洞解读: https://vulhub.org/#/environments/shiro/CVE-2016-4437/2 环境搭建 cd /home/kali/vulhub/shiro/CVE-2016-4437启动&#xff1a; sudo docker-compose up -d # 拉取下载并启动sud…

谢邀,ADconf安全大会

儒道易行 道虽远&#xff0c;行则易至&#xff1b;儒虽难&#xff0c;坚为易成 文笔生疏&#xff0c;措辞浅薄&#xff0c;望各位大佬不吝赐教&#xff0c;万分感谢。 免责声明&#xff1a;由于传播或利用此文所提供的信息、技术或方法而造成的任何直接或间接的后果及损失&am…

Linux:mongodb数据库源码包安装(4.4.25版本)

环境 系统&#xff1a;centos7 本机ip&#xff1a;192.168.254.1 准备的mongodb包 版本 &#xff1a; 4.4.25 全名称&#xff1a;mongodb-linux-x86_64-rhel70-4.4.25.tgz 下载源码包 Download MongoDB Community Server | MongoDBhttps://www.mongodb.com/try/downloa…