【Linux】进程间通信——共享内存

news2024/12/21 3:45:56

目录

一、什么是共享内存

二、共享内存的原理

三、使用共享内存实现进程间通信

3.1 shmget接口

3.1.1 key形参详解

3.2 释放共享内存

3.2.1 ipcs指令

3.2.2 ipcrm指令

3.2.3 shmctl接口

3.3 关联共享内存

3.4 去关联共享内存

3.5 使用共享内存进行进程间通信实例

四、共享内存的特性

4.1 共享内存的优点

4.2 共享内存的缺点

五、实例代码改进


一、什么是共享内存

共享内存区是最快的进程间通信(IPC)形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

二、共享内存的原理

要实现进程间通信的根本在于让不同的进程看到同一块份数据,共享内存也不例外

现在有两个毫不相干的进程A和B:

它们想要进行通信,就必须要看到同一份数据,所以操作系统会在物理空间上开辟一块空间,供这两个进程共同访问,这一块空间被称为共享内存

共享内存被页表映射到进程地址空间的存储区域在共享区

想要释放共享内存也很简单,先取消两个进程页表对应的映射关系,就可以释放共享内存块了

但是系统中的进程都可以用共享内存进行通信,所以在任何一个时刻,可能有多个共享内存在被用来进行通信,导致系统中一定会存在很多共享内存同时存在

那OS要不要整体管理所有的共享内存呢?

当然是要的,所以共享内存,不仅仅是我们想的那样,只要在内存中开辟空间即可,系统也要为了管理共享内存,构建对应的描述共享内存的结构体对象!

即:共享内存=共享内存的内核数据结构+真正开辟的内存空间

管理共享内存的数据结构struct shmid_ds:

struct shmid_ds
{
    struct ipc_perm shm_perm; /* Ownership and permissions */
    size_t shm_segsz;         /* Size of segment (bytes) */
    time_t shm_atime;         /* Last attach time */
    time_t shm_dtime;         /* Last detach time */
    time_t shm_ctime;         /* Last change time */
    pid_t shm_cpid;           /* PID of creator */
    pid_t shm_lpid;           /* PID of last shmat(2)/shmdt(2) */
    shmatt_t shm_nattch;      /* No. of current attaches */
    ...
};
struct ipc_perm
{
    key_t __key;          /* Key supplied to shmget(2) */
    uid_t uid;            /* Effective UID of owner */
    gid_t gid;            /* Effective GID of owner */
    uid_t cuid;           /* Effective UID of creator */
    gid_t cgid;           /* Effective GID of creator */
    unsigned short mode;  /* Permissions + SHM_DEST and
                             SHM_LOCKED flags */
    unsigned short __seq; /* Sequence number */
};

三、使用共享内存实现进程间通信

废话不多说,我们直接来上操作

3.1 shmget接口

现在有一个系统接口shmget(包含在头文件<sys/ipc.h>和<sys/shm.h>中)来帮我们建立共享内存:

该函数有三个参数:

key:这个共享内存段名字,要具备唯一性(通常使用ftok函数传入)

size:共享内存大小(以字节为单位),但是OS实际开辟的空间大小是以Page页(4KB)为单位的,底层会将我们传入的空间大小向上取整为Page页的倍数并开辟;但是这不代表OS实际开辟了多少空间我们就可以用多少空间,实际使用空间的大小还是依据我们传入数据的大小,防止越界

shmflg:由九个权限标志构成(它们的用法和创建文件时使用的mode模式标志是一样的),常用的标志位有IPC_CREATIPC_EXCL,另外我们还可以通过该参数设置我们所创建出的共享内存的权限(或上八进制方案)

        IPC_CREAT:可以单独使用,单独使用就是创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回

        IPC_EXCL:不能单独使用,一般都要配合IPC_CREAT使用(IPC_CREAT | IPC_EXCL:创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,则立马出错返回;这样可以保证创建成功时所对应的共享一定是最新的,一定没有被别的进程使用过)

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

3.1.1 key形参详解

操作系统中会存在很多共享内存,那如何让毫不相干的进程识别同一块共享内存空间呢?

我们可以给每个共享内存起名字,key该形参就可以表示共享内存段名字,但是名字可不是随便起的,要保证每一块共享内存的名字不相同我们可以使用ftok函数(包含在头文件<sys/types.h>和<sys/ipc.h>中):

该函数有两个形参:

● pathname:传入文件路径

● proj_id:传入工程id

对于这两个形参并没有强制性的要求,该函数会对传入的两个形参进行特定的算法计算后,返回一个冲突几率极小的值,该值可以作为系统接口shmget的key形参

该函数成功后,将返回生成的key_t值。失败时返回-1,并设置errno

这样子只要两个进程都调用ftok函数,传入相同的形参值就可以得到相同的值,以这个值我们就可以标定同一个共享内存,从而建立进程间的通信

3.2 释放共享内存

我们需要注意的是:当进程创建共享内存之后,当进程结束后,其创建的共享内存空间不会被自动释放,该空间的生命周期是随操作系统的(在下面我们来介绍释放共享内存的方法)

3.2.1 ipcs指令

我们在可以使用ipcs指令用于查看Linux进程间通信设施的状态,包括消息列表、共享内存和信号量的信息,下面是其命令选项:

-i,--id ID
    详细显示指定资源 ID 的 IPC 信息。使用时需要指定资源类型,资源包括消息队列(-q)、共享内存(-m)和信号量(-s)
-h,--help
    显示帮助信息
-V,--version
    显示版本信息

IPC 资源类型选项:
-q,--queues
    显示活动的消息队列信息
-m,--shmems
    显示活动的共享内存信息
-s, --semaphores
    显示活动的信号量信息
-a,--all
    显示系统内所有的IPC信息。命令的默认选项

输出格式选项:当指定多个时,以最后一个为准。
-c,--creator
    查看 IPC 的创建者和所有者
-l,--limits
    查看 IPC 资源的限制信息
-p,--pid
    查看 IPC 资源的创建者和最后操作者的进程 ID
-t,--time
    查看最新调用 IPC 资源的详细时间。包括 msgsnd() 和 msgrcv() 对 message queues 的操作,shmat() 和 shmdt() 对shared memory 的操作,以及 semop() 对 semaphores 的操作
-u,--summary
    查看 IPC 资源状态汇总信息

显示大小单位控制选项:只对选项 -l, --limits 生效
-b,--bytes
    以字节为单位显示大小
--human
    以可读的格式显示大小

下面我们显示活动的共享内存信息:

(nattch表示该共享内存被几个进程关联着)

可以看到在我的系统中有一个key为0x1101120d,shmid为0的共享内存块, 下面我们来释放它 :

3.2.2 ipcrm指令

ipcrm可以删除指定 ID 的 IPC(Inter-Process Communication)对象,包括消息队列(message queue)、共享内存(shared memory)和信号量(semaphore),同时将与 IPC 对象关联的数据一并删除,下面是其命令选项:

-a, --all [shm | msg | sem]
    删除所有 IPC 资源。当给定选项参数 shm、msg 或 sem,则只删除指定类型的 IPC 资源。注意:慎用该选项,否则可能会导致某些程序出于不确定状态
-M, --shmem-key SHMKEY
    当没有进程与共享内存段绑定时,通过 SHMKEY 删除共享内存段
-m, --shmem-id SHMID
    当没有进程与共享内存段绑定时,通过 SHMID 删除共享内存段
-Q, --queue-key MSGKEY
    通过 MSGKEY 删除消息队列
-q, --queue-id MSGID
    通过 MSGID 删除消息队列
-S, --semaphore-key SEMKEY
    通过 SEMKEY 删除信号量
-s, --semaphore-id SEMID
    通过 SEMID 删除信号量
-h, --help
    显示帮助信息并退出
-V, --version
    显示版本信息并退出
-v, --verbose
    以冗余模式执行 ipcrm,输出 rpcrm 正在做什么

下面我们来删除上面的key为0x1101120d,shmid为0的共享内存块,我们使用-m选项加上要删除的shmid即可:

3.2.3 shmctl接口

我们在写代码创建共享内存后总不可能使用系统指令去删除,所以我们需要在代码中调用系统接口shmctl来掌控共享内存:

shmctl函数的有三个参数:

● shmid :共享内存标识符,即要控制的共享内存段的标识符。


● cmd :控制命令,用于指定要执行的操作,比如删除共享内存段、修改权限等。常用的命令包括:

        IPC_RMID (删除共享内存段)

        IPC_SET (在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值)

        IPC_STAT(在进程有足够权限的前提下,把shmid ds结构中的数据设置为共享内存的当前关联值)


● buf :指向 struct shmid_ds 结构的指针,用于存储共享内存段的信息。可以为 NULL ,表示不获取共享内存段的信息。

shmctl函数的返回值是一个整型值,表示函数执行的结果。如果函数执行成功,返回值为0;如果出现错误,返回值为-1,并设置 errno 来指示具体的错误原因。

3.3 关联共享内存

我们创建(获取)了共享内存,那该使两个进程联系起来呢?

我们可以将共享内存段关联到进程地址空间,这样子进程就可以使用所获得的进程地址空间中的地址访问共享内存了

我们调用系统接口shmat(包含在头文件<sys/types.h>和<sys/shm.h>中)来让进程挂接共享内存:

该函数需要3个参数:

● shmid:传入shmget返回的标识符。


● shmaddr:如果传入NULL,系统将自动选择一个合适的进程地址空间! 如果shmaddr不是NULL 并且没有指定SHM_RND,则此段连接到addr所指定的地址上,如果shmaddr非0 并且指定了SHM_RND 则此段连接到shmaddr -(shmaddr mod SHMLAB)所表示的地址上(SHM_RND命令的意思是取整,SHMLAB的意思是低边界地址的倍数,它总是2的乘方。该算式是将地址向下取最近一个 SHMLAB的倍数)。除非只计划在一种硬件上运行应用程序(这在当今是不大可能的),否则不用指定共享段所连接到的地址。所以一般应指定shmaddr为NULL,以便由内核选择地址。

● shmflg:传入SHM_RDONLY,以只读方式连接此段;传入0以读写的方式连接此段

shmat返回值是返回创建的进程虚拟空间的地址 如果出错返回-1

3.4 去关联共享内存

那两个进程通过共享内存通信结束后,我们要取消进程与共享内存之间的关联,这里要用到另一个接口shmdt(包含在头文件<sys/types.h>和<sys/shm.h>中):

该接口有一个参数:

● shmaddr: 传入由shmat所返回的指针

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

注意:将共享内存段与当前进程去关联不等于删除共享内存段

3.5 使用共享内存进行进程间通信实例

下面我们使用两个进程:client和server来实现共享内存进行进程间通信:

common.hpp:

#ifndef __COMM_HPP__
#define __COMM_HPP__

#include <iostream>
#include <cstring>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#endif

#define PATHNAME "."
#define PROJID 0x1111

const int gsize = 4069; // 共享内存大小

std::string To_Hex(int x) // 将十进制转为十六进制
{
    char buffer[64];
    snprintf(buffer, sizeof buffer, "0x%x", x);
    return buffer;
}

const key_t GetKey() // 获取key值
{
    key_t k = ftok(PATHNAME, PROJID);
    if (k == -1)
    {
        std::cerr << "error: " << errno << ": " << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}

static int getshm(key_t key, int size, int flag) // 获取共享内存
{
    int k = shmget(key, size, flag);
    if (k == -1)
    {
        std::cerr << "error: " << errno << ": " << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}

int Create_shm(key_t key, int size) // 创建共享内存段
{
    umask(0);
    return getshm(key, size, IPC_CREAT | IPC_EXCL | 0666); //(IPC_CREAT | IPC_EXCL)以保证创建的共享内存段是最新的,|0666将创建的共享空间的权限让创建者有读写权
}

int Obtain_shm(key_t key, int size) // 获取srever进程创建的共享内存段的标识码
{
    return getshm(key, size, IPC_CREAT);
}

int Delete_shm(int shmid) // 删除共享内存
{
    return shmctl(shmid, IPC_RMID, nullptr);
}

void *Attach_shm(int shmid) // 让进程关联上共享内存
{
    return shmat(shmid, nullptr, 0);
}

void Datach_shm(void *addptr) // 解除进程关联的共享内存
{
    if (shmdt(addptr) == -1)
    {
        std::cerr << "error: " << errno << ": " << strerror(errno) << std::endl;
        exit(1);
    }
}

#define SERVER 0 // 服务段进程
#define CLIENT 1 // 客户端进程

class Init_shm
{
public:
    Init_shm(int type)
        : _type(type)
    {
        _key = GetKey();
        if (type == SERVER)
            _shmid = Create_shm(_key, gsize);
        else
            _shmid = Obtain_shm(_key, gsize);
        _addptr = Attach_shm(_shmid);
    }

    void Get_shm_data()
    {
        struct shmid_ds ds;
        int ret = shmctl(_shmid, IPC_STAT, &ds);
        if (ret == -1)
            std::cerr << "error: " << errno << ": " << strerror(errno) << std::endl;
        else
        {
            std::cout << "create shm process pid: " << ds.shm_cpid << " ,proccess pid:" << getpid() << std::endl;
            std::cout << "shm shmid: " << To_Hex(ds.shm_perm.__key) << std::endl;
        }
    }

    void *Getptr()
    {
        return _addptr;
    }

    ~Init_shm()
    {
        Datach_shm(_addptr);
        if (_type == SERVER)
            Delete_shm(_shmid);
    }

private:
    int _type; // 进程类型
    key_t _key;
    int _shmid;
    void *_addptr; // 关联的共享内存在进程空间中的地址
};

client.cc:

#include "common.hpp"
#include <unistd.h>
int main()
{
    Init_shm shm(CLIENT);
    shm.Get_shm_data();
    void *ptr = shm.Getptr();
    char c = 'a';
    std::cout << "Start writing" << std::endl;
    while (c != 'z' + 1) // 写入数据
    {
        ((char *)ptr)[c - 'a'] = c;
        ((char *)ptr)[c - 'a' + 1] = '\0';
        ++c;
        sleep(1);
    }
    std::cout << "Write End" << std::endl;
    return 0;
}

server.cc:

#include "common.hpp"

int main()
{
    Init_shm shm(SERVER);
    shm.Get_shm_data();
    void *ptr = shm.Getptr();
    int n = 30;
    while (n--) // 读取数据
    {
        std::cout << "client message:" << (char *)ptr << std::endl;
        sleep(1);
    }
    return 0;
}

运行效果:

四、共享内存的特性

4.1 共享内存的优点

我们可以看到上面的实例代码,在数据的写入和读取的过程中并没有向管道通信一样调用write、read等系统接口,而是直接向对应的地址空间写入和读取

这样子使共享内存间的进程间的数据不用传送,而是直接访问内存,绕过了Linux的内核,加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的父子关系

4.2 共享内存的缺点

下面我们单单运行serve进程:

可以看到即使共享内存中没有任何数据,但是该进程还是无脑的进行读取,但是在上期我们介绍的管道通信中,当管道中没有数据时,read函数会阻塞

所以共享内存没有任何保护机制(同步互斥),这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作

五、实例代码改进

现在我们将上面的代码改进一下,让client进程先向共享内存中写入数据,写完了再通知server进程来读取:

对于该功能的实现我们要联系到管道通信,创建一个管道来告知server进程是否完成了写入任务(对于管道不熟悉的同学请看到这里:【Linux】进程间通信——管道):

common.hpp

#pragma once

#ifndef __COMM_HPP__
#define __COMM_HPP__

#include <iostream>
#include <cstring>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#endif

#define PATHNAME "."
#define PROJID 0x1111

const int gsize = 4069; // 共享内存大小

std::string To_Hex(int x) // 将十进制转为十六进制
{
    char buffer[64];
    snprintf(buffer, sizeof buffer, "0x%x", x);
    return buffer;
}

const key_t GetKey() // 获取key值
{
    key_t k = ftok(PATHNAME, PROJID);
    if (k == -1)
    {
        std::cerr << "error: " << errno << ": " << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}

static int getshm(key_t key, int size, int flag) // 获取共享内存
{
    int k = shmget(key, size, flag);
    if (k == -1)
    {
        std::cerr << "error: " << errno << ": " << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}

int Create_shm(key_t key, int size) // 创建共享内存段
{
    umask(0);
    return getshm(key, size, IPC_CREAT | IPC_EXCL | 0666); //(IPC_CREAT | IPC_EXCL)以保证创建的共享内存段是最新的,|0666将创建的共享空间的权限让创建者有读写权
}

int Obtain_shm(key_t key, int size) // 获取srever进程创建的共享内存段的标识码
{
    return getshm(key, size, IPC_CREAT);
}

int Delete_shm(int shmid) // 删除共享内存
{
    return shmctl(shmid, IPC_RMID, nullptr);
}

void *Attach_shm(int shmid) // 让进程关联上共享内存
{
    return shmat(shmid, nullptr, 0);
}

void Datach_shm(void *addptr) // 解除进程关联的共享内存
{
    if (shmdt(addptr) == -1)
    {
        std::cerr << "error: " << errno << ": " << strerror(errno) << std::endl;
        exit(1);
    }
}

#define SERVER 0 // 服务端进程
#define CLIENT 1 // 客户端进程

class Init_shm
{
public:
    Init_shm(int type)
        : _type(type)
    {
        _key = GetKey();
        if (type == SERVER)
            _shmid = Create_shm(_key, gsize);
        else
            _shmid = Obtain_shm(_key, gsize);
        _addptr = Attach_shm(_shmid);
    }

    void Get_shm_data()
    {
        struct shmid_ds ds;
        int ret = shmctl(_shmid, IPC_STAT, &ds);
        if (ret == -1)
            std::cerr << "error: " << errno << ": " << strerror(errno) << std::endl;
        else
        {
            std::cout << "create shm process pid: " << ds.shm_cpid << " ,proccess pid:" << getpid() << std::endl;
            std::cout << "shm shmid: " << To_Hex(ds.shm_perm.__key) << std::endl;
        }
    }

    void *Getptr()
    {
        return _addptr;
    }

    ~Init_shm()
    {
        Datach_shm(_addptr);
        if (_type == SERVER)
            Delete_shm(_shmid);
    }

private:
    int _type; // 进程类型
    key_t _key;
    int _shmid;
    void *_addptr; // 关联的共享内存在进程空间中的地址
};

#define NUM 1024

std::string file_name = "./serve_fifo";

class Init_fifo
{
public:
    Init_fifo()
    {
        umask(0);
        int ret;

            ret = mkfifo(file_name.c_str(), 0666); // 创建管道文件
            if (ret == -1)
            {
                std::cout << "mkfifo error ,errno:" << strerror(errno) << std::endl;
                exit(1);
            }
            else
                std::cout << "created fifo successfully" << std::endl;
    }

    ~Init_fifo()
    {
        unlink(file_name.c_str()); // 删除管道文件
    }
};

client.cc:

#include "common.hpp"

int main()
{
    Init_shm shm(CLIENT);
    shm.Get_shm_data();
    void *ptr = shm.Getptr();
    char c = 'a';
    // 开始写入数据
    std::cout << "Start writing" << std::endl;
    while (c != 'z' + 1)
    {
        ((char *)ptr)[c - 'a'] = c;
        ((char *)ptr)[c - 'a' + 1] = '\0';
        ++c;
        sleep(1);
    }
    std::cout << "Write End" << std::endl;
    // 写入完毕
    int wfd;
    wfd = open(file_name.c_str(), O_WRONLY);
    if (wfd == -1)
    {
        std::cout << "open error ,errno:" << strerror(errno) << std::endl;
        exit(1);
    }
    char buffer[NUM];
    buffer[0] = '1';
    ssize_t n = write(wfd, buffer, strlen(buffer)); // 向管道文件中传入信号
    if (n < 0)
    {
        std::cout << "write error ,errno:" << strerror(errno) << std::endl;
        close(wfd);
        exit(1);
    }
    close(wfd);
    return 0;
}

 server.cc:

#include "common.hpp"

int main()
{
    Init_shm shm(SERVER);
    shm.Get_shm_data();
    Init_fifo client;
    int wfd;
    wfd = open(file_name.c_str(), O_RDONLY);
    if (wfd == -1)
    {
        std::cout << "open error ,errno:" << strerror(errno) << std::endl;
        exit(1);
    }
    else
        std::cout << "open success" << std::endl;
    char buffer[NUM];
    ssize_t n = read(wfd, buffer, sizeof(buffer) - 1); // 等待client进程的信号
    if (n > 0)
    {
        buffer[n] = '\0';
        if (buffer[0] == '1')
        {
            std::cout << "client write end" << std::endl;
            void *ptr = shm.Getptr();
            std::cout << "client message:" << (char *)ptr << std::endl;
            sleep(1);
        }
    }
    else if (n == 0)
    {
        std::cout << "client quit" << std::endl;
        exit(1);
    }
    else
    {
        std::cout << "read error ,errno:" << strerror(errno) << std::endl;
        close(wfd);
        exit(1);
    }
    close(wfd);
    return 0;
}

运行效果:

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

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

相关文章

YMK_周报2

周报 读论文 投机采样 为什么大语言模型&#xff08;LLM&#xff09;的推理过程文本生成这么慢&#xff1f; 因为运行大型模型的前向传递很慢&#xff0c;你可能需要依次执行数百次迭代。那么为什么前向传递速度慢&#xff1f;前向传递通常以矩阵乘法为主。内存带宽是此操作的…

【数据结构】八大排序算法(内含思维导图和画图分析)

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JAVA数据结构》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力…

跳表:为什么Redis一定要用跳表来实现有序集合

文章来源于极客时间前google工程师−王争专栏。 二分查找底层依赖的是数组随机访问的特性&#xff0c;所以只能用数组来实现。如果数据存储在链表中&#xff0c;就真的没法使用二分查找算法了吗&#xff1f; 我们可以对链表稍加改造&#xff0c;就可以支持类似“二分”的查找算…

LVGL_文件系统FS

LVGL_文件系统FS 前言&#xff1a; LVG 内置支持以下文件系统&#xff1a; 1、FATFS 2、STDIO (Linux 和 Windows 都可以使用的 C 标准函数接口&#xff0c;比如&#xff1a;fopen, fread…) 3、POSIX (Linux 和 Windows 都可以使用的 POSIX 函数接口&#xff0c;比如&#xff…

vue3 element-plus 组件table表格 勾选框回显(初始化默认回显)完整静态代码

<template><el-table ref"multipleTableRef" :data"tableData" style"width: 100%"><el-table-column type"selection" width"55" /><el-table-column label"时间" width"120">…

Go学习第三章——运算符与进制

Go学习第三章——运算符与进制 1 算术运算符2 关系运算符3 逻辑运算符4 赋值运算符5 其他运算符5.1 位运算符5.2 跟指针有关的运算符 6 运算符的优先级7 获取用户终端输入8 进制转换8.1 进制基本使用8.2 进制之间的转换8.3 原码 反码 补码8.4 位运算符详解 运算符是—种特殊的符…

KubeSphere一键安装部署K8S集群(单master节点)-亲测过

1. 基础环境优化 hostnamectl set-hostname master1 && bash hostnamectl set-hostname node1 && bash hostnamectl set-hostname node2 && bashcat >> /etc/hosts << EOF 192.168.0.34 master1 192.168.0.45 node1 192.168.0.209…

python查询数据库发送邮件,附件csv格式,xlsx格式

# 设置liunx系统运行python代码的解释器 #!/usr/bin/python3# python声明文件的编码格式为UTF-8 # python2默认以ASCII编码来读取文件&#xff0c;如果不声明编码格式&#xff0c;它可能会无法正确地解析非ASCII字符&#xff08;比如中文字符&#xff09;。 # python3开始默认支…

【ACO-KELM预测】基于蚁群算法优化核极限学习机回归预测研究(matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

VS Code C# 开发工具包正式发布

前言 微软于本月正式发布Visual Studio Code C#开发工具包&#xff0c;此前该开发套件已经以预览版的形式在6月份问世。经过4个月的测试和调整&#xff0c;微软修复了350多个问题&#xff0c;其中大部分是用户反馈导致的问题。此外&#xff0c;微软还对产品进行了300多项有针对…

【MicroSoft Edge】格式化的显示JSON格式的数据

当我们没有进行任何操作的时候&#xff0c;默认浏览器给我们展示的JSON的数据是这样的&#xff1a; 看着十分不便。 解决方案&#xff1a; 首先点击 MicroSoft Edge 浏览器右上角的三点&#xff0c;如何选择扩展 点击 获取Microsoft Edge 扩展 搜索 JSONView&#xff0c;第一…

智慧公厕系列产品:为您提供更便捷、更卫生的厕所体验

智慧公厕系列产品致力于改善公共厕所的管理和使用体验&#xff0c;通过引入先进的科技和智能设备&#xff0c;提升厕所的安全、卫生、舒适性。这些产品涵盖了从厕位监测到环境调控&#xff0c;从安全防范到能耗监测的各个方面&#xff0c;为用户提供了一个更加方便、舒适、卫生…

【每日一题】做菜顺序

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;贪心排序 写在最后 Tag 【贪心排序】【数组】【2023-10-22】 题目来源 1402. 做菜顺序 题目解读 每一道菜都有一个满足程度&#xff08;是一个整数&#xff09;&#xff0c;制作完成每道菜的时间为 1&#xff0c;每一…

Xray联动RAD实现自动扫描教程

Rad下载地址&#xff1a;https://github.com/chaitin/rad xray下载地址&#xff1a;https://github.com/chaitin/xray Xray启动监听&#xff1a; xray_windows_amd64.exe webscan --listen 127.0.0.1:7777 --html-output xray-xxx.html RAD启动爬虫抓包&#xff1a; rad_win…

反射、枚举及lambda表达式

文章目录 一、反射1.1 定义和用途1.2 反射基本信息1.3 反射相关的类&#xff08;重要&#xff09;1.4 Class类&#xff08;反射机制的起源&#xff09;1.5 反射优缺点1.6 总结 二、枚举2.1 定义2.2 使用2.3 枚举优缺点2.4 枚举和反射2.5 总结 三、lambda表达式3.1 背景3.2 基本…

家庭WIFI路由器、无线网卡购买指南

一、参考资料 【Wi-Fi】802.11/802.11b/802.11g/802.11n/802.11a/802.11ac/802.11ax/802.11be 从带宽到路由&#xff0c;从有线到无线&#xff0c;从需求到选购&#xff0c;从布网到实测&#xff0c;全部说尽。基础篇&#xff08;1&#xff09; 【一文详解】802.11a/b/g/n/ac/…

【28】c++设计模式——>观察者模式(1)

观察者模式概念 C观察者模式&#xff08;Observer Pattern&#xff09;是一种设计模式&#xff0c;它用于在对象之间建立一种一对多的依赖关系。在该模式中&#xff0c;当一个对象&#xff08;称为主题&#xff09;发生变化时&#xff0c;所有依赖于它的对象&#xff08;称为观…

跳跃游戏Ⅱ-----题解报告

题目&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 与Ⅰ不同的是&#xff0c;这次要求找出最小的跳跃次数。思路也很简单&#xff0c;在每一次跳跃之后都更新最远的跳跃距离。 举个列子&#xff1a; 输入&#xff1a;2,3,1,1,4 第一次…

【SSA-BP预测】基于麻雀算法优化BP神经网络回归预测研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Linux 中监控磁盘分区使用情况的 10 个工具

在本文[1]中&#xff0c;我们将回顾一些可用于检查 Linux 中磁盘分区的 Linux 命令行实用程序。 监控存储设备的空间使用情况是系统管理员最重要的任务之一&#xff0c;它可以确保存储设备上有足够的可用空间&#xff0c;以维持 Linux 系统的高效运行。 1. fdisk fdisk 是一个强…