【Linux初阶】进程间通信介绍 管道

🌟hello,各位读者大大们你们好呀🌟
🍭🍭系列专栏:【Linux初阶】
✒️✒️本篇内容:进程间通信介绍,管道概述,匿名管道+应用,命名管道+应用
🚢🚢作者简介:计算机海洋的新进船长一枚,请多多指教( •̀֊•́ ) ̖́-


文章目录

  • 一、进程间通信介绍
    • 1.进程间通信目的
    • 2.进程间通信发展
    • 3.进程间通信分类
      • 管道
      • System V IPC
      • POSIX IPC
  • 二、管道概述
    • 1.什么是管道
    • 2.进程间通信的原理
    • 3.站在文件描述符角度-深度理解管道
  • 三、匿名管道
    • 1.匿名管道概述及代码实现
    • 2.管道的读写特征(4种情况)
    • 3.管道的特征(5种特征)
  • 四、匿名管道应用
    • 1.匿名管道在命令行中的应用
    • 2.基于匿名管道的进程池设计
  • 五、命名管道
    • 1.命名管道概述
    • 2.创建一个命名管道
      • (1)命令行创建命名管道
      • (2)程序创建命名管道
    • 3.匿名管道与命名管道的区别
    • 4.命名管道的打开规则
  • 六、用命名管道实现server&client通信
  • 结语


一、进程间通信介绍

1.进程间通信目的

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

2.进程间通信发展

  • 管道
  • System V进程间通信(聚焦本地通信)
  • POSIX进程间通信(让通信过程可以跨主机)

3.进程间通信分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

二、管道概述

1.什么是管道

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道"
  • 管道分匿名管道命名管道

2.进程间通信的原理

我们知道,进程间通信需要一定的成本,那么这个成本是怎么产生的呢?这就要我们理解通信的本质了:1.OS直接或间接给通信双方的进程提供“内存空间”2.要通信的进程,必须看到同一份公共的资源

这个很好理解,因为进程具有独立性,所以需要有一个公共的地方让两个进程完成信息或资源的传输。我们所学的不同的通信种类,就是以操作系统提供的不同模块进行区别分类的

任何一个文件(struct file)都有 file的操作方法、属于自己的内核缓冲区strcut Page[]。当我们进行 fork创建子进程的时候,我们会将父进程的 task_strcut文件描述符表拷贝一份,子进程文件描述符表指向的文件在不做修改的情况下和父进程文件描述符表指向的文件相同。此时,我们就能让父子两个不同的进程看到同一份资源了。
在这里插入图片描述

在能看到同一份资源的基础上,如果父进程可以向文件缓冲区中写入,子进程能从文件缓冲区中读取,不就完成了进程间的通信了吗。我们将操作系统提供的这个内核级文件,我们称之为管道文件

进程间通信的数据不会刷新到磁盘,而是在OS内部完成,因为刷新到磁盘会大大降低通信的速度。管道文件是一个内存级文件,通信只需要在OS内部完成,因此我们可以不关心它在磁盘的路径,甚至这个文件可以是虚拟出来的。

3.站在文件描述符角度-深度理解管道

下面我们一起来认识一下管道通信的基本原理:

在这里插入图片描述

上图的步骤执行完之后,就可以实现将父进程的数据传输到子进程中去了。通过对上面图解的学习,我们不难知道:管道通常只能进行单向数据通信!单向通信相比于双向通信更简单且更具有依赖性。

最终,设计者们根据这个方案的特性(有出入口、单向传输),为这个方案/方法取名为 - 管道


三、匿名管道

1.匿名管道概述及代码实现

我们知道通信的实现需要两个步骤:1.让不同的进程拥有一份共享资源;2.通信。在文章之前的内容中,我们已经对第一点的原理进行了讲解,下面我将带着大家重一起学习管道的具体实现(开辟共享资源+通信)。

匿名管道:目前能用来进行父子进程间的通信

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

因为文件描述符0,1,2已经被占用,因此在理想情况下也需要用3,4代表读取和写入端,由于我们不清楚系统申请读写的顺序,所以我们习惯上直接使用fd[0]表示读端, fd[1]表示写端。

读取和写入端巧记:[0]: 读取,嘴巴,读书的;[1]: 写入,钢笔,写的

代码示例:

#include <iostream>
#include <cstdio> //snprintf
#include <cstring> //write
#include <string>
#include <cassert>
#include <unistd.h> //sleep wait
#include <sys/types.h>
#include <sys/wait.h>


using namespace std;

// 父进程进行读取,子进程进行写入
int main()
{
    // 第一步:创建管道文件,打开读写端
    int fds[2];
    int n = pipe(fds);
    assert(n == 0);

    // 第二步: fork
    pid_t id = fork();
    assert(id >= 0);
    if (id == 0)
    {
        // 子进程进行写入 - 关闭读
        close(fds[0]);
        // 子进程的通信代码
        const char* s = "我是子进程,我正在给你发消息";
        int cnt = 0;
        while (true)
        {
            cnt++;
            char buffer[1024]; // 只有子进程能看到!
            snprintf(buffer, sizeof buffer, "child->parent say: %s[%d][%d]", s, cnt, getpid());//格式化输出

            // 写端写满的时候,再写会阻塞,等对方进行读取!
            write(fds[1], buffer, strlen(buffer));
            cout << "count: " << cnt << endl;

			//sleep(2); - 用于测试写端阻塞
        }

        // 子进程
        close(fds[1]); // 子进程关闭写端fd
        cout << "子进程关闭自己的写端" << endl;

        exit(0);
    }
    
    // 父进程进行读取
    close(fds[1]);
    // 父进程的通信代码
    while (true)
    {
        //sleep(2); - 用于测试读端阻塞
        char buffer[1024];

        // 如果管道中没有了数据,读端在读,默认会直接阻塞当前正在读取的进程!
        ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1); //buffer如果满了,s代表下一个未被使用的字节数

        if (s > 0)
        {
            buffer[s] = 0;
            cout << "Get Message# " << buffer << " | my pid: " << getpid() << endl;
        }
        else if (s == 0)
        {
            //读到文件结尾 - 读完之后s为0 - 跳出循环结束读取
            cout << "read: " << s << endl;
            break;
        }
        break;

    }
    close(fds[0]);
    cout << "父进程关闭读端" << endl;

    int status = 0;
    n = waitpid(id, &status, 0);
    assert(n == id);

    cout << "pid->" << n << " : " << (status & 0x7F) << endl;

    return 0;
}

在这里插入图片描述
———— 我是一条知识分割线 ————

2.管道的读写特征(4种情况)

读写特征:

  1. 管道是一个固定大小的缓冲区

  2. 读慢、写快:写端将缓冲区写满的时候,再写(写端)会阻塞,需要等对方进行读取!

  3. 读快、写慢:如果管道中没有了数据,读端在读,默认会直接阻塞当前正在读取的进程!

  4. 写关闭、读到0:根据自己代码的判断,进行不同的操作;
    在这里插入图片描述

  5. 读关闭、一直写:为避免资源浪费,OS会关闭写端,即给写进程发送信号,终止写端。

———— 我是一条知识分割线 ————

3.管道的特征(5种特征)

  1. 管道的生命周期随进程;
  2. 管道可以用来实现具有血缘关系(父子、兄弟)进程之间的通信,常用于父子进程通信;
  3. 管道是面向字节流的(网路);
  4. 一般而言,内核会对管道操作进行同步与互斥,同步与互斥机制是一种对共享资源的一种保护方案;
  5. 管道是半双工的,即数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
    在这里插入图片描述

四、匿名管道应用

1.匿名管道在命令行中的应用

我们在命令行中经常会使用这样的指令

cat file.txt | grep "hello"

它实质上就是通过树划线对指令进行了分割,让操作系统 fork两个子进程对分割好的两条指令进行实现,再通过兄弟管道之间的进程通信,最终变成我们看到的样子的!

2.基于匿名管道的进程池设计

我们可以用匿名管道设计一个简易的进程池,即用一个辅助进程控制其他进程完成工作。在父进程(写端)我们通过给不同的子进程(读端)传输不同的命令操作码(commandCode),让对应的子进程根据命令操作码实现不同的功能,我们让命令操作码为 4字节。

场景示例:我们可以给 2号进程发送命令操作码和对应的运算数据,唤醒 2号进程之后,让进程对命令操作码对应的功能(运算)进行实现。

我们希望:我们可以将我们的任务均衡的下发给每一个子进程,让子进程执行 - 负载均衡(单机)。

在这里插入图片描述

代码示例

  • Makefile
processpool:processPool.cc
g++ - o $@ $ ^ -std = c++11
.PHONY:clean
clean :
rm - rf processpool
  • ProcessPool.cc
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <ctime>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#define MakeSeed() srand((unsigned long)time(nullptr) ^ getpid() ^ 0x171237 ^ rand() % 1234)//随机数种子

#define PROCSS_NUM 10

///子进程要完成的某种任务 -- 模拟一下/
// 函数指针 类型
typedef void (*func_t)();

void downLoadTask()
{
    std::cout << getpid() << ": 下载任务\n"
        << std::endl;
    sleep(1);
}

void ioTask()
{
    std::cout << getpid() << ": IO任务\n"
        << std::endl;
    sleep(1);
}

void flushTask()
{
    std::cout << getpid() << ": 刷新任务\n"
        << std::endl;
    sleep(1);
}

void loadTaskFunc(std::vector<func_t>* out) //加载方法表
{
    assert(out);
    out->push_back(downLoadTask);
    out->push_back(ioTask);
    out->push_back(flushTask);
}

/下面的代码是一个多进程程序//
class subEp // Endpoint
{
public:
    subEp(pid_t subId, int writeFd)
        : subId_(subId), writeFd_(writeFd)
    {
        char nameBuffer[1024];  //定义对象名称
        snprintf(nameBuffer, sizeof nameBuffer, "process-%d[pid(%d)-fd(%d)]", num++, subId_, writeFd_);
        name_ = nameBuffer;
    }

public:
    static int num; //用于区别不同对象(整体)
    std::string name_;  //管道+子进程的整体名称
    pid_t subId_;   //子进程pid
    int writeFd_;   //父进程写端的文件描述符
};

int subEp::num = 0;

int recvTask(int readFd) //读端接口
{
    int code = 0;
    ssize_t s = read(readFd, &code, sizeof code);
    if (s == 4) return code;
    else if (s <= 0) return -1;
    else return 0;
}

void sendTask(const subEp& process, int taskNum)
{
    std::cout << "send task num: " << taskNum << " send to -> " << process.name_ << std::endl;
    int n = write(process.writeFd_, &taskNum, sizeof(taskNum));
    assert(n == sizeof(int));
    (void)n;
}

void createSubProcess(std::vector<subEp>* subs, std::vector<func_t>& funcMap)
{
    std::vector<int> deleteFd;  //管道+子进程整体选择(可以选择不同整体)
    for (int i = 0; i < PROCSS_NUM; i++)
    {
        int fds[2];
        int n = pipe(fds);
        assert(n == 0);
        (void)n;
        // 父进程打开的文件,是会被子进程共享的
        // 你试着多想几轮 - 子进程会继承上一个子进程的写端
        pid_t id = fork();
        if (id == 0)
        {
            for (int i = 0; i < deleteFd.size(); i++) close(deleteFd[i]); //关闭从上一个子进程继承而来的写端
            // 子进程, 进行处理任务
            close(fds[1]);
            while (true)
            {
                // 1. 获取命令码,如果没有发送,我们子进程应该阻塞
                int commandCode = recvTask(fds[0]);
                // 2. 完成任务
                if (commandCode >= 0 && commandCode < funcMap.size())
                    funcMap[commandCode]();
                else if (commandCode == -1) break;
            }
            exit(0);
        }
        close(fds[0]);
        subEp sub(id, fds[1]);  //构造一个整体对象
        subs->push_back(sub);   //将新整体链接到vector中
        deleteFd.push_back(fds[1]); //保存文件描述符,方便以后关闭从上一个子进程继承而来的写端
    }
}

void loadBlanceContrl(const std::vector<subEp>& subs, const std::vector<func_t>& funcMap, int count)
{
    int processnum = subs.size();
    int tasknum = funcMap.size();
    bool forever = (count == 0 ? true : false);

    while (true)
    {
        // 1. 选择一个子进程 --> std::vector<subEp> -> index - 随机数(负载均衡)
        int subIdx = rand() % processnum;
        // 2. 选择一个任务 --> std::vector<func_t> -> index
        int taskIdx = rand() % tasknum;
        // 3. 任务发送给选择的进程
        sendTask(subs[subIdx], taskIdx);
        sleep(1);   //防止发送过快
        if (!forever)
        {
            count--;
            if (count == 0) break;
        }
    }
    // write quit -> read 0
    for (int i = 0; i < processnum; i++) close(subs[i].writeFd_); // waitpid();
}


void waitProcess(std::vector<subEp> processes)
{
    int processnum = processes.size();
    for (int i = 0; i < processnum; i++)
    {
        waitpid(processes[i].subId_, nullptr, 0);
        std::cout << "wait sub process success ...: " << processes[i].subId_ << std::endl;
    }
}

int main()
{
    MakeSeed(); //生成随机数种子
    // 1. 建立子进程并建立和子进程通信的信道, 有bug的,但是不影响我们后面编写
    // 1.1 加载方法表
    std::vector<func_t> funcMap;
    loadTaskFunc(&funcMap);
    // 1.2 创建子进程,并且维护好父子通信信道
    std::vector<subEp> subs;
    createSubProcess(&subs, funcMap);

    // 2. 走到这里就是父进程, 控制子进程,负载均衡的向子进程发送命令码
    int taskCnt = 3; // 0: 永远进行,>0: 父进程发送几次
    loadBlanceContrl(subs, funcMap, taskCnt);

    // 3. 回收子进程信息
    waitProcess(subs);

    return 0;
}

在这里插入图片描述


五、命名管道

1.命名管道概述

  • 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
  • 如果我们想在不相关(不具有亲缘关系)的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道
  • 命名管道是一种特殊类型的文件。

2.创建一个命名管道

(1)命令行创建命名管道

$ mkfifo filename

在这里插入图片描述
在这里插入图片描述

———— 我是一条知识分割线 ————

(2)程序创建命名管道

命名管道也可以从程序里创建,相关函数有:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename,mode_t mode);

3.匿名管道与命名管道的区别

  • 匿名管道由pipe函数创建并打开。它通过文件指针的方式,让子进程继承父进程指向的文件,再通过文件描述符的方式看到同一份资源。
  • 命名管道由mkfifo函数创建,打开用open。它通过让不同进程看到同一个路径标定的文件,是它们能看到同一份资源。
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完 成之后,它们具有相同的语义。

4.命名管道的打开规则

  • 只有读端打开FIFO,写端没打开:阻塞直到有相应进程为写而打开该FIFO。
  • 只有写端打开FIFO,读端没打开:阻塞直到有相应进程为读而打开该FIFO。

六、用命名管道实现server&client通信

这里我们先要搞清楚一个概念,命名管道如何让不同的进程,看到同一份资源?

可以让不同的进程打开指定名称(路径+文件名)的同一文件

  • comm.hpp
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define NAMED_PIPE "/tmp/mypipe"

bool createFifo(const std::string& path)
{
    umask(0);
    int n = mkfifo(path.c_str(), 0600);
    if (n == 0)
        return true;
    else
    {
        std::cout << "errno: " << errno << " err string: " << strerror(errno) << std::endl;
        return false;
    }
}

void removeFifo(const std::string& path)
{
    int n = unlink(path.c_str()); //删除管道
    assert(n == 0); // debug , release 里面就没有了
    (void)n;
}
  • client.cc(写端)
#include "comm.hpp"

int main()
{
    std::cout << "client begin" << std::endl;
    int wfd = open(NAMED_PIPE, O_WRONLY);
    std::cout << "client end" << std::endl;
    if (wfd < 0) exit(1);

    //write
    char buffer[1024];
    while (true)
    {
        std::cout << "Please Say# ";
        fgets(buffer, sizeof(buffer), stdin); // abcd\n
        if (strlen(buffer) > 0) buffer[strlen(buffer) - 1] = 0; //去掉输入的\n
        ssize_t n = write(wfd, buffer, strlen(buffer));
        assert(n == strlen(buffer));
        (void)n;
    }

    close(wfd);
    return 0;
}
  • server.cc(读端)
#include "comm.hpp"

int main()
{
    bool r = createFifo(NAMED_PIPE);
    assert(r);
    (void)r;

    std::cout << "server begin" << std::endl;
    int rfd = open(NAMED_PIPE, O_RDONLY);
    std::cout << "server end" << std::endl;
    if (rfd < 0) exit(1);

    //read
    char buffer[1024];
    while (true)
    {
        ssize_t s = read(rfd, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << "client->server# " << buffer << std::endl;
        }
        else if (s == 0)
        {
            std::cout << "client quit, me too!" << std::endl;
            break;
        }
        else
        {
            std::cout << "err string: " << strerror(errno) << std::endl;
            break;
        }
    }

    close(rfd);

    // sleep(10);
    removeFifo(NAMED_PIPE);
    return 0;
}

注意:命名管道文件必须读端、写端都打开才能开始运行

在这里插入图片描述

注意:当我们关闭写端,读端会跟着关闭(代码实现的!)

在这里插入图片描述


结语

🌹🌹 进程间通信介绍 & 管道 的知识大概就讲到这里啦,博主后续会继续更新更多C++ 和 Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何在 Spring Boot 中集成日志框架 SLF4J、Log4j

文章目录 具体步骤附录 笔者的操作环境&#xff1a; Spring Cloud Alibaba&#xff1a;2022.0.0.0-RC2 Spring Cloud&#xff1a;2022.0.0 Spring Boot&#xff1a;3.0.2 Nacos 2.2.3 Maven 3.8.3 JDK 17.0.7 IntelliJ IDEA 2022.3.1 (Ultimate Edition) 具体步骤 因为 …

HTTP代理编程:Python实用技巧与代码实例

今天我要与大家分享一些关于HTTP代理编程的实用技巧和Python代码实例。作为一名HTTP代理产品供应商&#xff0c;希望通过这篇文章&#xff0c;帮助你们掌握一些高效且实用的编程技巧&#xff0c;提高开发和使用HTTP代理产品的能力。 一、使用Python的requests库发送HTTP请求&a…

【ElasticSearch入门】

目录 1.ElasticSearch的简介 2.用数据库实现搜素的功能 3.ES的核心概念 3.1 NRT(Near Realtime)近实时 3.2 cluster集群&#xff0c;ES是一个分布式的系统 3.3 Node节点&#xff0c;就是集群中的一台服务器 3.4 index 索引&#xff08;索引库&#xff09; 3.5 type类型 3.6 doc…

STM32F429IGT6使用CubeMX配置串口通信

1、硬件电路 2、设置RCC&#xff0c;选择高速外部时钟HSE,时钟设置为180MHz 3、配置USART1引脚 4、生成工程配置 5、部分代码 //重定向printf函数 int fputc(int ch, FILE *f) {HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);return ch; } /* USER CODE BE…

Mac M1 安装Oracle Java 与 IEDA

文章目录 1 官网下载2 安装IDEA参考 1 官网下载 https://www.oracle.com/ 使用finder中的拖拽进行安装即可 2 安装IDEA https://www.jetbrains.com/zh-cn/idea/download/?sectionmac 同样的&#xff0c;下载完后拖拽安装即可 参考 Mac M1 安装Java 开发环境 https://blog.…

cuda+anaconda+pytorch按照教程

首先安装显卡对应的CUDA版本&#xff0c;关键点在于区别显卡支持的CUDA最高版本和运行版本 1、查看当前显卡支持的最高版本&#xff0c;有两种方式&#xff1a; 1&#xff09;NVIDIA控制面板—>帮助—>系统信息—>组件—>NVCUDA.dll对应版本 请注意&#xff0c;12…

快速上手React:从概述到组件与事件处理

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

React源码解析18(1)------ React.createElement 和 jsx

1.React.createElement 我们知道在React17版本之前&#xff0c;我们在项目中是一定需要引入react的。 import React from “react” 即便我们有时候没有使用到React&#xff0c;也需要引入。原因是什么呢&#xff1f; 在React项目中&#xff0c;如果我们使用了模板语法JSX&am…

Spring-1-深入理解Spring XML中的依赖注入(DI):简化Java应用程序开发

学习目标 前两篇文章我们介绍了什么是Spring,以及Spring的一些核心概念&#xff0c;并且快速快发一个Spring项目&#xff0c;以及详细讲解IOC&#xff0c;今天详细介绍一些DI(依赖注入) 能够配置setter方式注入属性值 能够配置构造方式注入属性值 能够理解什么是自动装配 一、…

【C语言】每日一题---1

大家好&#xff0c;我是苏貝&#xff0c;本篇博客是系列博客每日一题的第一篇&#xff0c;本系列的题都不会太难&#xff0c;如果大家对这种系列的博客感兴趣的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 下面代码的结果是&#xff1a; #include <…

【Nginx】Nginx负载均衡

负载均衡&#xff1a;通过反向代理来实现 Nginx的七层代理和四层代理&#xff1a; 七层是最常用的反向代理方式&#xff0c;只能配置在nginx配置文件的http模块当中 &#xff1b;配置的方法名称为&#xff1a;upstream模块&#xff0c;不能写在server中也不能写在location中&a…

爬虫ip池越大越好吗?

作为一名资深的程序员&#xff0c;今天我要给大家分享一些关于爬虫ip池的知识。关于ip代理池的问题&#xff0c;答案是肯定的&#xff0c;池子越大越好。下面跟我一起来盘点一下ip池大的好处吧&#xff01; 1、提高稳定性 爬虫ip池越大&#xff0c;意味着拥有更多可用的爬虫ip…

大O表示法表示算法运行时间

大O表示法用来度量一个算法的运行时间。书写为O(n)&#xff0c;其中n为一个算法所执行的操作次数。当我们讨论算法的运行时间时&#xff0c;说的是一个算法在给定的输入列表增加的情况下算法执行操作数的增速&#xff0c;也就是运行时间的增速。 二分查找算法 下面介绍两种简…

锐捷设备密码管理、密码恢复、恢复出厂设置

目录 配置登录用户名密码以及Enable密码 只需要密码登录 需要用户名和密码登录&#xff08;无AAA&#xff09; 需要用户名和密码登录&#xff08;有AAA&#xff09; 密码恢复 Web密码忘记 Telnet/SSH密码忘记 Console密码忘记 所有密码都忘记&#xff0c;通过Console进…

Java GUI,mybatis实现资产管理系统

Java GUI——资产管理系统 前言&#xff1a;为了做java课设&#xff0c;学了一手Java GUI。感觉蛮有意思的&#xff0c;写写文章&#xff0c;做个视频记录一下。欢迎大家友善指出我的不足 资产管理系统录制视频&#xff0c;从头敲到尾 模块划分 资产信息管理 资产信息查询 …

SpringBoot在线失物招领系统

一个基于SpringBootSemanticUI的pc Web在线失物招领系统 http://localhost:8080/swzl/index 主页 http://localhost:8080/swzl/login 登录页 用户表user admin字段为true是管理员 false用户 springboot2.3 springmvc mybatis html ajax idea 或eclipse maven mys…

【Linux进行时】进程概念

进程的概念 什么是进程呢&#xff1f; ❓首先我们需要认识一下什么叫进程呢&#xff1f; 课本概念&#xff1a;程序的一个执行实例&#xff0c;正在执行的程序等 &#x1f525;内核观点&#xff1a;担当分配系统资源&#xff08;CPU时间&#xff0c;内存&#xff09;的实体。…

SpringBoot06---前端路由VueRouter

单页面应用&#xff0c;意思是只有一个html&#xff0c;变化的内容是不同组件进行切换&#xff0c;每个组件加载网络请求&#xff0c;渲染对应的数据&#xff0c;这个内容就是学习怎么完成组件切换 以网易云音乐为例&#xff1a; 网易云音乐 (163.com) 现在无需注册&#xf…

springBoot的日志文件

日志是程序的重要组成部分&#xff0c;主要可以用来定位和排查问题。除此之外&#xff0c;还可以用来&#xff1a; 1. 记录用户的登录日志&#xff0c;方便分析用户是正常登录还是恶意破解&#xff1b; 2. 记录系统的操作日志&#xff0c;方便数据恢复和定位操作人&#xff1b;…

docker 安装elasticsearch、kibana

下载es镜像 docker pull elasticsearch 启动es容器 docker run --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.typesingle-node" -e ES_JAVA_OPTS"-Xms512m -Xmx512m" -d elasticsearch 验证es界面访问 ​​​​​http://节点ip:9200/ ​…
最新文章