linux之进程通信

目录

一、进程通信介绍

1.目的

2.发展 

3.进程通信是什么,怎么通信?

二、管道

1.介绍

2.匿名管道 

1.单向通信管道原理

 2.代码实现

3.管道特征

4.管道的四种情况

5.管道的应用场景 

使用管道实现一个简易版本的进程池

3.命名管道

        1.思考

        2.创建一个命名管道

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

        4.命名管道的打开规则 

4.日志 

日志等级:

日式时间相关函数 

日志代码实现

5.总结

三、system V共享内存

1.原理

2.代码书写

1.相关函数

1.shmget 

返回值:

key:

size:

shmfig:

2.shmat

3.shmdt

4.shmctl

2.代码

3.共享内存特性


一、进程通信介绍

1.目的

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

2.发展 

  • 管道       
  1. 匿名管道pipe
  2. 命名管道 

         简单经典的通信使用的一种方式

  • System V IPC 
  1. System V 消息队列
  2. System V 共享内存
  3. System V 信号量 

        单独设计了一套接口,与文件无关。 只能本地使用,本地通信,在网络阶段,有很多替代方案。

  • POSIX IPC 
  1. 消息队列
  2. 共享内存
  3. 信号量
  4. 互斥量
  5. 条件变量
  6. 读写锁 

       网络和多线程时使用 

3.进程通信是什么,怎么通信?

1.是什么?

        两个或多个进程实现数据层面的交互。

        因为进程独立性的存在,进程通信的成本较高 -> 进程通信是有成本的

2.怎么办?

  • 进程间通信的本质:必须让不同的进程看到同一份"资源"
  • "资源"?:特定形式的内存空间
  • 这个"资源"谁提供?一般是操作系统
  • 为什么不是我们两个进程中的一个呢?假设一个进程提供,这个资源属于谁?这个进程独有,破环进程独立性。来自第三方空间
  • 我们进程访问这个空间,进行通信,本质就是访问操作系统!进程代表的就是用户,"资源"从创建,使用,释放  --- 出自系统调用接口! --- 1.从底层设计,从接口设计,都要由操作系统独立设计;2.一般操作系统,会有一个独立的通信模块 -- 隶属于文件系统  -- IPC通信模块定制标准  -- 进程间通信是有标准的  -- 就是上述的system V(本机内部) 和 POSIX(网络通信)

二、管道

1.介绍

        基于文件级别的进程通信方式

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

2.匿名管道 

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

1.单向通信管道原理

  

 2.代码实现

创建管道函数 pipe

#include <iostream>
#include <string>

#include <cstdlib> //stdlib.h
#include <cstdio>
#include <cstring>

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

using namespace std;

#define N 2
#define NUM 1024

//child
void Writer(const int& wfd)
{
    string str = "hello, i am child!";
    pid_t self = getpid();
    int number = 0;
    char buffer[NUM];
    while(true)
    {
        //构建发送字符串
        buffer[0] = 0; //字符串清空,只是为了提醒阅读代码的人,把这个字符数组当作字符串了。
        snprintf(buffer, sizeof(buffer), "%s-%d-%d", str.c_str(), self, number++);

        //发送/写入给父进程
        write(wfd, buffer, strlen(buffer));
        sleep(1);
    }
}

//father
void Reader(const int& rfd)
{
    char buffer[NUM];
    while(true)
    {
        ssize_t n = read(rfd, buffer, sizeof(buffer));
        if(n > 0)
        {
            buffer[n] = 0; // 0  ==  '\0'
            cout << "father get some message[" << getpid() << "]:" << buffer << endl;
        }            
    }
}

int main()
{
    int pipefd[N] = {0}; //输出型参数
    int n = pipe(pipefd); //申请管道
    if(n < 0)
        return -1;
    // cout << "pipefd[0]:" << pipefd[0] << ",pipefd[1]" << pipefd[1] << endl;

    // father -> r ; child -> w;
    pid_t id = fork(); //创建子进程
    if(id < 0)
        return 2;
    if(id == 0)
    {
        //child
        close(pipefd[0]);
        
        //IPC code
        Writer(pipefd[1]);

        close(pipefd[1]);
        exit(0);
    }
    //father
    close(pipefd[1]);

    // IPC code
    Reader(pipefd[0]);

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

3.管道特征

  1. 具有血缘关系的进程会进行进程间通信
  2. 管道只能单向通信
  3. 父子进程是会进程协同的,同步和互斥的 --- 保护管道文件的数据安全
  4. 管道是面向字节流的。
  5. 管道是基于文件的,而文件的生命周期是跟随进程的

4.管道的四种情况

  1.  读写端正常,管道如果为空,读端就要阻塞
  2. 读写端正常,管道如果被写满,写端就要阻塞
  3. 读端正常读,写端关闭,读端就会读到0,表面读到了文件(pipe)结尾,不会被阻塞
  4. 读端关闭,写端正常写,操作系统就要杀掉正在写入的进程。如何杀掉--通过信号杀掉

5.管道的应用场景 

使用管道实现一个简易版本的进程池

原由:创建进程需要调用fork函数,而fork函数这个系统调用是有成本的!

"Task.hpp"

#pragma once

#include <iostream>
#include <vector>

using namespace std;

//函数指针
typedef void (*task_t)();

void task1()
{
    std::cout << "lol : 刷新野怪" << std::endl;
}

void task2()
{
    std::cout << "lol : 刷新蓝条" << std::endl;
}

void task3()
{
    std::cout << "lol : 刷新血量" << std::endl;
}

void task4()
{
    std::cout << "lol : 更新系统" << std::endl;
}

void LoadTasks(std::vector<task_t> *tasks)
{
    tasks->push_back(task1);
    tasks->push_back(task2);
    tasks->push_back(task3);
    tasks->push_back(task4);
}

"ProcessPool.cc"

#include "Task.hpp"

#include <string>
#include <vector>

#include <cstdlib>
#include <ctime>

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>

const int ProcessNum = 10;

std::vector<task_t> tasks;

//先描述 -- 管道
struct channel 
{
    int _cmdfd;                  //发送任务的文件描述符
    pid_t _slaverid;             //子进程的pid
    std::string _processname;    //子进程的名字,方便我们打印日志

    channel(int cmdfd, pid_t slaverid, const std::string &processname)
        : _cmdfd(cmdfd)
        , _slaverid(slaverid)
        , _processname(processname)
        {}
};


void slaver()
{
    while(true)
    {
        int cmdcode = 0;
        int n = read(0, &cmdcode, sizeof(int));
        if(n == sizeof(int))
        {
            std::cout << " child get a command: " << getpid() << " cmdcode: " << cmdcode << std::endl;
            if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();
        }
        if(!n)
            break;
    }
}

void InitProcessPool(std::vector<channel> *channels)
{
    std::vector<int> oldfd;
    // 1.初始化 --- bug
    for (size_t i = 0; i < ProcessNum; ++i)
    {
        int pipefd[2];   //临时空间
        int n = pipe(pipefd); //
        if(n != 0)
        {
            perror("pipe create file");
            return;
        }

        pid_t id = fork();
        if(id == 0)
        {
            std::cout << "child " << getpid() << " close history fd :";
            for (auto &e : oldfd)
            {
                std::cout << e << " ";
                close(e);
            }
            std::cout << std::endl;
            // child
            close(pipefd[1]);
            dup2(pipefd[0], 0);
            close(pipefd[0]);
            slaver();
            std::cout << "process " << getpid() << ":quit!" << std::endl;
            exit(0);
        }
        //father
        close(pipefd[0]);

        //开始添加channel字段
        std::string name = "process-" + std::to_string(i);
        channels->push_back(channel(pipefd[1], id, name));
        oldfd.push_back(pipefd[1]);
        sleep(1);
    }
}

void Debug(const std::vector<channel> &channels)
{
    // test
    for(const auto& e:channels)
    {
        std::cout << "pid: " << getpid() << " " << e._cmdfd << " " << e._slaverid << " " << e._processname << std::endl;
    }
}

void Menu()
{
    std::cout << "############################################" << std::endl;
    std::cout << "########1.刷新野怪  2.刷新蓝条 #############" << std::endl;
    std::cout << "########3.刷新血量  4.更新系统  0.退出######" << std::endl;
    std::cout << "############################################" << std::endl;
}

void ctrlSlaver(const std::vector<channel> &channels)
{
    srand(time(0));
    int which = 0;
    //int cnt = 0;
    while (true)
    {
        Menu();
        std::cout << "Please enter@:";
        int n;
        std::cin >> n;
        if(n <= 0 || n >= 5)
            break;
        // 1.选择任务
        // int cmdcode =  rand() % tasks.size();
        int cmdcode = n - 1;
        // 2.选择子进程
        // int processpos = rand() % channels.size(); //随机方法

        std::cout << "father say:"
                  << "cmdcode: " << cmdcode 
                  << " already sendto " << channels[which]._slaverid 
                  << " processname: " << channels[which]._processname 
                  << endl;
        // 3.发送任务
        write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));
        
        ++which;
        which %= channels.size(); //轮转法
        sleep(1);
    }
}

void QuitProcess(const std::vector<channel>& channels)
{
    for(const auto& e: channels)
    {
        close(e._cmdfd);
        waitpid(e._slaverid, nullptr, 0);
    }
    //version1
    // for (int i = channels.size() - 1; i >= 0; --i)
    // {
    //     close(channels[i]._cmdfd);
    //     waitpid(channels[i]._slaverid, nullptr, 0);
    // }
    //sleep(5);
    // 有bug
    // for (const auto &e : channels)
    //     close(e._cmdfd);

    // sleep(5);
    // for (const auto &e : channels)
    //     waitpid(e._slaverid, nullptr, 0);
    // sleep(5);
}

int main()
{
    //再组织
    //将对子进程结构的增删查改转化为对数据结构vector的增删查改
    std::vector<channel> channels;
    // 1.初始化
    LoadTasks(&tasks);
    InitProcessPool(&channels);

    //test
    Debug(channels);

    // 2.开始控制子进程
    ctrlSlaver(channels);

    // 3.清理收尾
    QuitProcess(channels);
    return 0;
}

 结果

注:

 这是我们原先创建子进程的代码,但是这份代码会造成一个问题,就是子进程会继承父进程对上一个子进程管道读端。

void InitProcessPool(std::vector<channel> *channels)
{
    // 1.初始化 --- bug
    for (size_t i = 0; i < ProcessNum; ++i)
    {
        int pipefd[2];   //临时空间
        int n = pipe(pipefd); //
        if(n != 0)
        {
            perror("pipe create file");
            return;
        }

        pid_t id = fork();
        if(id == 0)
        {
            // child
            close(pipefd[1]);
            dup2(pipefd[0], 0);
            close(pipefd[0]);
            slaver();
            std::cout << "process " << getpid() << ":quit!" << std::endl;
            exit(0);
        }
        //father
        close(pipefd[0]);

        //开始添加channel字段
        std::string name = "process-" + std::to_string(i);
        channels->push_back(channel(pipefd[1], id, name));
    }
}

 而解决办法是,把上一个写端记录下来,在创建子进程时,顺便把子进程的继承自父进程的写端给close掉。 

3.命名管道

        1.思考

        我们上面使用的匿名管道只能在有共同祖先/血缘相近的进程间使用,而我们想在不同进程间进行管道通信时,应该怎么做呢?

        我们可以使用FIFO文件在做这项工作,它经常被称作命名管道。

        2.创建一个命名管道

               1.命令行创建 

mkfifo filename

                2.程序中创建 

int mkfifo(const char *filename,mode_t mode)

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

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

        4.命名管道的打开规则 

如果当前打开操作是为读而打开FIFO时

  • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
  • O_NONBLOCK enable:立刻返回成功

如果当前打开操作是为写而打开FIFO时

  • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
  • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO 

4.日志 

      此内容与管道无关,只是需在当前练习中打印日志,所以做一个笔记

日志包含:日志时间,日志等级,日志内容,文件的名称和行号

日志等级:

  • Info:常规消息
  • Warning:报警信息
  • Error:必要严重的问题,可能需要立即处理
  • Fatel:致命的
  • Debug:调试

日式时间相关函数 

time:打印时间戳

time_t time(time_t *t);

 当前时间戳传nullptr

gettimeofday:

int gettimeofday(struct timeval *tv, struct timezone *tz /*时区*/);

struct timezone *tz:时区,缺省为nullptr即可

localtime:

struct tm *localtime(const time_t *timep);

注意:这里年是从1900年开始的,所以要加上1900.月是从0开始的,所以要加1.

日志格式可变参数部分

 int vsnprintf(char *str, size_t size, const char *format, va_list ap);

日志代码实现

#pragma once

#include <iostream>
#include <string>
#include <stdarg.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


#define SIZE 1024

//日志等级
#define Info 0
#define Debug 1
#define Warning 3
#define Error 4
#define Fatel 5

//打印方式
#define Screen 1
#define Onefile 2
#define Classfile 3

#define Logfile "log.txt"

class Log
{
public:
    Log()
    {
        PrintMethod = Screen;
        path = "./log/";
    }

    void Enable(int method)
    {
        PrintMethod = method;
    }

    std::string levelToString(int level)
    {
        switch(level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";    
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatel:
            return "Fatel";
        default:
            return "None";
        }
    }

    //日志函数
    // void logmessage(int level, const char* format, ...)
    // {
    //     time_t t = time(nullptr);
    //     struct tm *ctime = localtime(&t);

    //     char leftbuffer[SIZE];
    //     snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
    //             ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
    //             ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

    //     va_list s;
    //     va_start(s, format);
    //     char rightbuffer[SIZE];
    //     vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
    //     va_end(s);

    //     //格式:默认部分+自定义部分
    //     char logtxt[SIZE];
    //     snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);

    //     //printf("%s", logtxt);
    //     printlog(level, logtxt);
    // }

    void printlog(int level, const std::string &logtxt)
    {
        switch(PrintMethod)
        {
        case Screen:
            std::cout << logtxt;
            break;
        case Onefile:
            PrintOneFile(Logfile, logtxt);
            break;
        case Classfile:
            PrintClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }

    void PrintOneFile(const std::string &filename, const std::string &logtxt)
    {
        std::string logname = path + filename;
        int fd = open(logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
        if(fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }

    void PrintClassFile(int level, const std::string &logtxt)
    {
        std::string filename = Logfile;
        filename += ".";
        filename += levelToString(level);
        PrintOneFile(filename, logtxt);
    }

    void operator()(int level, const char* format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);

        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        //格式:默认部分+自定义部分
        char logtxt[SIZE];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);

        //printf("%s", logtxt);
        printlog(level, logtxt);
    }
    
private:
    int PrintMethod;
    std::string path;
};

5.总结

        总的来说,匿名管道和命名管道都是面向字节流的,会进行同步和互斥,生命周期随进程,使用时需要打开文件,单向通行。让不同进程看到同一份资源 -- 文件。

  

三、system V共享内存

1.原理

共享内存是在物理地址空间上申请的,通过页表挂接到不同进程程序地址空间的一种通信方式。

那么这块物理内存是进程申请的还是操作系统来申请的呢?

答案是操作系统,因为进程具有独立性,进程申请的资源归进程所有。

ipcs -m :查看所有的共享内存

共享内存的生命周期是跟随的内核的,用户不主动释放,共享内存会一直存在,除非内核关闭(用户释放)。

ipcrm -m shmid :删除shmid对应的共享内存

2.代码书写

1.相关函数

1.shmget 

申请一块共享内存

int shmget(key_t key, size_t size, int shmflg);
返回值:

        共享内存标识符

key:

1.key是一个数字,这个数字是几不重要。关键在于它必须在内核中具有唯一性,能够让不同的进程进行唯一性标识

2.第一个进程可以通过key创建共享内存,第二个之后的进程,只要拿着同一个key,就可和第一个进程看到同一个共享内存!

3.对于一个已经创建好的共享内存,key在哪?key在共享内存的描述对象中!

4.第一次创建的时候,必须有一个key了,怎么有?

         ftok - convert a pathname(路径) and a project identifier(项目id) to a System V IPC key

key_t ftok(const char *pathname, int proj_id);

 ftok是一套算法,将路径字符串和整形id进行了数值计算。

5.key -- 类似 -- 路径 -- 唯一性

size:

        创建共享内存的大小,单位是字节

shmfig:

        如何创建,获取 。。 

IPC_CREAT单独使用,如果你申请的共享内存不存在,就创建,存在,就获取并返回
IPC_CREAT | IPC_EXCL如果你申请的共享内存不存在,就创建,存在,就出错返回。确保我们如果申请成功了一个共享内存,这个共享内存一定是一个新的
IPC_EXCL 不单独使用

注:

key与shmid

 key:操作系统内标定唯一性。

 shmid:只在你的进程内,用来表示资源的唯一性。

2.shmat
void *shmat(int shmid, const void *shmaddr, int shmflg);

将申请的共享内存挂接到进程的虚拟地址空间

3.shmdt
 int shmdt(const void *shmaddr);

 取消挂接到进程的虚拟地址空间的共享内存

4.shmctl
 int shmctl(int shmid, int cmd, struct shmid_ds *buf);

cmd:

IPC_STAT: 获取

IPC_RMID:删除

2.代码

comm.hpp

#ifndef __COMM_HPP__
#define __COMM_HPP__

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

#include "log.hpp"

using namespace std;


class comm
{
public:
    comm()
    {
        //log.Enable(Classfile);
    }

    //获取key
    key_t Getkey()
    {
        key_t k = ftok(pathname.c_str(), proj_id);
        if(k < 0)
        {
            log(Fatel, "ftok error string: %s, ftok error code: %d", strerror(errno), errno);
            exit(1);
        }
        log(Info, "ftok success, key is : 0x%x", k);
        return k;
    }

    int GetShareMemHelper(int flag)
    {
        int shmid = shmget(Getkey(), size, flag);
        if(shmid < 0)
        {
            log(Fatel, "create share memory error string: %s, error code: %d", strerror(errno), errno);
            exit(1);
        }
        log(Info, "create share memory success, shmid: %d", shmid);
        return shmid;
    }

    int CreateShm()
    {
        return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
    }

    int GetShm()
    {
        return GetShareMemHelper(IPC_CREAT);
    }

private:
    Log log;
    const string pathname = "/home/shen";
    const int proj_id = 0x6666;
    //共享内存大小一般是4096的整数倍, 如果我们申请4097的话,操作系统实际给出4096*2的大小
    const int size = 4096;
};

#endif

processa.cc



#include "comm.hpp"

Log log;

int main()
{
    comm co;
    int shmid = co.CreateShm();
    log(Debug, "create shm done");

    char *straddr = (char*)shmat(shmid, nullptr, 0);
    log(Debug, "attach shm done");

    while(true)
    {
        cout << "client say#:" << straddr << endl; //直接访问共享内存
        sleep(1);
    }

    shmdt(straddr);
    log(Debug, "detach shm done");

    shmctl(shmid, IPC_RMID, nullptr);
    log(Debug, "delete shm done");

    return 0;
}

 processb.cc



#include "comm.hpp"

Log log;
int main()
{
    comm co;
    int shmid = co.GetShm();
    log(Debug, "Get shm done");

    char *straddr = (char*)shmat(shmid, nullptr, 0);
    log(Debug, "attach shm done");

    while(true)
    {
        cout << "Please Enter@ ";
        fgets(straddr, 4096, stdin);
    }

    shmdt(straddr);
    log(Debug, "detach shm done");

    return 0;
}

3.共享内存特性

  1. 共享内存没有同步互斥之类的保护机制
  2. 共享内存是所有的进程间通信中,速度是最快的! ---  原因:拷贝次数少
  3. 共享内存内部的数据,由用户自己维护!

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

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

相关文章

了解IPS和IDS:这5个差异将改变你的安全观念!

IPS 代表 入侵防御系统&#xff08;Intrusion Prevention System&#xff09;&#xff0c;它是 IDS 的进一步发展&#xff0c;不仅具备检测攻击的能力&#xff0c;还能在检测到攻击后主动采取措施阻止攻击。IPS 通常部署在防火墙和网络设备之间&#xff0c;能够深度感知并检测流…

ubuntu18.04与windows文件互传

目录 window下载Xftp软件ubuntu上的配置windows端Xftp软件的使用 window下载Xftp软件 下载&#xff1a;家庭/学校免费版 安装教程推荐下面的文章 xftp7免费版安装教程&#xff08;详细&#xff09; ubuntu上的配置 在进入系统后&#xff0c;确保有网络连接的情况下按Ctrl A…

cookie与session区别和联系

在Web应用中&#xff0c;HTTP协议是无状态的&#xff0c;每次请求都是独立的&#xff0c;服务器无法直接识别一个用户的不同请求之间的关联。这就导致了如果我们希望在一个会话中保持一些数据的状态&#xff0c;比如用户的身份认证信息、购物车内容等&#xff0c;就需要借助Coo…

golang本地缓存库之bigcache

1. 前言 上周工作之余逛github看到一个本地缓存库bigcache&#xff0c;这个是allegro公司开源的一个项目&#xff0c;主要是用于本地缓存使用&#xff0c;根据他们的博客说明&#xff0c;他们编写这个库最初的目的就是实现一个非常快速的缓存服务。 看了下bigcache这个库的源…

[StartingPoint][Tier2]Base

Task 1 Which two TCP ports are open on the remote host? (远程服务器开放了哪两个TCP端口?) $ nmap -sC -sV 10.129.234.232 22,80 Task 2 What is the relative path on the webserver for the login page? (相关的登录页面路径是什么?) /login/login.php Task 3 …

自动驾驶控制算法

本文内容来源是B站——忠厚老实的老王&#xff0c;侵删。 三个坐标系和一些有关的物理量 使用 frenet坐标系可以实现将车辆纵向控制和横向控制解耦&#xff0c;将其分开控制。使用右手系来进行学习。 一些有关物理量的基本概念&#xff1a; 运动学方程 建立微分方程 主要是弄…

格局在尘埃之间与宇宙之外。

在沈自所 见天地、见众生、见自己。 所里待了两年半&#xff0c;感叹时光飞逝&#xff0c;无疑在所这段时间&#xff0c;对我的成长是巨大的支持。感谢我的导师给予我的自由度和包容&#xff0c;感谢817所有兄弟姐妹。感谢所里面的兄弟们&#xff0c;我们一起经历了很多事情&…

ctfshow web29-web40

命令执行 看清都过滤了些什么&#xff01;&#xff01; 知识点&#xff1a; web34&#xff1a;当;和()被过滤了就用语言结构&#xff0c;一般有echo print isset unset include require web37&#xff1a;data协议是将后面的字符串当成php代码执行&#xff0c;例如 /?cdat…

【leetcode面试经典150题】62. K 个一组翻转链表(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主&#xff0c;题解使用C语言。&#xff08;若有使用其他语言的同学也可了解题解思路&#xff0c;本质上语法内容一致&…

Oracle 可传输表空间(Transportable Tablespace)

在数据归档、备份、测试等场景&#xff0c;我们经常需要将数据从一个系统移动到另一个系统&#xff0c;一个较常用的方案是数据的导出/导入&#xff08;export/import&#xff09;&#xff0c;但是在数据量较大的场景&#xff0c;此方案可能比较耗时。而可传输表空间是一种以文…

大数据技术应用实训室解决方案

一、大数据课程体系 1.1 大数据实验实训课程体系设计依据 大数据实验实训课程体系的设计依据主要围绕培养目标、培养方案和课程体系建设三个方面来展开。 1、培养目标 大数据实验实训课程的设计旨在培养具备大数据理论知识和实践技能的专业人才。具体而言&#xff0c;这些人才…

Midjourney 中文文档

快速使用 学习如何在Discord上使用Midjourney Bot从简单的文本提示中创建自定义图像。 行为准则 不要表现出不良行为。不要使用我们的工具制作可能引起煽动&#xff0c;不安或引起争议的图像。这包括血腥和成人内容。尊重其他人和团队。 1&#xff1a;加入Discord 访问Midj…

【软件测试】关于Web自动化测试

文章目录 &#x1f343;前言&#x1f332;如何实现Web自动化&#x1f6a9;安装驱动管理&#x1f6a9;Selenium库的安装 &#x1f333;自动化常用函数&#x1f6a9;元素的定位&#x1f388;cssSelector&#x1f388;xpath &#x1f6a9;操作测试对象&#x1f388;点击/提交对象—…

Linux中docker安装

准备工作 系统要求 Docker 支持 64 位版本 CentOS 7/8&#xff0c;并且要求内核版本不低于 3.10。 CentOS 7 满足最低内核的要求&#xff0c;但由于内核版本比较低&#xff0c;部分功能&#xff08;如 overlay2 存储层驱动&#xff09;无法使用&#xff0c;并且部分功能可能不…

个人电脑信息安全注意事项

个人电脑信息安全注意事项 一、密码安全&#xff1a; 设置复杂且独特的密码&#xff0c;避免使用容易猜测或常见的密码。 定期更换密码&#xff0c;特别是在重要账户或应用上。 不要在多个账户上重复使用相同的密码。 使用密码管理工具来安全地存储和访问密码。 二、软件安…

在传统云安全失败时提供帮助的六种策略

随着基于内存的攻击的激增继续挑战传统的云安全防御&#xff0c;对主动和全面的安全措施的需求变得至关重要。采用结合端点检测和响应、内存完整性保护和定期更新的多层方法可以加强对这些难以捉摸的威胁的防御。 随着云计算技术在各行各业的迅速普及&#xff0c;数据保护和安全…

在Windows 10中禁用Windows错误报告的4种方法,总有一种适合你

序言 在本文中&#xff0c;我们的主题是如何在Windows 10中禁用Windows错误报告。你知道什么是Windows错误报告吗&#xff1f;事实上&#xff0c;Windows错误报告有助于从用户的计算机收集有关硬件和软件问题的信息&#xff0c;并将这些信息报告给Microsoft。 它将检查任何可…

图像哈希:GLCM+DCT

文章信息 作者&#xff1a;Ziqing Huang期刊&#xff1a;IEEE&#xff08;一区&#xff09;题目&#xff1a;Perceptual Image Hashing with Texture and Invariant Vector Distance for Copy Detection 目的、实验步骤及结论 目的&#xff1a;使用GLCM进行全局特征的提取&am…

Transformer - 时间特征的处理

Transformer - 时间特征的处理 flyfish ETTm1.csv有如下内容 假如有2016/7/1 0:45:00有这样的时间字符串&#xff0c;如何变成时间特征列表 from typing import Listimport numpy as np import pandas as pd from pandas.tseries import offsets from pandas.tseries.freq…

BFS 专题 ——FloodFill算法:733.图像渲染

文章目录 前言FloodFill算法简介题目描述算法原理代码实现——BFSCJava 前言 大家好啊&#xff0c;今天就正式开始我们的BFS专题了&#xff0c;觉得有用的朋友给个三连呗。 FloodFill算法简介 中文&#xff1a;洪水灌溉 举个例子&#xff0c;正数为凸起的山峰&#xff0c;负…