【Linux从入门到精通】通信 | 共享内存(System V)

news2024/10/7 11:37:10

 

  本篇文章接着上篇文章通信 | 管道通信(匿名管道 & 命名管道)进行讲解。本篇文章的中点内容是共享内存

文章目录

 一、初识与创建共享内存

1、1 什么是共享内存

1、2 共享内存函数

1、2、1 创建共享内存 shmget

1、2、2 ftok 生成 key

1、2、3 获取共享内存 shmget

1、3 demo 代码

二、对共享内存进行相关操作 

2、1 查看/删除 共享内存资源

2、2 共享内存挂接和访问

2、2、1 共享内存的挂接 shmat()

2、2、2 共享内存的访问

2、3 删除共享内存 shmctl 

三、完整共享内存通信 demo 代码

3、1 Log.hpp 日志

3、2 comm.hpp

3、3 shmClient.cpp

3、4 shmServer.cpp


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:Linux从入门到精通  👀

💥 标题:共享内存 💥

 ❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️

 一、初识与创建共享内存

1、1 什么是共享内存

  我们在之前学管道通信时,是怎么实现通信的呢?匿名管道通信的方式是子进程继承父进程的内核数据结构,使得父子进程能够看到同一块空间命名管道信是让不同进程打开同一份文件。我们发现通信的前提就是让不同的进程看到同一份“资源”。当然,共享内存也不例外。

   每个进程都有自己独立的地址空间,所以它们彼此之间不能直接访问对方的内存。而共享内存则提供了一种特殊的内存区域,允许多个进程可以同时访问和操作同一块内存。具体如下图:

  这里再次解释一下上图。我们创建共享内存的过程:一个进程在内核空间申请一个共享内存对象,让后通过页表建立与物理内存的映射。让后另一个进程通过特殊的方法和算法来找到该共享内存并且与其建立映射。下面我们会对上述过程进行详细解释。 

1、2 共享内存函数

1、2、1 创建共享内存 shmget

  shmget函数用于创建一个新的共享内存段或者获取现有的共享内存段的标识符

函数原型为:

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

参数说明:

  • key:用于标识共享内存段的键值。可以使用ftok函数生成
  • size:指定共享内存段的大小,以字节为单位。
  • shmflg:用于指定共享内存段的访问权限和标志位,可以使用IPC_CREAT、IPC_EXCL等宏进行设置。

返回值:

  • 如果成功,返回共享内存段的标识符(即共享内存ID)。
  • 如果失败,返回-1,并设置errno。

  更加详细的如下图:

  我们在这里具体解释一下第三个参数 shmflg。这个参数是可以有多个选择的。底层是利用了位图的思想。主要是IPC_CREAT和IPC_EXCL两个选项。

  IPC_CREAT和IPC_EXCL是在shmget函数中使用的标志位,用于指定共享内存段的访问权限和标志。它们在shmget函数的第三个参数shmflg中使用。

  • IPC_CREAT:该标志用于创建一个新的共享内存段。如果指定的key对应的共享内存段不存在,则创建一个新的共享内存段。如果共享内存段已经存在,则返回该共享内存段的标识符(即共享内存ID)。

  • IPC_EXCL:该标志与IPC_CREAT一起使用,在创建共享内存段时起作用。如果指定的key对应的共享内存段已经存在,则shmget函数会失败,并返回-1,并且置errno为EEXIST(资源已存在)。

   我们接下来再看一下 shmget 的具体使用例子。

int shmid = shmget(key, size, IPC_CREAT | permission_flags);

  上述代码中,IPC_CREAT标志位用于创建共享内存段。如果指定的key对应的共享内存段已经存在,那么shmget函数会返回该共享内存段的标识符;如果共享内存段不存在,则会创建一个新的共享内存段,并返回新创建的共享内存段的标识符。

1、2、2 ftok 生成 key

  在函数shmget中,key值是用于标识或检索共享内存段的关键值。它在创建或访问共享内存时起到重要作用。具体来说,key值用于以下两个目的:

  • 当多个进程需要访问同一个共享内存段时,它们可以使用相同的key值来标识这个共享内存段。
  • 如果一个共享内存段已经存在,并且其他进程想要访问它,那么只需要提供相同的key值即可找到该共享内存段。

  那在使用 shmget 函数之前,我们应该使用 ftok 函数生成key值,来表示这个共享内存段。由于是标示共享空间,所以应该确定唯一性。至于key的值是多少并不关键。那我们看一下ftok 函数的用法。具体如下:

参数说明:

  • pathname:一个包含一个现有文件的路径名,用于生成k值。最好是有权访问这个文件。
  • proj_id:不同的proj_id可以被用作区分不同类型的通信方式或不同的ipc资源,来生成不同的k值。其实就是一个任意整型值。

  我们再看一下其具体的例子:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>

int main()
{
    key_t key;
    char *path = "./example.txt";
    int proj_id = 0x66;

    // 使用ftok函数生成k值
    key = ftok(path, proj_id);

    printf("Generated key: %d\n", key);

    return 0;
}

  另一个进程也可以用相同的方法生成相同的key值。ftok函数根据给定的路径名和proj_id生成k值。当路径名和proj_id相同时,生成的k值也相同。这是因为ftok函数内部使用了哈希运算,将路径名和proj_id转化为一个唯一的整数。尽管可能存在哈希冲突(即不同的路径名和proj_id生成相同的k值),但概率非常低。通常情况下,不同的进程可以使用相同的路径名和proj_id生成相同的k值是非常罕见的。即使出现相同的k值,由于进程间通信中还有其他参数的限制(如消息队列标识、共享内存标识等),不同进程之间的IPC通信仍然可以正常进行。  

  生成 key 值后,我们就可以用key值创建共享内存,或者来获取共享内存。下面我们看一下获取使用key值来获取共享内存的方法。

1、2、3 获取共享内存 shmget

  shmget 函数还可用来获取共享内存。当生成的key值已经有对应的共享内存时,shmget 函数就会返回这段共享内存的标识码。我么不只需要将第三个参数修改为0,就是来获取对应key值的共享内存。

1、3 demo 代码

  我们接下来写一段代码测试和总结一下我们上面所学到的函数。下面为实例:

// Log.hpp
#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;
}


// comm.hpp
#pragma once

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

using namespace std; 

#define PATH_NAME "/home/gtm"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //共享内存的大小,最好是页(PAGE: 4096)的整数倍

#define FIFO_NAME "./fifo"


// shmClient.cc
#include"comm.hpp"

int main()
{
    Log("child pid is : ", Debug) << getpid() << endl;
    key_t k = ftok(PATH_NAME, PROJ_ID);
    if (k < 0)
    {
        Log("create key failed", Error) << " client key : " << k << endl;
        exit(1);
    }
    Log("create key done", Debug) << " client key : " << k << endl;

    // 获取共享内存
    int shmid = shmget(k, SHM_SIZE, 0);
    if(shmid < 0)
    {
        Log("create shm failed", Error) << " client key : " << k << endl;
        exit(2);
    }
    Log("create shm success", Error) << " client shmid : " << shmid << endl;


    return 0;
}


// shmServer.cc
#include "comm.hpp"

string TransToHex(key_t k)
{
    char buffer[32];
    snprintf(buffer, sizeof buffer, "0x%x", k);
    return buffer;
}

int main()
{
    // 我们之前为了通信,所做的所有的工作,属于什么工作呢:让不同的进程看到了同一份资源(内存)
    // 1. 创建公共的Key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    assert(k != -1);

    Log("create key done", Debug) << " server key : " << TransToHex(k) << endl;

    // 2. 创建共享内存 -- 建议要创建一个全新的共享内存 -- 通信的发起者
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); //
    if (shmid == -1)
    {
        perror("shmget");
        exit(1);
    }
    Log("create shm done", Debug) << " shmid : " << shmid << endl;
    
    return 0;
}

  shmServer.cc文件中的代码是服务端代码。首先调用ftok函数生成一个唯一的键值,并将其转换为十六进制字符串表示。然后使用shmget函数创建一个共享内存段,创建时指定了IPC_CREAT标志,用于新建共享内存段。如果创建成功,返回一个共享内存标识符shmid。

  shmClient.cc文件中的代码是客户端代码。首先也是调用ftok函数生成一个唯一的键值。然后通过shmget函数打开已存在的共享内存段,打开时不需要指定IPC_CREAT标志,而是提供即将打开的共享内存段的键值和大小。如果打开成功,返回一个共享内存标识符shmid。

  Log.hpp文件定义了一个宏和一个Log函数。宏定义了四个日志级别,分别对应Debug、Notice、Warning和Error四个字符串。Log函数负责输出日志信息,接受一个字符串信息和一个日志级别参数。Log函数将时间戳、日志级别和消息内容输出到标准输出流中。

  上述代码就是完成了创建共享内存的功能,并且在其中打印了一些日志信息。我们不妨来看一下运行结果。具体如下图:

  我们发现对应的shmid是相同的,说明Server和Client确实获得了相同的共享内存块。他们所生成的key值相同吗?其实是相同的,如下图:

二、对共享内存进行相关操作 

2、1 查看/删除 共享内存资源

  当我们再次运行时,就会发生错误。具体如下图:

  为什么呢?原因是我们刚刚创建的共享内存依然存在。当进程结束时,共享内存并不会自动释放。为什么呢?我们可以认为共享内存是属于操作系统。所以共享内存的生命周期随操作系统!这时我们可以手动关闭共享内存。在关闭前首先要查看共享内存,指令:ipcs -m。具体如下图:

  当然,查到共享内存后,可以用指令进行删除。那么问题来了,使用key值删除呢?还是用shmid 进行删除呢?我们这里需要注意:共享内存中的shmid和key值是两个不同的概念

  1. shmid(Shared Memory ID)是共享内存的标识符,由操作系统分配,并作为一个非负整数对共享内存进行引用。在使用共享内存时,我们需要通过shmid来进行操作,如创建、附加、访问和删除等。shmid可以看作是内核用于标识某个特定共享内存段的一个唯一值

  2. key值是用户定义的一个标识符,通常是一个整数值。在创建共享内存时,我们可以使用ftok函数生成一个key值,以便其他进程可以通过这个key值来获取相同的共享内存区域。key值是用于标识共享内存的用户级别的标识符,不同于shmid,其值不受内核控制

  所以删除共享内存,我们使用的shmid。具体指令:ipcrm -m shmid。如下图:

  当我们删除共享内存后,再次进行查找发现就没有了,且程序能够正常运行。

2、2 共享内存挂接和访问

2、2、1 共享内存的挂接 shmat()

  在调用shmget()函数时,内核会在内部维护一个共享内存表格,其中包含了共享内存的相关信息,包括共享内存的大小、权限等。当调用成功后,将返回一个唯一的共享内存标识符,该标识符可以用于后续的共享内存操作。

  那么正常来说,我们访问共享内存是需要通过系统调用的。但是我们这里可以将内核级别的共享内存挂接到进程的地址空间。然后用户就可以直接进行访问

  进程可以使用系统提供的函数(如shmat())将自己的地址空间映射到共享内存。也可以理解为shmat()函数将共享内存附加到进程的虚拟地址空间中,使得进程可以访问该共享内存所指向的物理内存区域。具体用法如下:

  参数说明:

  • shm_id:共享内存标识符,通过调用 shmget 获取。
  • shm_addr:内存段的地址,通常传入 NULL,表示由系统自动选择一个适合的地址。
  • shmflg:控制共享内存段的附加方式和权限,可以使用 IPC_CREAT 标志创建新的共享内存段。通常传入0。

  返回值:

  • 如果成功,返回指向共享内存段第一个字节的指针;
  • 如果失败,返回 void * 类型的错误值 -1

  其实我们看完其使用方法后,有没有发现与 malloc 很相似。malloc 申请空间成功后,会返回所申请空间的起始地址。否则就会返回NULL。shmat 与其确实有些相似。我们可结合如下例子一起理解:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

int main() {
    int shm_id;
    key_t key;
    int *shared_memory;

    // 获取共享内存标识符
    key = ftok("。/file", 0x66);
    shm_id = shmget(key, sizeof(int), IPC_CREAT | 0666);
    
    // 将共享内存段附加到进程的地址空间中
    shared_memory = shmat(shm_id, NULL, 0);
    
    // 访问共享内存
    printf("共享内存中的值为:%d\n", *shared_memory);
    
    // 分离共享内存段
    shmdt(shared_memory);
    
    return 0;
}

  我们也看到了最后是有一个去关联的 shmdt 函数。参数就是我们所获取的共享内存的起始地址,这里就不再过多解释此函数

  这里有会有一个问题:将内核级别的共享内存挂接到进程地址空间的哪里了呢?我们看如下图:

   我们之前学进程地址空间时,知道堆和栈的中间有大量的镂空,而这段位置就是内核级别的共享内存所挂接到的位置!

2、2、2 共享内存的访问

  到这里,我们已经学习了共享内存的大部分内容。只差对共享内存的访问了。当我们对共享内存进行挂接后, 就可以得到共享内存挂接后的起始地址。我们用户可以对其进行直接访问(写入/读取)。我们给出如下伪代码:

    // shmServer.cpp
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    Log("attach shm done", Debug) << " shmid : " << shmid << endl;

    for(;;)
    {
        printf("%s\n", shmaddr);
        if(strcmp(shmaddr, "quit") == 0) break;
        sleep(1);
    }

    // shmClient.cpp  
    // 挂接并获得共享内存起始地址
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if(shmaddr == nullptr)
    {
        Log("attach shm failed", Error) << " client key : " << k << endl;
        exit(3);
    }

    char a = 'a';
    for(; a <= 'c'; a++)
    {
        // 我们是每一次都向shmaddr[共享内存的起始地址]写入
        snprintf(shmaddr, SHM_SIZE - 1,\
            "hello server, 我是其他进程,我的pid: %d, inc: %c\n",\
            getpid(), a);
        sleep(3);
    }

  对上述代码是一个使用共享内存进行进程间通信的伪代码。下面对代码进行详解:

  1. 首先,在服务端(shmServer.cpp)中,通过shmat函数将共享内存连接到当前进程的地址空间。shmat函数的第一个参数是共享内存的标识符shmid,第二个参数为NULL表示让系统自动选择合适的地址分配给共享内存,第三个参数为0表示以默认权限进行操作。连接完成后,返回共享内存的起始地址,并赋值给shmaddr指针。

  2. 服务器端的for循环中,通过printf函数将shmaddr指向的共享内存内容输出到标准输出(读取)。然后通过strcmp函数判断共享内存中的内容是否为"quit",如果是,则跳出循环,结束程序。否则,通过sleep函数暂停1秒钟。

  3. 在客户端(shmClient.cpp)中,同样通过shmat函数连接到共享内存,并将共享内存的起始地址赋给shmaddr指针。若连接失败(shmaddr为nullptr),则输出错误信息并退出程序。

  4. 客户端的for循环中,使用snprintf函数将格式化的字符串写入shmaddr指向的共享内存中(写入)。该字符串包含了客户端进程的PID(进程标识符)和一个递增的字符,以展示多次写入的内容。然后通过sleep函数暂停3秒钟。

  运行结果如下:

  我们通过运行结果发现:在客户端没有写入的情况下,服务端进行读取时也会读到内容。读到的是空字符串(共享内存默认会初始化为0)。我们发现共享内存的读写并没有访问控制。我们知道命名管道通信是由访问控制的。但是当一个进程写入时,另一个就能够马上看到写入的内容。所以共享内存是所有进程间通信(IPC),速度最快的!不需要过多的拷贝!!(不需要将数据给操作系统)。如果我想一定程度的访问控制呢?可以在共享内存读写的过程中加入命名管道来控制。

2、3 删除共享内存 shmctl 

  上面我们了解了可以使用Linux指令对共享内存进行删除,我们也可以使用系统调用 shmctl()函数 对其进行删除。具体使用如下:

 参数说明:

  • shmid:共享内存标识符,通过shmget函数获取得到。
  • cmd:表示对共享内存进行的操作类型,可以选择的参数有:
    • IPC_STAT:获取共享内存的状态信息,将共享内存的属性保存在buf所指向的结构体中。
    • IPC_SET:设置共享内存的属性,使用buf所指向的结构体中的值进行设置。
    • IPC_RMID:删除共享内存。
  • buf:指向一个struct shmid_ds结构体的指针,用于存储共享内存的属性信息。通常使用nullptr。

  shmctl函数可以用于对共享内存段进行控制操作。它能够实现共享内存的创建、删除、以及获取和修改共享内存的属性。但是我们该函数最常用删除共享内存。使用IPC_RMID操作可以删除指定的共享内存段,并释放系统资源。这个操作会立即删除共享内存段,以及与它关联的任何进程中的键和id

三、完整共享内存通信 demo 代码

3、1 Log.hpp 日志

  

#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;
}

3、2 comm.hpp

#pragma once

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

using namespace std; //不推荐

#define PATH_NAME "/home/whb"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //共享内存的大小,最好是页(PAGE: 4096)的整数倍

#define FIFO_NAME "./fifo"

class Init
{
public:
    Init()
    {
        umask(0);
        int n = mkfifo(FIFO_NAME, 0666);
        assert(n == 0);
        (void)n;
        Log("create fifo success",Notice) << "\n";
    }
    ~Init()
    {
        unlink(FIFO_NAME);
        Log("remove fifo success",Notice) << "\n";
    }
};

#define READ O_RDONLY
#define WRITE O_WRONLY

int OpenFIFO(std::string pathname, int flags)
{
    int fd = open(pathname.c_str(), flags);
    assert(fd >= 0);
    return fd;
}

void Wait(int fd)
{
    Log("等待中....", Notice) << "\n";
    uint32_t temp = 0;
    ssize_t s = read(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
}

void Signal(int fd)
{
    uint32_t temp = 1;
    ssize_t s = write(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
    Log("唤醒中....", Notice) << "\n";
}

void CloseFifo(int fd)
{
    close(fd);
}

3、3 shmClient.cpp

#include "comm.hpp"

int main()
{
    Log("child pid is : ", Debug) << getpid() << endl;
    key_t k = ftok(PATH_NAME, PROJ_ID);
    if (k < 0)
    {
        Log("create key failed", Error) << " client key : " << k << endl;
        exit(1);
    }
    Log("create key done", Debug) << " client key : " << k << endl;

    // 获取共享内存
    int shmid = shmget(k, SHM_SIZE, 0);
    if(shmid < 0)
    {
        Log("create shm failed", Error) << " client key : " << k << endl;
        exit(2);
    }
    Log("create shm success", Error) << " client key : " << k << endl;

    // sleep(10);

    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if(shmaddr == nullptr)
    {
        Log("attach shm failed", Error) << " client key : " << k << endl;
        exit(3);
    }
    Log("attach shm success", Error) << " client key : " << k << endl;


    int fd = OpenFIFO(FIFO_NAME, WRITE);

    // client将共享内存看做一个char 类型的buffer
    while(true)
    {
        ssize_t s = read(0, shmaddr, SHM_SIZE-1);
        if(s > 0)
        {
            shmaddr[s-1] = 0;
            Signal(fd);
            if(strcmp(shmaddr,"quit") == 0) break;
        }
    }

    CloseFifo(fd);

    // 去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("detach shm success", Error) << " client key : " << k << endl;


    return 0;
}

3、4 shmServer.cpp

#include "comm.hpp"

Init init; 

string TransToHex(key_t k)
{
    char buffer[32];
    snprintf(buffer, sizeof buffer, "0x%x", k);
    return buffer;
}

int main()
{
    // 1. 创建公共的Key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    assert(k != -1);

    Log("create key done", Debug) << " server key : " << TransToHex(k) << endl;

    // 2. 创建共享内存 -- 建议要创建一个全新的共享内存 -- 通信的发起者
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); //
    if (shmid == -1)
    {
        perror("shmget");
        exit(1);
    }
    Log("create shm done", Debug) << " shmid : " << shmid << endl;


    // 3. 将指定的共享内存,挂接到自己的地址空间
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    Log("attach shm done", Debug) << " shmid : " << shmid << endl;

    
    int fd = OpenFIFO(FIFO_NAME, READ);
    for(;;)
    {
        Wait(fd);

        // 临界区
        printf("%s\n", shmaddr);
        if(strcmp(shmaddr, "quit") == 0) break;
        // sleep(1);
    }
    // 4. 将指定的共享内存,从自己的地址空间中去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    (void)n;
    Log("detach shm done", Debug) << " shmid : " << shmid << endl;


    // 5. 删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存
    n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
    Log("delete shm done", Debug) << " shmid : " << shmid << endl;

    CloseFifo(fd);
    return 0;
}

  上述共享内存代码是结合了命名管道通信进行了访问控制

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

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

相关文章

nvm 在 Windows 上的使用

NVM&#xff08;Node Version Manager&#xff09;是一个用于管理和切换多个 Node.js 版本的工具。它允许你在同一台机器上同时安装和使用不同版本的 Node.js&#xff0c;而无需手动安装和卸载。 之前都是只安装一个版本的 node.js&#xff0c;该更新时更新&#xff0c;使得以前…

RK3568-GPIO控制

RK3568-GPIO控制 1. Sysfs接口 实现逻辑 芯片的GPIO由芯片的GPIO控制器来管理&#xff0c;GPIO控制器封装在芯片内部&#xff0c;控制器的驱动芯片厂家已经写好了。RK3568有五组GPIO控制器&#xff0c;每组管理32个引脚&#xff0c;对应/dev下的gpiochip0~4的设备节点。 Lin…

【Redis专题】RedisCluster集群运维与核心原理剖析

目录 课程内容一、Redis集群架构模型二、Redis集群架构搭建&#xff08;单机搭建&#xff09;2.1 在服务器下新建各个节点的配置存放目录2.2 修改配置&#xff08;以redis-8001.conf为例&#xff09; 三、Java代码实战四、Redis集群原理分析4.1 槽位定位算法4.2 跳转重定位4.3 …

【Linux】网络编程网络基础(C++)

目录 一、计算机网络背景 二、认识 "协议" 三、网络协议初识 【3.1】协议分层 【3.2】OSI七层模型 【3.3】TCP/IP五层(或四层)模型 四、网络传输基本流程 【4.1】网络传输流程图 【4.2】数据包封装和分用 五、网络中的地址管理 一、计算机网络背景 【独立…

用Python实现一个可定制风格的绘图系统

文章目录 调用绘图风格控件代码组织源代码base.pyaframe.pyalist.pyds.py Python绘图系统&#xff1a; &#x1f4c8;从0开始的3D绘图系统&#x1f4c9;一套3D坐标&#xff0c;多个函数&#x1f4ca;散点图、极坐标和子图自定义控件&#xff1a;&#x1f4c9;绘图风格&#x1…

【精华】AIGC专栏-Text/Img/Video/audio

&#xff08;一&#xff09;LLM专栏 大模型相关技术原理以及实战经验&#xff1a;liguodongiot/llm-action 1 ColossalAI &#xff08;1&#xff09;参考资料&#xff1a;700 亿参数 LLaMA2 训练加速 195%&#xff0c;基础大模型最佳实践再升级 &#xff08;2&#xff09;开…

Linux常用命令——convertquota命令

在线Linux命令查询工具 convertquota 把老的配额文件转换为新的格式 补充说明 convertquota命令用于将老的磁盘额数据文件&#xff08;“quota.user”和“quota.group”&#xff09;转换为新格式的文件&#xff08;“quota.user”和“quota.group”&#xff09;。 语法 c…

Day59|leetcode 503.下一个更大元素II、42. 接雨水

leetcode 503.下一个更大元素II 题目链接&#xff1a;503. 下一个更大元素 II - 力扣&#xff08;LeetCode&#xff09; 视频链接&#xff1a;单调栈&#xff0c;成环了可怎么办&#xff1f;LeetCode&#xff1a;503.下一个更大元素II_哔哩哔哩_bilibili 题目概述 给定一个循环…

接口使用的最佳时机

1. 引言 接口在系统设计中&#xff0c;以及代码重构优化中&#xff0c;是一个不可或缺的工具&#xff0c;能够帮助我们写出可扩展&#xff0c;可维护性更强的程序。 在本文&#xff0c;我们将介绍什么是接口&#xff0c;在此基础上&#xff0c;通过一个例子来介绍接口的优点。…

【2023高教社杯】A题 定日镜场的优化设计 问题分析及数学模型

【2023高教社杯】A题 定日镜场的优化设计 问题分析及数学模型 1 题目 构建以新能源为主体的新型电力系统&#xff0c;是我国实现“碳达峰”“碳中和”目标的一项重要措施。塔式太阳能光热发电是一种低碳环保的新型清洁能源技术[1]。 定日镜是塔式太阳能光热发电站&#xff08;…

微电网的概念

微电网分布式控制理论与方法  顾伟等 微电网的概念和作用 微电网是由多种分布式电源、储能、负载以及相关监控保护装置构成的能够实现自我控制和管理的自治型电力系统&#xff0c;既可以与电网并网进行&#xff0c;也可以以孤岛运行。 分布式发电是指将容量在兆瓦以内的可再…

Elsevier出版社 | 优质好刊合集

【SciencePub学术】 爱思唯尔(Elsevier)是一家全球专业从事科学与医学的信息分析公司作为出版公司&#xff0c;成立于1880年&#xff0c;其产品包括《柳叶刀》、《四面体》和《细胞》等学术期刊&#xff0c;ScienceDirect电子期刊集&#xff0c; “趋势”(Trends)系列和“新见…

uniapp项目运行Missing script: “dev“, To see a list of scripts, run:

webstorm 打开项目根目录不对&#xff0c;打开到了项目上一级。 另外一个原因是&#xff0c;当前项目是Hbuilder 可视化界面创建的&#xff0c;不能在terminal直接脚本指令启动。 可以webstorm 安装支持uniapp项目插件&#xff0c;然后创建一个运行器&#xff0c;运行h5。 安…

vue 验证码 图片点击

实现登陆验证 图片依次点击功能 demo &#xff0c;上图可以根据demo修改&#xff0c;直接拿用 <template><div><div class"big-box" id"BigBox" :style"background-image:url( imgCodeUrl )"><div class"click-box…

C#,《小白学程序》第十八课:随机数(Random)第五,方差及标准方差(标准差)的计算方法与代码

1 文本格式 /// <summary> /// 《小白学程序》第十八课&#xff1a;随机数&#xff08;Random&#xff09;第五&#xff0c;方差及标准方差&#xff08;标准差&#xff09;的计算方法与代码 /// 方差 SUM(&#xff08;Xi - X)^2 ) / n i0...n-1 X Average of X[i] ///…

APP备案流程详细解读

背景介绍 2023年8月4日&#xff0c;工信部发布《工业和信息化部关于开展移动互联网应用程序备案工作的通知》。 在中华人民共和国境内从事互联网信息服务的APP主办者&#xff0c;应当依照《中华人民共和国反电信网络诈骗法》《互联网信息服务管理办法》&#xff08;国务院令第…

SpotBugs代码检查:在整数上进行没有起任何实际作用的位操作(INT_VACUOUS_BIT_OPERATION)

https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html#int-vacuous-bit-mask-operation-on-integer-value-int-vacuous-bit-operation 在整数上进行无用的与、异或操作&#xff0c;实质上没有做任何有用的工作。 例如&#xff1a;v & 0xffffffff 再例如&…

如何处理异步编程中的回调地狱问题?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 解决回调地狱问题的方法⭐使用 Promise⭐使用 async/await⭐ 使用回调函数库⭐模块化⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端…

微软研究院团队获得首届AI药物研发算法大赛总冠军

编者按&#xff1a;AI 药物研发是人工智能未来应用的重要方向之一。自新冠病毒&#xff08;SARS-CoV-2&#xff09;首次爆发以来&#xff0c;新冠病毒的小分子药物研发备受关注&#xff0c;于近期举行的首届 AI 药物研发算法大赛便聚焦于此。在比赛中&#xff0c;来自微软研究院…

go语言基础操作---七

socket简单介绍—套接字编程 什么是Socket Socket&#xff0c;英文含义是【插座、插孔】&#xff0c;一般称之为套接字&#xff0c;用于描述IP地址和端口。可以实现不同程序间的数据通信。 Socket起源于Unix&#xff0c;而Unix基本哲学之一就是“一切皆文件”&#xff0c;都可…