Linux中进程间通信--匿名管道和命名管道

news2024/11/26 3:42:15

        本篇将会进入 Linux 进程中进程间通信,本篇简要的介绍了 Linux 中进程为什么需要通信,进程间通信的常用方式。然后详细的介绍了 Linux 进程间的管道通信方式,管道通信分为匿名管道和命名管道,本篇分别介绍了其实现的原理,以及使用 Linux 中的系统调用,用代码实现了这两种通信方式。

        还详细的介绍了关于管道通信的五种特征和四种情况。

目录

Linux中进程间通信

1. 进程之间为什么需要通信

2. 进程之间如何进行通信

3. 进程间通信常用方式

匿名管道

1. 匿名管道实现原理

2. 匿名管道的实现规定

3. 模拟管道进程通信

5. 匿名管道在命令行中的方式

6. 基于匿名管道的进程池

6. 该代码容易出现的 bug

管道通信的特征与情况

1. 管道通信的五种特征

2. 管道通信的四种情况

命名管道 

1. 命名管道实现原理

2. 管道文件的特点

3. 使用命名管道通信

Linux中进程间通信

1. 进程之间为什么需要通信

        Linux 中的各种进程之间是相互独立的,但是进程和进程之间也是需要相互协调工作的,所以进程之间就会存在相互通信,一切协调完成工作。

        在用于通信的数据中,数据是有类别的,比如:用于通知就绪的、单纯传递给我的数据、控制相关的信息等等。

2. 进程之间如何进行通信

        首先,对于进程之间的通信,成本会比较高。因为进程之间相互独立,也就意味着进程之间的数据也是相互独立的,通信的本质就是信息之间的传递(数据之间的传递),所以进程之间并不能直接的进行通信(传递数据)。

        那么对于一个特殊的例子:父子进程之间算不算天然具有通信的能力呢?从严格意义上来说着并不算一种通信方式,虽然子进程可以继承父进程的数据和代码,但是子进程和父进程之间并不能一直交换数据(写时拷贝会将数据修改),所以并不能算得上是通信。

        所以对于进程间通信的前提:让不同的进程,看到同一份操作系统级别的资源,也就是同一段内存。如下:

        所以对于以上进程之间的通信:

        a. 一定是某个进程先需要通信,然后让操作系统创建一个共享资源;

        b. 操作系统必须提供很多的系统调用,进程需要使用系统调用让操作系统申请资源。

        c. 进程间通信会存在不同的种类,所以操作系统共享的资源会不同,系统调用接口也会不同。

3. 进程间通信常用方式

        进程间通信常用的两套标准为 system V 和 Posix 标准。对于这两个标准,只做介绍并不详解。

        对于 system V 标准来说,一共存在三种方式:消息队列、贡献内存、信号量。三种方式。但是在这些方式出来之前,还存在一种更为简单的通信方式:管道(直接复用内核代码进行直接通信)

        管道又分为命名管道匿名管道

匿名管道

1. 匿名管道实现原理

        我们首先先介绍关于匿名管道的实现原理,如下图:

        如上图:对于存在磁盘中的一个文件,父进程首先使用读方式和写方式分别打开该文件,然后就会依次创建出文件结构体以及文件的各种缓冲区,接着将文件结构体中的各种指针指向这些缓冲区,然后将文件结构体链接到父进程的文件描述符表中。

        然后父进程创建出它的子进程,子进程会开辟出空间,然后继承父进程的代码和数据,但是对于父进程的文件系统中的数据并不会创建出独立的空间用于存放,所以子进程创建出来的文件描述符表同样会指向之前父进程打开的文件结构体

        通过以上操作之后,子进程和父进程之间也就存在指向同一个文件了,也就是满足了进程之间通信的前提:不同进程可以看见同一块内存空间、同一份文件缓冲区(这个文件被我们称为管道文件)(其实这也是为什么每个进程会默认打开三个标准输入输出标准错误流(0、1、2)的原因:因为每个创建的子进程都是 bash 创建的,bash 是打开这三个流的,所以子进程会进程 bash 的文件描述符表,同样指向 0、1、2)。

        那么为什么我们子进程使用 close 关闭文件的时候,父进程也还可以使用呢?

        这是因为关于文件结构体 struct file 会存在着内存级的引用计数,几个进程指向该文件就有几个引用计数,当引用计数变为 0 的时候才会将文件关闭。

2. 匿名管道的实现规定

        对于管道而言,我们规定其只能单向通信,也就是意味只有两种通信方式:父进程发,子进程收 或者 子进程发、父进程收。所以我们在创建出了管道文件之后,我们需要将文件描述符表对应的管道文件关闭,比如父进程发消息、子进程收消息时,父进程关闭读方式的管道文件、子进程关闭写方式的管道文件。

        我们在以上创建出管道文件之后,我们只会用其进行进程之间的通信,也就是不需要将通信的内容刷新到磁盘中,所以我们可以重新设计出通信接口,只设计出内存级的管道文件,不用访问磁盘,如下:

        将其简化之后,就是下图:

        关于以上的规定,匿名管道只允许单向进行通信,那假设我们要进行双向通信呢,我们只需要将创建出两个管道文件就可以进行双向通信了

        为什么需要规定是单向通信呢?这是因为在设计管道通信时就是想要复用代码进行通信,也就是尽可能简单的进行通信,假若设计成双向的通信方式,则需要在管道内分别区分出来自不同进程的信息,这就会大大增加通信的复杂程度。

3. 模拟管道进程通信

        接下来我们将使用代码来模拟管道间的通信,其中有一个很重要的通信接口,如下:

int pipe(int pipefd[2]);

        该接口的底层实现就是调用了文件打开函数 open,不过我们并不需要提供文件路径和文件名,因为生成的是匿名管道文件,pipefd[0] 表示的是文件读端、pipefd[1] 表示的是文件写端,该接口的返回值若为0表示管道文件设置成功,非零则表示设置失败。

        现在我们通过如下代码来查看子进程向父进程发送消息,父进程接收信息之后将信息打印出来,如下代码:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <sys/wait.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string>

const int size = 1024;

std::string GetOtherMessage() {
    static int cnt = 0;
    std::string messageid = std::to_string(cnt);
    pid_t rid = getpid();
    std::string child_id = std::to_string(rid);
    std::string retMessage = "messageid: ";
    retMessage += messageid;
    retMessage += " my pid is ";
    retMessage += child_id;
    cnt++;
    return retMessage;
}

void SubProcessWrite(pid_t wfd) {
    std::string message = "father, I am your son! ";
    while (true) {
        std::string info = message + GetOtherMessage();
        write(wfd, info.c_str(), info.size());
        sleep(1);
    }
}

void FatherProcessRead(pid_t rfd) {
    char inbuff[size];
    while (true) {
        int n = read(rfd, inbuff, sizeof(inbuff) - 1);
        if (n > 0) {
            inbuff[n] = '\0';
            std::cout << "father get message: " << inbuff << std::endl;
        }
    }
}

int main () {
    // 创建管道
    int pipefd[2];
    int n = pipe(pipefd);
    if (n != 0) {
        std::cout << "errno: " << errno << " errstring: " << strerror(errno) << std::endl;
        return 1;
    }
    // 创建出子进程
    pid_t id = fork();
    if (id == 0) {
        // 使用子进程进行写,关闭读端
        close(pipefd[0]);
        // 子进程发送信息
        SubProcessWrite(pipefd[1]);
        close(pipefd[1]);
        exit(0);

    }
    // 父进程进行读,关闭写端
    close(pipefd[1]);
    // 父进程接收信息
    FatherProcessRead(pipefd[0]);
    close(pipefd[0]);
    // 等待回收子进程
    int status;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0) 
        std::cout << "wait child process sucess!" << std::endl;
    else
        std::cout << "wait child process fail!" << std::endl;
    return 0;
}

        运行结果如下:

        由上可得,父进程会逐一从管道中读出子进程写入管道的数据。

5. 匿名管道在命令行中的方式

        在命令行中直接使用匿名管道,我们只需要在我们需要执行的命令间加上 " | ",就表示使用管道,如下:

        使用管道同时执行三个指令,这三个指令是同时进行的。

6. 基于匿名管道的进程池

        现在我们将基于以上的知识,使用代码来实现一份基于匿名管道通信的一个进程池项目。该进程池实现的功能为:父进程为任务派送方,然后生成若干子进程,然后将任务均匀的分发至子进程,让子进程分别执行派发下来的任务

        我是在 Linux 下完成的,.cc 后缀的也是 C++ 代码文件,.hpp 也是 C++ 代码的头文件,另外还给出了我的 makefile 文件。

        代码如下:

Task.hpp

#pragma once
#include <iostream>
#include <cstdlib>

#define TASKNNUM 3

typedef void (*task_t)();
task_t tasks[TASKNNUM];

void Print() {
    std::cout << "I am print task" << std::endl;
}

void DownLoad() {
    std::cout << "I am a dowmload task" << std::endl;
}

void Flush() {
    std::cout << "I am a flush task" << std::endl;
}

void LoadTask() {
    srand(time(0) ^ getpid());
    tasks[1] = DownLoad;
    tasks[2] = Flush;
    tasks[0] = Print;

}

size_t SelectTask() {
    int selectnum = rand() % TASKNNUM;
    return selectnum;
}

void SubReadCommand(std::string name) {
    while (true) {
        int command = 1;
        int n = read(0, &command, sizeof(int));
        if (n == sizeof(int)) {
            // 执行对应的程序
            std::cout << "I am " << name << std::endl;
            size_t taskindex = command;
            tasks[taskindex]();
        } else if (n == 0) {
            // 读到0个数,说明读端已经关闭
            std::cout << "sub process : " << getpid() << " quit" << std::endl;
            break;
        }
    }
}

ProcessPool.cc

#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <sys/wait.h>
#include "Task.hpp"

const int size = 256;

class Channel {
public:
    Channel(const std::string& name, pid_t subprocessid, size_t wfd)
        : _name(name)
        , _subprocessid(subprocessid)
        , _wfd(wfd)
    {} 

    pid_t GetSubProcessId() {
        return _subprocessid;
    }

    std::string GetSubName() {
        return _name;
    }

    size_t GetSubWfd() {
        return _wfd;
    }

    // 等待子进程退出
    void SubWait() {
        int status;
        pid_t rid = waitpid(_subprocessid, &status, 0);
        if (rid > 0)
            std::cout << _name << " wait success!" << " exit signal: " << (status & 0x7F) << std::endl;
        else
            std::cout << _name << " wait fail!" << " exit signal: " << (status & 0x7F) << std::endl;
    }

    // 关闭写端
    void CloseWfd() {
        close(_wfd);
        // std::cout << _name << " wfd close success!" << std::endl;
    }

private:
    std::string _name;
    pid_t _subprocessid;
    size_t _wfd;
};



void FatherCreateWorkerProcess(std::vector<Channel>* channels, int num) {
    for (int i = 0; i < num; i++) {
        int pipefd[2];
        int n = pipe(pipefd);
        if (n == -1) {
            std::cerr << "Create pipe fail! " << "error string: " << strerror(errno) << std::endl;
            exit(1);
        }
        // 使用fork创建出子进程
        // 创建出子进程之后,让子进程关闭掉写端,父进程关闭掉读端
        std::string name("worker-");
        name += std::to_string(i);

        pid_t id = fork();
        if (id == 0) {
            if (!channels->empty()) {
                for (auto& channel : *channels)
                    channel.CloseWfd();
            }
            close(pipefd[1]);
            // 子进程一直阻塞等待读入消息
            // 重定向子进程
            dup2(pipefd[0], 0);
            SubReadCommand(name);     
            close(pipefd[0]);       
            exit(0);    
        }
        close(pipefd[0]);
        Channel channel(name, id, pipefd[1]);
        channels->push_back(channel);
    }
}
    
size_t SelectSubProcess(int num) {
    static size_t cnt = 0;
    int ret = cnt;
    cnt++;
    cnt %= num;
    return ret;
}

void ControlProcessOnce(std::vector<Channel>& channels, int num) {
    size_t taskindex = SelectTask();
    // 然后让对应的子进程执行对应的程序
    size_t subindex = SelectSubProcess(num);
    write(channels[subindex].GetSubWfd(), &taskindex, sizeof(taskindex));
    std::cout << std::endl;
    sleep(1);
}

void ControlProcess(std::vector<Channel>& channels, int num, int times = -1) {
    // 让父进程向管道中输入信息
    // 先选中要执行的任务
    if (times > 0) 
        while (times--)
            ControlProcessOnce(channels, num);
    else
        while (true)
            ControlProcessOnce(channels, num);
}

void ClosePipeandSub(std::vector<Channel>& channels) {
    // 逐一关闭对应的,关闭了写端
    for (auto& channel : channels)
        channel.CloseWfd();
    for (auto& channel : channels)
        channel.SubWait();
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cout << "the parameter of command line is not equal two, please input again" << std::endl;
        return 1;
    }
    // 加载任务
    LoadTask();
    // 创建子进程
    std::vector<Channel> channels;
    int channelnums = std::stoi(argv[1]);
    FatherCreateWorkerProcess(&channels, channelnums);

    // for (auto& channel : channels)
    //     std::cout << channel.GetSubName() << std::endl;

    // 控制子进程
    ControlProcess(channels, channelnums, 5);
    // 退出,关闭对饮的管道和子进程
    ClosePipeandSub(channels);
    return 0;
}

makefile

processpool:ProcessPool.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f processpool
6. 该代码容易出现的 bug

        实现该代码容易出现的一个问题为,开辟出来的匿名管道可能有着多个进程指向同一个管道,如下:

        当我们 fork 出第一个子进程创建出管道的时候,我们可以建立出一个一对一的匿名管道通信,当我们继续进行 fork 子进程创建出管道的时候,父进程和子进程仍然会创建一个单独的匿名管道,然后分别关闭对应的读端和写端,但是子进程会继承父进程的数据,也就是会继承指向第一个创建出的管道的文件描述符。以此类推,每创建出一个子进程,指向前面管道的文件描述符都会加一。所以我们在创建子进程的时候就应该关闭这些指向匿名管道的文件描述符,如下:

void FatherCreateWorkerProcess(std::vector<Channel>* channels, int num) {
    for (int i = 0; i < num; i++) {
        int pipefd[2];
        int n = pipe(pipefd);
        if (n == -1) {
            std::cerr << "Create pipe fail! " << "error string: " << strerror(errno) << std::endl;
            exit(1);
        }
        // 使用fork创建出子进程
        // 创建出子进程之后,让子进程关闭掉写端,父进程关闭掉读端
        std::string name("worker-");
        name += std::to_string(i);

        pid_t id = fork();
        if (id == 0) {
            if (!channels->empty()) {
                for (auto& channel : *channels)
                    channel.CloseWfd();
            }
            close(pipefd[1]);
            // 子进程一直阻塞等待读入消息
            // 重定向子进程
            dup2(pipefd[0], 0);
            SubReadCommand(name);     
            close(pipefd[0]);       
            exit(0);    
        }
        close(pipefd[0]);
        Channel channel(name, id, pipefd[1]);
        channels->push_back(channel);
    }
}

        我们只需要在除创建第一个子进程的时候,将指向其他管道给关闭就可以了。

管道通信的特征与情况

1. 管道通信的五种特征

        对于管道一共存在 5 种特征和 4 种情况。

        管道的 5 种特征

        1. 对于匿名管道:只用来进行具有血缘关系的进程之间进行通信,比如兄弟进程、爷孙进程、父子进程,常用于父子进程之间进行通信。因为具有血缘关系的进程可以建立匿名管道

        2. 管道内部,自带进程之间同步的机制,多执行流执行代码的时候,具有明显的顺序性。比如子进程写一个数据,父进程读一个数据;子进程不写数据,父进程就阻塞不读数据。这样一种同步的过程。

        3. 管道文件的生命周期是随进程的。当进程打开该文件并且没有主动关闭,只要指向该文件的进程都关闭了,该文件也会被关闭,管道文件亦是如此。

        4.. 管道文件在通信的时候,是面向字节流的。管道中的读端和写端分别在读和写的时候可以看成是流动的,不过读端和写端他们之间的读取次数和写入次数不一定是一一匹配的,写端可以一次写很多,读端分批读出,写端也可以一次写一点,读端一次读出来。

        5. 管道的通信模式,是一种特殊的半双工模式。半双工的通信模式就是一次只能有一方发消息另一方收消息,也可以反过来。但是管道的通信方式,发消息和收消息方已经确定。

2. 管道通信的四种情况

         管道的四种情况

        1. 管道内部为空且写端的文件描述符并没有关闭,还会写但是还没写。这种情况为:读取条件不具备读进程会被阻塞,读进程会等待读条件具备(也就是写端写入的时候)。所 以这个时候读进程就会被操作系统从运行队列中拿出来放到管道的等待队列中,直到管道中有内容的时候,才会将其唤醒。

        2. 如果管道被写满且读端的文件描述符没有关闭,还能读但是没有读,这种情况为写条件不具备,写进程将会被阻塞,因为在写下去将会覆盖原来的数据,只有当数据被读端读出之后,写段才可以继续写下去。

        3. 读端一直在读,但是写端关闭了写文件描述符,则读端 read 的返回值将会读到0,表示读到了文件末尾。

        4. 读端关闭,写端一直在写入的情况,这种情况为坏管道的情况。一个管道只会有一个读端一个写端,当读端关闭之后,写端在继续写下去就没有意义了,操作系统不会允许这样浪费时间浪费空间的事情发生,因为会无缘无故的浪费资源,所以将会被操作系统认定为一种异常情况,操作系统将会给目标进程发送 13号 SIGPIPE 信号,杀掉对应的写端。

另外,对于写端和写入信息被读取的时候还有两个特点,如下:

        当写端一次写入的数据小于 Linux 中规定的 PIPE_BUF 的时候,那么这个信息是原子的,也就是写入这份数据之后,读端并不能将数据读走。但是当大于 PIPE_BUF的时候,那么有可能写到一半就被读端读走了。

        PIPE_BUF 在 Linux 中一半是 4096 个字节。

命名管道 

1. 命名管道实现原理

        匿名管道用于存在血缘关系(父子、兄弟、爷孙)进程之间的通信,那么命名管道就是用于两个毫不相关之间的进程之间的管道通信。通信原理如下:

        如上图所示,两个毫不相关的进程之间通信会打开同一个文件(每个文件都有唯一的路径),命名管道的通信方式和匿名管道一样,只能一端写一端读。由于两端实时通信之间并不需要将数据刷新到磁盘,所以我们打开这个文件是一个特殊文件,并不需要将数据刷新到磁盘中,这种文件为管道文件

2. 管道文件的特点

        我们创建出管道文件可以使用命令 mkfifo,如下:

mkfifo - make FIFOs (named pipes)

        当写端向管道文件写入消息的之后,假若没有读端将信息读出来,那么写端将会一直阻塞,如下:

        这种情况也符合管道的一种情况,当写端在写信息,但是读端不读且文件描述符未关闭,这就会导致写端处于一种阻塞的状态。

        假若我们将在管道进行写入的时候,将读端的文件描述符给关闭了,那么就会导致我们的 shell 出问题,被强制退出,这是因为当我们将读端的文件描述符给关闭,但写端未关闭,这个时候就会给写端发送 SIGPIPE 信号,然后就会将写端给强制杀掉,但是这个时候写端是由 bash 打开的,所以就会导致我们的 shell 出问题。

        所以不管是命名管道还是匿名管道都遵循管道的四情况和五特征

        管道文件的大小不会变化,不管是否向管道文件中写入,都不会有变化。因为通信双方写入到文件缓冲区之后并不会将信息刷新到磁盘中。

3. 使用命名管道通信

        使用命名管道通信,仍然有一个很重要的系统调用,还是 mkfifo,不过这是在代码级别的,如下:

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

        对于返回值:创建成功返回 0,创建失败返回 -1。

        代码如下:

NamedPipe.hpp:

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>

#define NPCreater 0
#define NPUser    1
#define NPMode 0666
#define NPRead  O_RDONLY
#define NPWrite O_WRONLY
#define DEFAULT_FDX -1
#define NPReadSize 1024

const std::string ComPath = "./myfifo";


class NamedPipe {
private:
    std::string GetName() {
        if (_who == NPCreater)
            return "Creater";
        else if (_who == NPUser)
            return "User";
        else
            return "None";
    }

    // 以不同的方式打开对应的文件
    int OpenNamedPipe(mode_t mode) {
        int fd = open(_path.c_str(), mode);
        if (fd == -1) {
            std::cout << "open file fail" << " the reason is " << strerror(errno) << std::endl;
        }
        return fd;
    }
public:
    NamedPipe(const std::string& path, size_t who)
        : _path(path), _who(who), _fd(DEFAULT_FDX)
    {
        // 让服务器端读,让客户端写,服务器创建出对应的管道文件
        if (who == NPCreater) {
            int n = mkfifo(_path.c_str(), NPMode);
            if (n < 0) {
                std::cout << "create named pipe fail!" << " the reason is " << strerror(errno) << std::endl;
            }
            _fd = OpenNamedPipe(NPRead);
            std::cout << GetName() << " create the named pipe" << std::endl;

        } else {
            _fd = OpenNamedPipe(NPWrite);   
        }
    }

    int SomeoneUseToRead(std::string* out) {
        char inbuff[NPReadSize];
        int n = read(_fd, inbuff, sizeof(inbuff) - 1);
        if (n == -1) {
            std::cout << "read failed" << " the reason is " << strerror(errno) << std::endl;
        } 
        inbuff[n] = '\0';
        *out = inbuff;
        return n;
    }

    void SomeoneUseForWrite(const std::string& info) {
        int n = write(_fd, info.c_str(), info.size());
        if (n == -1) {
            std::cout << "write failed" << " the reason is " << strerror(errno) << std::endl;
        }
    }

    ~NamedPipe() {
        if (_who == NPCreater) {
            // 让创建者删除对应的管道文件
            int n = unlink(_path.c_str());
            if (n < 0) 
                std::cout << "remove the named pipe fail!" << " the reason is " << strerror(errno) << std::endl;    
        }
        std::cout << GetName() << " unlink the named pipe file " << std::endl;
        if (_fd != DEFAULT_FDX) {
            close(_fd);
        }
    }
private:
    std::string _path;
    size_t _who;
    int _fd;
};

server.cc:

#include "NamedPipe.hpp"

int main() {
    NamedPipe fifo(ComPath, NPCreater);
    // 让服务器不断地读
    while (true) {
        std::string message;
        int n = fifo.SomeoneUseToRead(&message);
        if (n == 0) {
            std::cout << "Client quit... Server too" << std::endl;
            break;
        }
        std::cout << "Client Say > " << message << std::endl;
        sleep(1);
    }
    return 0;
}

client.cc:

#include "NamedPipe.hpp"

std::string GetInfo() {
    static int cnt = 0;
    std::string message = "Hello, I am client";
    message += std::to_string(cnt);
    pid_t id = getpid();
    message += " my pid is ";
    message += std::to_string(id);
    cnt++;
    return message;
}

int main() {
    NamedPipe fifo(ComPath, NPUser);

    while (true) {
        std::string info;
        std::cout << "Please Enter > ";
        std::cin >> info;
        fifo.SomeoneUseForWrite(info);
        sleep(1);
    }

    return 0;
}

        代码测试如下:

        由上的测试我们可以得出,对于读端而言,当我们打开文件,但是写端还没有来,进程会阻塞在 open 调用阶段,只有当写端也打开文件之后,读端才会打开文件。这种机制实际上是一种进程同步

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

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

相关文章

4.Java Web开发模式(javaBean+servlet+MVC)

Java Web开发模式 一、Java Web开发模式 1.javaBean简介 JavaBeans是Java中一种特殊的类&#xff0c;可以将多个对象封装到一个对象&#xff08;bean&#xff09;中。特点是可序列化&#xff0c;提供无参构造器&#xff0c;提供getter方法和setter方法访问对象的属性。名称中…

顺序 IO 和 随机IO

顺序 IO 和 随机IO 顺序IO 和 随机IO 是计算机存储系统领域中的概念&#xff0c;主要涉及数据的读取和写入方式。这些术语通常在讨论硬盘驱动器&#xff08;HDDs&#xff09;、固态驱动器&#xff08;SSD&#xff09;以及其他存储设备的性能时使用。 顺序IO&#xff08;Sequen…

TeamViewer关闭访问密码或固定一组密码不变

TeamViewer的新UI界面变化较大&#xff0c;网上的一些信息已经不再有效&#xff0c;更新后的访问密码在如下图所示&#xff1a; 演示的版本为7.21.4—— 设置每次你的设备访问的密码

Hi6274 反激式20瓦电源芯片

HI6274为高性能多模式 PWM 反激式20瓦电源芯片。HI6274较少的外围元器件、较低的系统成本可设计出高性能的"无Y"开关电源。HI6274提供了极为全面和性能优异的智能化保护功能&#xff0c;包括逐周期过流保护、过载保护、软启动、芯片过温保护、可编程输出过压保护功能…

Kettle 登录示例 POST请求

登录接口是post请求&#xff0c;组装Body为json字符串 var body "{\"username\":\""username"\",\"password\": \""password"\",\"code\":\""verification"\",\"uuid\…

【算法/训练】:前缀和差分

&#x1f680; 前言&#xff1a; 前面我们已经通过 【算法/学习】前缀和&&差分-CSDN博客 学习了前缀和&&差分的效相关知识&#xff0c;现在我们开始进行相关题目的练习吧 1. 校门外的树 思路&#xff1a;给[0, n]的数组都标记为1&#xff0c;然后输出m行范围…

初学Mybatis之配置解析

MyBatis 中文网配置教程 mybatis-config.xml 环境配置&#xff08;environments&#xff09; 尽管可以配置多个环境&#xff0c;但每个 SqlSessionFactory 实例只能选择一种环境 可以有多个 enviroment&#xff0c;但是 enviroments default&#xff08;默认&#xff09;只…

Linux:Linux发展史

大家好&#xff01;此篇文章并非技术博文&#xff0c;而是简单了解Linux的时代背景和发展史&#xff0c;只有知其所以然才能让我们更好地让走进Liunx的世界&#xff01; 一、计算机的发展历史背景 首先我们要知道&#xff0c;早期大多数科技的进步都是以国家的对抗为历史背景的…

【优秀设计案例】基于K-Means聚类算法的球员数据聚类分析设计与实现

背景及意义 随着NBA比赛的日益竞争激烈&#xff0c;球队需要更加深入地了解球员的能力和特征&#xff0c;以制定更有效的战术和球队管理策略。而NBA球员的统计数据包含了大量有价值的信息&#xff0c;通过对这些数据进行聚类分析&#xff0c;可以揭示出球员之间的相似性和差异…

Java生成四位纯数字并且确保唯一性

背景&#xff1a; 给了我一个需求&#xff0c;由于某些问题原因&#xff0c;需要给属性和数据添加一个code字段&#xff0c;这是给我发的消息 这两个要求其实是同一个需求&#xff0c;就是在创建对象的时候塞入一个unique的code嘛&#xff0c;听起来很简单吧&#xff0c;但是实…

WPF串口通讯程序

目录 一 设计原型 二 后台源码 一 设计原型 二 后台源码 using HardwareCommunications; using System.IO.Ports; using System.Windows;namespace PortTest {/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainW…

二叉树精选面试题

&#x1f48e; 欢迎大家互三&#xff1a;2的n次方_ 1. 相同的树 100. 相同的树 同时遍历两棵树 判断结构相同&#xff1a;也就是在遍历的过程中&#xff0c;如果有一个节点为null&#xff0c;另一棵树的节点不为null&#xff0c;那么结构就不相同 判断值相同&#xff1a;只需…

【刷题汇总 -- 压缩字符串(一)、chika和蜜柑、 01背包】

C日常刷题积累 今日刷题汇总 - day0181、压缩字符串(一)1.1、题目1.2、思路1.3、程序实现 2、chika和蜜柑2.1、题目2.2、思路2.3、程序实现 3、 01背包3.1、题目3.2、思路3.3、程序实现 -- dp 4、题目链接 今日刷题汇总 - day018 1、压缩字符串(一) 1.1、题目 1.2、思路 读完…

宠物空气净化器哪款除臭效果好?质量好的养狗空气净化器排名

作为一个宠物家电小博主&#xff0c;炎炎夏日&#xff0c;家中的宠物给你带来的不仅仅是温暖的陪伴&#xff0c;还有那挥之不去的宠物异味。普通空气净化器虽然能够应对一般的空气净化需求&#xff0c;但对于养猫家庭特有的挑战&#xff0c;如宠物毛发、皮屑和异味等&#xff0…

模版初阶与STL

1.泛型编程 void Swap(int& left, int& right) {int temp left;left right;right temp; } void Swap(double& left, double& right) {double temp left;left right;right temp; } void Swap(char& left, char& right) {char temp left;left r…

Linux系统安装的详细步骤详解

在VM虚拟机上安装Linux系统全过程&#xff0c;闭眼跟着走就行&#xff01;&#xff01;&#xff01; 1、准备好VMware Worestation虚拟机软件和Linux系统的映像文件 2、点击创建新的虚拟机 3、在新建虚拟机向导中&#xff0c;选择典型安装模式。典型安装模式可以通过几个简单的…

简析漏洞生命周期管理的价值与关键要求

开展全面且持续的漏洞管理工作&#xff0c;对于企业组织改善数字化应用安全状况&#xff0c;降低潜在风险&#xff0c;并保持数字资产的完整性和可信度至关重要。做好漏洞管理并不容易&#xff0c;组织不仅需要拥有健全的漏洞管理策略&#xff0c;同时还要辅以明确定义的漏洞管…

VulnHub:tenderfoot1

靶机下载地址 信息收集 主机发现 扫描攻击机同网段存活主机。nmap 192.168.31.0/24 -Pn -T4 目标主机ip&#xff1a;192.168.31.199。 端口扫描 nmap 192.168.31.199 -A -p- -T4 开放了22,80端口&#xff0c;即ssh和http服务。 目录扫描 访问http服务&#xff0c;是apac…

IPython魔法命令的深入应用

目录 IPython魔法命令的深入应用 一、魔法命令基础 1. 魔法命令的分类 2. 基本使用 二、高级应用技巧 1. 数据交互与处理 2. 交互式编程与调试 三、魔法命令的进阶操作 1. 自定义魔法命令 2. 利用魔法命令优化工作流程 四、总结与展望 IPython魔法命令的深入应用 IP…

指针!!C语言(第二篇)

目录 一. 数组名的理解 二. 一维数组传参的本质 三. 冒泡排序法 四. 二级指针与指针数组 五. 字符指针变量与数组指针 一. 数组名的理解 在我们对指针有了初步的理解之外&#xff0c;今天我们来掌握一些新的知识就是数组与指针&#xff0c;第一个对数组名的了解&#xff…