【Linux】内存文件系统的I/O、重定向

文章目录

  • 1. 系统中的文件
  • 2. 回顾C中的文件接口
  • 3. 文件类的系统调用
    • 3.1 open
    • 3.2 文件描述符
  • 4. IO的基本过程
  • 5.重定向
    • 5.1 引入重定向
    • 5.2 系统中的重定向接口
  • 6. 缓冲区问题
  • 7. 简单版shell的实现

在这里插入图片描述

1. 系统中的文件

在学习完Linux权限后,我们清楚的知道:文件 = 文件内容 + 文件属性,这和进程就很像。所以我们文件的所有操作无非就是对文件内容或属性进行的。

无论何种语言,在访问文件之前,我们都必须打开文件,例如C语言中的fopen。但是我们访问前为什么要打开它呢?

文件在没有被访问的时候,它存储在磁盘上。学习完进程后,我们清楚的知道,访问文件其实是进程在访问。

在下面的代码中,当程序跑起来,进程执行到fopen时,文件才被打开。
在这里插入图片描述
进程是在内存中的,最终要被CPU执行;当进程执行文件操作时,但文件在磁盘上,根据冯诺依曼体系,CPU不能访问磁盘。
所以,文件也必须加载到内存中,否则进程访问不了(因为CPU访问不了),所以,打开文件的本质是:将文件加载到内存中。

因为文件 = 内容 + 属性,所以加载时,加载的就是文件的内容或属性。

我们知道一个进程可以打开多个文件,多个进程就可以打开更多的文件。
既然操作系统要管理进程,那么操作系统也要管理加载到内存中的文件(文件是谁加载的?什么时候加载的?要不要释放?…),操作系统管理加载到内存中文件的方式:先描述,再组织!

因此,我们研究打开的文件,是在研究:进程和文件的关系!

文件就可以分为:

  • 被打开的文件 - -内存中
  • 未被打开的文件 - - 磁盘中

2. 回顾C中的文件接口

在C语言中,我们学习了很多的文件相关接口,例如:
在这里插入图片描述
在系统文件中,我们重点关注 w 与 a
在这里插入图片描述
在这里插入图片描述


在这里插入图片描述
执行任何一个程序,进程默认会打开三个输入输出流,分别是stdin, stdout, stderr。
在这里插入图片描述

仔细观察发现,这三个流的类型都是FILE*,而且fopen的返回值也是FILE*。

在任何一个语言中,都会提供类似的三个流,既然语言都支持,那这本身并不属于语言的特性,而是属于操作系统所做的工作。

所以各种语言提供的访问文件(键盘、显示器)的接口,本质都是封装的文件类的系统调用接口。

3. 文件类的系统调用

3.1 open

在这里插入图片描述

  • 参数

第二个参数是标记位,上方列出了常用的选项,它们本质上都是宏,可以组合使用。

但是以前使用多个宏时,我们需要传递多个参数,为什么这里一个int flag就能解决呢?- - - 位图,32个比特位,可以存储32个选项。如果是这样的话,那上方列出的宏选项,它们应该只有一个比特位为1

举个栗子:
在这里插入图片描述
上方代码就通过检查flag对应位置上是0还是1,就可以实现传递多个选项的效果。

下面我们使用一下系统调用open
在这里插入图片描述
在这里插入图片描述
运行后发现,文件不存在时确实创建了,但是文件的权限为什么是错乱的呢?

因为创建文件和起始权限是操作系统的两个分支功能,操作系统不会在系统调用上让你创建文件时就按照默认权限来,它也没有这个权力。你要告诉系统调用,文件的默认起始权限是什么。

我们需要通过第三个参数mode指定,这不就是权限那里的chmod吗?

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

权限不是666是因为有 umask,去除umask可在函数中调用 umask(0);
每个进程都有自己的umask,继承自系统,如果你设置了umask,它采用就近原则使用用户设置的,不使用系统的了。

有了open,我是不是就可以自己实现一个touch命令了。

如果是touch命令,则获取命令行中的第二个参数,调用open(argv[1],O_WRONLY | O_CREAT,0666)
不重新设置umask时,文件权限就是664

  • 返回值

在这里插入图片描述
打开并新建一个文件后,会返回新文件的文件描述符descriptor
在这里插入图片描述
上方的代码返回的文件描述符为什么是3呢?- - 后面讲
它有什么用呢?

在这里插入图片描述

使用文件描述符可对文件进行操作
在这里插入图片描述

修改要写入的内容后
在这里插入图片描述

为什么此时写到文件中的内容,没有清空之前的内容呢? - - (因为你只告诉open系统调用,只写,没有就创建,没告诉我要清空。)

清空/截断的选项:O_TRUNC ,带上该选项,写入前就会清空。
在这里插入图片描述
新增式的写,需要带上选项:O_APPEND

有了上面的知识后,我们就可以清楚的知道:fopen -> open,fclose -> close,fwrite->write,fread -> read;w、a、r就对应相应的宏,所以语言层的文件库函数,本质上就是封装了文件的系统调用接口。

3.2 文件描述符

在这里插入图片描述

open的返回值(文件描述符)为什么从3开始呢?

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2。 0,1,2对应的物理设备一般是:键盘,显示器,显示器

那么此时就可以直接使用三个默认打开的文件描述符了

 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <string.h>
 int main()
 {
	 char buf[1024];
	 ssize_t s = read(0, buf, sizeof(buf));//从标准输入中读
	 if(s > 0)
	 {
		 buf[s] = 0;
		 write(1, buf, strlen(buf));//输入到标准输出中
	 }
	 return 0;
 }

所以这个文件描述符到底是什么呢?- - 连续的数字,是数组下标吗?

下面我们从内核的角度看一下
在这里插入图片描述

内核源代码中确实存在文件描述符表
在这里插入图片描述

所以,每次我们在open时,当前进程会去自己的文件描述符表中的 结构体指针数组 中从前向后找空位置

那么,在系统层面,文件描述符就是访问文件的唯一方式。

但是我们平常在使用C标准库提供的文件访问函数时,并没有看到相应的fd呀?- - 这是因为库中进行了封装,全部封装到FILE中去了!
在这里插入图片描述
在这里插入图片描述


上面所说的我都能理解,可是键盘、显示器也能被当作文件来看吗?Linux下一切皆文件又如何理解呢?

对于硬件来说,它们都有一个特点,它们统一叫做外设。操作系统是通过类似于链表的方式将各个外设管理起来的。
在这里插入图片描述

但对于很多外设(struct device)来说,它们的属性可以相同,但是属性的内容可以不同;可是键盘就是键盘、显示器就是显示器,对这两个外设进行操作的方法只有两个:读和写(IO),所以它们的方法一定不同,因为不同的设备访问对应硬件的方式是不同的。那是怎么做到统一以文件的视角来访问的呢?

  • 对于每一种设备,都要给其定义读写方法。
    例如:键盘定义了读和写的方法,但是只需要实现读方法;显示器定义了读和写的方法,但是只需要实现写方法。对于未实现的方法,就让它为空。
  • 在对设备进行管理时,只需要在其struct file中设置两个函数指针,让其指向对应的方法(不关心方法的实现),屏蔽了底层的差异。
    在这里插入图片描述
  • 从struct file向上开始,所有访问硬件的方法,统一叫做读和写,struct file就相当于做了一次封装。
  • Linux系统对外部只需要提供struct file对象,这叫做虚拟文件系统vfs
  • 那这个struct file是怎么让用户看到的呢?通过文件描述符,让进程看到。
    • 因为所有用户的行为,都会被转化为进程
    • 站在进程角度,它只需要通过fd,找到对应的struct file,执行struct file中对应的方法即可完成对设备的操作
      所以Linux中一切皆文件是对进程而言的
  • 在一个结构体当中,一切皆文件,但是底层却有不同硬件,这种模式在C++ 中叫做多态。(struct file就是父类,因为struct file都一样;硬件的struct device就是子类,子类实现了不同的读写方法;继承就是产生了上下层关系),这就是C语言中实现多态的一种形式。

内核源代码
在这里插入图片描述

4. IO的基本过程

  • 写文件
    在这里插入图片描述

其实write做的工作就是将要写的内容,拷贝到文件的内核缓冲区即可。

  • 读文件

先从磁盘读到文件的内核缓冲区,在调用read函数拷贝到指定位置

  • 修改文件

修改的本质:也是先读取,在写入(先将文件加载到文件的内核缓冲区,在内核中修改,然后再将修改后的内容从缓冲区加载到外设中)

存在缓冲区的原因:内存的操作快,外设的操作慢

5.重定向

5.1 引入重定向

先看一个现象
在这里插入图片描述
为什么关掉0以后,我自己打开的文件就是0了呢?

因为进程打开文件,需要给进程分配新的fd,fd的分配规则:最小的,没有被使用的fd!会从上往下扫描文件描述符表,找未被使用的,最小的。

如果我把1号关了,此时fd1就应该为1;由于printf底层封装了fprintf,fprintf中有FILE*参数,默认fd为1,所以只会向1号描述符中打印,不管1指向哪里,那么就应该将内容打印到log1.txt中!

在这里插入图片描述
但是log1.txt中怎么什么都没有呢?
在这里插入图片描述

fflush(stdout)后为什么又有了呢? - - 跟缓冲区相关,后面讲。

为什么本来应该向显示器中打印的内容,最终却写到了文件中呢?
因为在上层的调用中,fprintf、fwrite等向stdout中打印的所有调用,它们只认文件描述符1。 1号描述符表并没有变,我们只改变了1中的内容,这就叫做重定向

所以,重定向的原理就是:更改文件描述符表中特定下标中的内容。重定向的过程中,上层代码毫不知情!

5.2 系统中的重定向接口

dup2:系统重定向接口
在这里插入图片描述
本质就是用新的fd覆盖到指定位置! 被覆盖的将被关掉。

  • 输出/追加重定向

在这里插入图片描述

追加重定向不就是只需要将选项 O_TRUNC换成O_APPEND了吗?

  • 输入重定向

在这里插入图片描述

在写我们自己的shell时,思考一下,程序替换会影响重定向的结果吗?

  • 不会,因为程序替换仅仅是替换进程所对应的代码和数据,必要时修改mm_struct中页表的映射关系。对于一个进程“上层”的东西(task_struct、file_struct、mm_struct等)都不会修改,依旧使用重定向以后的内容。

6. 缓冲区问题

在这里插入图片描述
在上面的内容中,我们遗留了一个问题。
那就是为什么我重定向以后它并没有直接给我写到指定文件中,而fflush(stdout)后就写进去了呢?

  • 首先我们要知道,在C语言中我们使用的printf、scanf、fprintf、fscanf、fwrite、fread等都要求有一个FILE*的指针。
  • 所以,在调用这些函数进行操作时,它并没有直接调用系统调用read、write直接拷贝到文件的内核缓冲区,因为频繁的调用系统调用的成本太高了,效率低。
  • 所以怎么能提高效率呢?
  • 通过用户级缓冲区!! 你printf、fprintf等只需要将内容拷贝到用户级缓冲区中任务就完成了,无非就是在拷贝的过程中进行一下格式化;等用户级缓冲区攒了足够多的数量,在统一调用系统调用写入到文件的内核缓冲区,提高了效率。
  • 该缓冲区在FILE结构体中,刷新的本质就是从用户级缓冲区拷贝到内核的文件缓冲区。

用户级缓冲区有以下几种刷新方案:

  • 显示器文件:行刷新
  • 普通文件:缓冲区写满再刷新
  • 不缓冲(语言级无需刷新)

我们将最开始的代码修改一下会发现,如果我不调用任何的close或者调用fclose,内容可以正常打印出来,这是为什么呢?
在这里插入图片描述
因为,当一个进程退出的时候,会自动刷新自己的缓冲区(所有的FILE对象内部,包括stdin、stdout、stderr);fclose是C语言级的,调用它关闭FILE时,也会自动刷新。

那close(fd)后,为什么不会刷新呢?
在这里插入图片描述
此时尽管“表面上”是向显示器中打,应该是行刷新,那么我不自己刷新应该也可以显示出来呀? - - 此时不是行刷新,因为显示器文件早就关闭了,1中放的是普通文件,应执行写满刷新的策略。

  • 那操作系统是什么时候将文件内核缓冲区的内容刷新到外设中的呢?我能不能控制呢?
  • 通过系统调用fsync在这里插入图片描述
  • 一个简单的题目

在这里插入图片描述
如果在调用函数时不加 \n,即使不重定向,也是上图所示的打印效果。

7. 简单版shell的实现

#include<cstdio>
#include<stdlib.h>
#include<string>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
using namespace std;

const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;
//存储命令行参数的两个全局变量
char* g_argv[argvnum];
int g_argc;
//存shell自己的环境变量
char* g_env[envnum];

//如果这两个设置为局部变量,则会写入一个空白
//局部变量销毁,环境变量表中存的是这两个变量的地址,所以就是空
char pwd[basesize];
char pwdenv[basesize];

//存储之前的退出码
int lastcode = 0;

//重定向相关全局变量
#define NonRedir 0
#define InputRedir 1
#define OutputRedir 2
#define AppendRedir 3
int redir = NonRedir;
char* filename = nullptr;
//去除空格
#define TrimSpace(pos)\
    do {\
        while(isspace(*pos)){\
            pos++;\
        }\
    }while(0)\


string GetName()
{
    string username = getenv("USER");
    return username.empty() ? "None" : username;
}

string GetHostName()
{
    string hostname = getenv("HOSTNAME");
    return hostname.empty() ? "None": hostname;
}

bool repalcePwd()
{
    for(int i = 0; g_env[i]; i++)
    {
        if(strncmp(g_env[i],pwdenv,3) == 0)
        {
            g_env[i] = pwdenv;
            return true;
        }
    }
    return false;
}

string GetPwd()
{
    if(getcwd(pwd,sizeof(pwd)) == nullptr)
        return "Node";
    //将当前的工作路径,保存至环境变量中
    snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);

    //putenv(pwdenv); //将新的pwd,添加到系统的环境变量表中
    repalcePwd();//将新的pwd,添加到自己的环境变量表中
    return pwd;
   // string pwd = getenv("PWD");
}

string LastDir()
{
    string pwd = GetPwd();
    if(pwd == "/" || pwd == "None")
        return pwd;
    //寻找最后一个文件夹
    int pos = pwd.rfind("/");
    return pwd.substr(pos+1);
}

//1.显示命令行提示符
void ShowCommandLine()
{
    char command_line[basesize];
    snprintf(command_line,basesize,"[%s@%s %s]#",\
            GetName().c_str(),GetHostName().c_str(),LastDir().c_str());
   printf("%s",command_line); 
   fflush(stdout);
}

//2.读取命令行参数
bool GetCommandLine(char command_buffer[],int size)
{
    //读取一行的用户输入
    char* ret = fgets(command_buffer,size,stdin);
    if(ret == NULL)
    {
        return false; //获取输入失败
    }
    //处理回车键
    command_buffer[strlen(command_buffer) - 1] = '\0';
    if(strlen(command_buffer) == 0)
        return false;
    return true; 
}

void debug()
{
    printf("argc:%d\n",g_argc);
    for(int i=0; g_argv[i]; i++)
    {
        printf("[%d] = %s\n",i,g_argv[i]);
    }
}

void CheckRedir(char command_line[],int len)
{
    int end = len - 1;
    while(end >= 0)
    {
        if(command_line[end] == '<')
        {
            redir = InputRedir;
            command_line[end] = '\0';
            filename = &command_line[end+1];
            //过滤空格
            TrimSpace(filename);
            break;
        }
        else if(command_line[end] == '>')
        {
            if(command_line[end - 1] == '>')
            {
                redir = AppendRedir;
                command_line[end] = '\0';
                command_line[end-1] = '\0';
                filename = &command_line[end+1];
                //过滤空格
                TrimSpace(filename);
                break;
            }
            else 
            {
                redir = OutputRedir;
                command_line[end] = '\0';
                filename = &command_line[end+1];
                //过滤空格
                TrimSpace(filename);
                break;
            }
        }
        else 
        {
            end--;
        }
    }
}

void InitCommand()
{
    //命令行清空
    memset(g_argv,0,sizeof(g_argv));
    g_argc = 0;
    //每次检查是否有重定向前先清空
    redir = NonRedir;
    filename = nullptr;
}

void AnalyCommand(char command_line[])
{
    const char sep[10] = " ";//指定分隔符
    g_argv[g_argc++] = strtok(command_line,sep);//先提取第一个
    //strtok读取失败返回null
    while((g_argv[g_argc++] = strtok(nullptr,sep)));//依次提取后面的
    g_argc--;//个数要-1
}

void AnalyzeCommandLine(char command_line[])
{
    InitCommand();
    //printf("redir before:%s\n",command_line);
    //检查重定向
    CheckRedir(command_line,strlen(command_line));
  	// printf("redir:%d\n",redir);
  	// printf("filename:%s\n",filename);
  	// printf("redir after:%s\n",command_line);
    AnalyCommand(command_line);
}

void Redir()
{
    //程序替换不会影响重定向,因为内核数据结构中的file_struct没变
    //程序替换,替换的是代码和数据。
    int fd = -1;
    if(redir == InputRedir)
    {
        if(filename)
        {
            fd = open(filename,O_RDONLY);
            if(fd < 0)
            {
                exit(3);
            }
            dup2(fd,0);
        }
        else 
        {
            exit(2);
        }
    }
    else if(redir == OutputRedir)
    {
        if(filename)
        {
            fd = open(filename,O_CREAT | O_WRONLY | O_TRUNC,0666);
            if(fd < 0)
            {
                exit(5);
            }
            dup2(fd,1);
        }
        else  
        {
            exit(4);
        }
    }
    else if(redir == AppendRedir)
    {
        if(filename)
        {
            fd = open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);
            if(fd < 0)
            {
                exit(7);
            }
            dup2(fd,1);

        }
        else 
        {
            exit(6);
        }
    }
    else 
    {
        //没有重定向
        //do nothing
    }
}
bool ExecCommandLine()
{
    //shell创建子进程执行任务
    pid_t id = fork();
    if(id < 0)
        return false;
    if(id == 0)
    {
        //child执行命令
        //重定向应由子进程做
        Redir();

        execvpe(g_argv[0],g_argv,g_env);
        exit(1);//执行失败,退出码为1
    }
    int status = 0;
    pid_t rid = waitpid(id,&status,0); //阻塞式等待

    if(rid > 0)
    {
        //等待成功
        //1.子进程正常结束
        if(WIFEXITED(status))
        {
            lastcode = WEXITSTATUS(status);
        }
        else 
        {
            lastcode = 99;
        }
        return true;
    }
    return false;
}

void AddEnv(const char* str)
{
    int index = 0;
    while(g_env[index])
    {
        index++;
    }
    g_env[index] = (char*)malloc(strlen(str) + 1);
    strncpy(g_env[index++],str,strlen(str)+1);
    g_env[index] = nullptr;
}

bool CheckAndExecBuildCommand() //判断是否式内建命令
{
    //枚举几个内建命令
    if(strcmp(g_argv[0],"cd") == 0)
    {
        if(g_argc == 2)
        {
            chdir(g_argv[1]);
            lastcode = 0;
        }
        else
        {
            lastcode = 1;
        }
        return  true;
    }
    else if(strcmp(g_argv[0],"export") == 0)
    {
        if(g_argc == 2)
        {
            AddEnv(g_argv[1]);
            lastcode = 0;
        }
        else
        {
            lastcode = 2;
        }
        return true;
    }
    else if(strcmp(g_argv[0], "env") == 0)
    {
        for(int i = 0; g_env[i]; i++)
        {
            printf("%s\n",g_env[i]);
        }
        lastcode = 0;
        return true;
    }
    else if(strcmp(g_argv[0],"echo") == 0)
    {
        if(g_argc == 2)
        {
            if(g_argv[1][0] == '$')
            {
                if(g_argv[1][1] ==  '?')
                {
                    printf("lastcdoe:%d\n",lastcode);
                    lastcode = 0;
                }
            }
            else
            {
                printf("%s\n",g_argv[1]);
                lastcode = 0;
            }
        }
        else
        {
            lastcode = 3;
        }
        return true;
    }
    return false;
}

void InitEnv()
{
    extern char** environ;
    //从系统shell中获取环境变量
    int index = 0;
    while(environ[index])
    {
        int len = strlen(environ[index]);
        g_env[index] = (char*)malloc(len + 1);
        strncpy(g_env[index],environ[index],len + 1);
        index++;
    }
    g_env[index] = nullptr;
}

int main()
{
    InitEnv();
    char command_buffer[basesize];
    while(true)
    {    
        //1.显示命令行提示符
        ShowCommandLine();
        //slsleep(2);
        //printf("\n");
        //2.读取命令行参数
        if( !GetCommandLine(command_buffer,basesize) )
        {
            continue; 
        }
        
        //3.解析命令行参数
        AnalyzeCommandLine(command_buffer);
        //debug();
        if(CheckAndExecBuildCommand()) //判断是否式内建命令
        {
            continue;
        }
        //4.执行命令
        ExecCommandLine();
    }
    return 0;
}

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

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

相关文章

【JVM】内存模型

文章目录 内存模型的基本概念案例 程序计数器栈Java虚拟机栈局部变量表栈帧中局部变量表的实际状态栈帧中存放的数据有哪些 操作数栈帧数据 本地方法栈 堆堆空间是如何进行管理的? 方法区静态变量存储 直接内存直接内存的作用 内存模型的基本概念 在前面的学习中,我们知道了字…

论文笔记:Pre-training to Match for Unified Low-shot Relation Extraction

论文来源&#xff1a;ACL 2022 论文地址&#xff1a;https://aclanthology.org/2022.acl-long.397.pdf 论文代码&#xff1a;https://github.com/fc-liu/MCMN &#xff08;笔记不易&#xff0c;请勿恶意转载抄袭&#xff01;&#xff01;&#xff01;&#xff09; 目录 A…

从头预训练一只迷你 LLaMA 3_llama3 预训练预处理

我将向你展示如何使用 LLama 3.1&#xff08;一个本地运行的模型&#xff09;来执行GraphRAG操作&#xff0c;总共就50号代码。。。 首先&#xff0c;什么是GraphRAG&#xff1f;GraphRAG是一种通过考虑实体和文档之间的关系来执行检索增强生成的方式&#xff0c;关键概念是节…

Elasticsearch学习笔记(七)安装并配置Metricbeat

Metricbeat 是一个轻量级的开源数据采集器&#xff0c;专门用于收集操作系统和服务的指标&#xff08;metrics&#xff09;。它是 Elastic Stack&#xff08;即 ELK Stack&#xff09;的一部分&#xff0c;通常用于监控系统性能、收集应用程序和服务器的性能指标&#xff0c;并…

【大模型】AI视频课程制作工具开发

1. 需求信息 1.1 需求背景 讲师们在制作视频的过程中&#xff0c;发现录制课程比较麻烦&#xff0c;要保证环境安静&#xff0c;保证录制过程不出错&#xff0c;很容易反复重复录制&#xff0c;为了解决重复录制的工作量&#xff0c;想通过 ai 课程制作工具&#xff0c;来解决…

字节跳动青训营——入营考核解答(持续更新中~~~)

考核内容&#xff1a; 在指定的题库中自主选择不少于 15 道算法题并完成解题&#xff0c;其中题目难度分配如下&#xff1a; 简单题不少于 10 道中等题不少于 4 道困难题不少于 1 道 解答代码 5.简单四则运算 &#xff08;中&#xff09; 代码实现&#xff1a; import ja…

TON(六)——fift算法,注释的改写

系列文章目录 TON&#xff08;五&#xff09; TON&#xff08;四&#xff09; TON&#xff08;三&#xff09; TON&#xff08;二&#xff09; TON&#xff08;一&#xff09; 前言 fift是一门十分强大的栈编程语言&#xff0c;&#xff0c;在TON中它是由c编译而成的语言…

WordPress官方发布“新”插件“SCF”(安全自定义字段)

安全自定义字段 (SCF) 为您提供了处理数据所需的所有工具&#xff0c;从而将 WordPress 网站转变为成熟的内容管理系统。 使用 SCF 插件可以完全控制您的 WordPress 编辑屏幕、自定义字段数据等。 按需添加字段—SCF字段生成器允许您快速轻松地将字段添加到 WP 编辑屏幕&…

第一个servlet程序

文章目录 在原有工程上建立模块前端配置前后端映射关系添加外部依赖库后端代码启动配置 在原有工程上建立模块 添加web框架 前端 应用结构 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>第一…

什么是SYN flood,如何处理

在数字化时代&#xff0c;随着互联网的普及和技术的飞速发展&#xff0c;网络安全问题变得日益严峻。Flood攻击&#xff0c;作为一种典型的网络攻击手段&#xff0c;对个人和企业的信息安全构成了重大威胁。通过深入了解Flood攻击的概念、特点、影响及解决方案&#xff0c;我们…

SpringSecurity源码分析以及如何解决前后端分离出现的跨域问题

解决Security前后端分离出现的跨域问题 一. Security源码分析 首先在看源码之前我们先来看这张图 , 这张图展示了Security执行的全部流程 从上图可知Security执行的入口是UsernamePasswordAuthenticationFilter这个抽象类 , 那我们就先从该类进行分析 1. UsernamePasswordAu…

【智慧大屏】BI智慧大屏,大屏可视化解决方案(word原件)

1.系统概述 1.1.需求分析 1.2.重难点分析 1.3.重难点解决措施 2.系统架构设计 2.1.系统架构图 2.2.关键技术 2.3.接口及要求 3.系统功能设计 3.1.功能清单列表 3.2.数据源管理 3.3.数据集管理 3.4.视图管理 3.5.仪表盘管理 3.6.移动端设计 3.1.系统权限设计 3.…

Scala入门基础(12)抽象类

抽象类&#xff0c;制定标准&#xff0c;不要求去具体实现 包含了抽象方法的类就是抽象类。抽象方法只是有方法名&#xff0c;没有具体方法体的方法 定义抽象类要用abstract&#xff08;抽象&#xff09;关键字 用智能驾驶技术举例&#xff1a;演示&#xff09…

深入理解WPF中的命令机制

Windows Presentation Foundation&#xff08;WPF&#xff09;是微软推出的一种用于构建桌面客户端应用程序的技术。它被认为是现代Windows应用程序的基础&#xff0c;具有强大的图形和媒体处理能力。在WPF中&#xff0c;“命令”是一个重要的概念&#xff0c;它为应用程序开发…

2024.10月11日--- SpringMVC拦截器

拦截器 1 回顾过滤器&#xff1a; Servlet规范中的三大接口&#xff1a;Servlet接口&#xff0c;Filter接口、Listener接口。 过滤器接口&#xff0c;是Servlet2.3版本以来&#xff0c;定义的一种小型的&#xff0c;可插拔的Web组件&#xff0c;可以用来拦截和处理Servlet容…

力扣 142.环形链表Ⅱ【详细解释】

一、题目 二、思路 三、代码 /*** Definition for singly-linked list.* class ListNode {* int val;* ListNode next;* ListNode(int x) {* val x;* next null;* }* }*/ public class Solution {public ListNode detectCycle(ListNode hea…

LSL常见应用场景及示例<一>

目录 往期推荐 场景1&#xff1a;如何在指定内存定义中定位一个函数&#xff1f; 场景2&#xff1a;如何在绝对内存偏移地址处定位一个函数&#xff1f; 场景3&#xff1a;如何在绝对地址处定位一个函数&#xff1f; 场景4&#xff1a;有多个函数必须位于特定的内存定义中。…

vue3+ts+vite--路由跳转,params传参好像丢失了?

前言 相信大家一定写过后台管理系统&#xff0c;有一个很普遍的功能&#xff0c;就是点击编辑&#xff0c;根据id&#xff0c;跳转到相对应的编辑页面&#xff0c;id是通过路由params传递过去了&#xff0c;但是还有一个需求是要将父组件的名称也传递过去 &#xff0c;过程特别…

从0到1封装一个image/pdf预览组件

iShot_2024-10-14_16.47.10 目录结构 content.vue <template><div class"no-content-block"><i class"iconfont icondocument large-file" /><div class"text-wrapper">{{ t(__ui__.siPreview.previewSupported) }}<…

Spring Cloud Sentinel配置

Spring Cloud Sentinel 文章目录 Spring Cloud Sentinel1. Sentinel Dashboard 启动2. Spring Cloud 客户端配置3. Sentinel Dashboard 限流配置流控模式直连关联链路 流控规则快速失败Warm Up排队等待 4. Sentinel Dashboard 熔断配置5. Sentinel Dashboard 热点配置 1. Senti…