【Linux】Linux下的基础IO

❤️前言

        大家好!今天这篇博客和大家聊一聊关于Linux下的基础IO。

正文

        在阅读本篇博客之前,请大家先回顾一下C语言文件操作的一些方法,这里可以看看我之前记录的一些内容:

【C语言】C语言成长之路之文件操作_MO_lion的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/MO_lion/article/details/128730282?spm=1001.2014.3001.5501

        除此以外,我们要引入C语言默认会打开的三个输入输出流,它们分别是stdin、stdout、stderr,它们三个是FIFE*类型的文件指针。

        我们知道,在Linux系统下一切事物都可以视作文件进行处理,那么键盘和显示屏也可以看做是两个文件,大家可以猜猜为什么C语言要默认打开文件指针呢?其实就是因为这三个文件指针指向的就是输入输出设备文件,这样我们便可以对键盘和显示屏进行读写,举例来说,我们用printf和scanf就是用到这些文件指针对设备文件进行写入和读取。

系统级IO

        要操作文件,我们不仅仅可以使用语言带有的文件接口,也可以使用操作系统提供的系统调用接口。

系统调用接口

        可能你会对系统调用接口感到疑惑,它是什么呢?让我们来看下面这张图:

        通过上面的这张图片,我们可以看出系统调用接口与库函数的关系,我们可以认为C语言中的文件操作函数都是对系统调用的封装,其目的是让我们用户更规范安全的操作文件。

        系统调用的意义与上述的封装的意义很相似。操作系统在管理资源的时候并不相信任何用户的操作,它采取的是一刀切的方式来减少可能发生的危险操作,不让用户直接操作底层数据,但是又要让用户进行上层的操作,于是操作系统便在用户和底层数据之间加上了一层系统调用接口。系统调用接口的存在能很好地规范用户的操作,保护底层的数据。

        接着,我们用系统调用接口演示一遍简单的文件读写操作:

写文件:

// 简单的写文件
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main()
{
    umask(0); // 将权限掩码设置为0
    // file descriptor: 文件描述符
    int fd = open("lion", O_CREAT|O_WRONLY, 0666); 
    if(fd < 0){
        perror("open");
        return -1;
    }

    const char* message = "I like Linux!\n";
    write(fd, message, strlen(message));

    close(fd);

    return 0;
}

        上述程序成功的创建出一个文件并向其中写入了一段字符:

读文件:

// 读文件
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main()
{
    // file descriptor: 文件描述符
    int fd = open("lion", O_RDONLY); 
    if(fd < 0){
        perror("open");
        return -1;
    }

    char buf[1024] = {0};
    read(fd, buf, 1023);
    printf("%s", buf);

    close(fd);

    return 0;
}

运行结果:

        现在我们根据上面的程序对文件读写的系统调用接口进行介绍:

打开文件open

用man指令看看open的文档
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。

mode:创建文件时,该文件的权限参数。

参数:
        O_RDONLY: 只读打开
        O_WRONLY: 只写打开
        O_RDWR : 读,写打开
        这三个常量,必须指定一个且只能指定一个
        O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
        O_APPEND: 追加写

返回值:
        成功:新打开的文件描述符
        失败:-1

文件描述符

        在继续了解其他系统调用之前,我们先来聊聊open正常返回的返回值文件描述符(file descriptor)。

        文件描述符fd是一个int类型的整数,顾名思义,它在每个系统调用接口的使用中扮演代表某个文件的角色,就类似于C语言中的FIFE*指针。那么这样简单的int类型整数是如何做到代表一个文件的呢?

        我们可以打印出fd的值,会发现它们的值都是一些接近0的小整数。这些值的意义是什么呢?

        这里我们又要引出一个新的知识,当每个进程要对某些文件做管理时,会有一个文件描述符表对所有的文件结构进行对应映射,也就是说,通过这个文件描述符表就可以让进程找到对应的文件并操作它。文件描述符表的映射是借由什么完成的呢?其实就是依靠数组下标,这里的数组下标指的就是文件描述符了。

        具体的内核存储结构是这样的:当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件,于是就有了file结构体表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程控制块task_struct都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。我们可以参照下图进行想象:

其他常用的IO系统调用接口:

        除了上面给出的系统调用接口的简单信息,大家也可以通过man指令的2号选项进行系统调用接口的文档信息查找。

系统默认打开的文件描述符

        当进程要对文件进行操作时,就需要使用文件描述符,因为所有IO库函数的底层都是封装了系统调用的,使用系统调用就需要传入文件描述符。而我们又知道,Linux下一切事物都可以看做文件,也就是说:键盘和显示器也要看做文件!那么当我们在进行对键盘和显示器文件的输入输出时,也就需要文件描述符,但是在平时对键盘和显示屏做输入输出时,我们其实并没有手动的创建过文件描述符,那么这些用于操作IO设备的文件描述符是从哪里来的呢?

        其实,当我们创建一个进程以后,操作系统会默认帮我们打开三个文件描述符分别占用文件描述符表的前三个位置,代表下标0、1、2(这三个数字也对应C语言中打开的三个标准输入输出的FIFE*指针stdin、stdout、stderr),这三个文件描述符分别代表的是标准输入、标准输出、标准错误输出,它们一般也对应着这三个物理设备:键盘、显示器、显示器。

        其实这里我们也可以验证上层语言的库函数与系统操作的一些关系,如果我们访问C语言默认打开的三个文件指针:

printf("stdin->fd: %d\n", stdin->_fileno);
printf("stdout->fd: %d\n", stdout->_fileno);
printf("stderr->fd: %d\n", stderr->_fileno);

 我们就会发现对应的输出结果是:

       输出结果表明系统和C语言默认打开的东西产生了关联,这与我们之前所说库函数是由系统调用封装而成的观点不谋而合。

        当我们掌握了这样的信息,那我们也就可以自己使用系统调用接口进行键盘读入和显示屏输出。我们可以运行一下以下的代码:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main()
{
    // file descriptor: 文件描述符
    int fd = open("lion", O_RDONLY, 0666); 
    if(fd < 0){
        perror("open");
        return -1;
    }
    
    //之前所输入的信息
    //const char* message = "I like Linux!\n";

    // 用户级缓冲区
    char buf[1024] = {0};
    read(fd, buf, 1023);
    // 我们可以看看这两个操作的效果:
    write(1, buf, strlen(buf));
    printf("%s", buf);

    close(fd);

    return 0;
}

        运行结果如下:
 

        大家也可以使用read来读取0号文件(键盘)的信息,这里就不再演示。

文件描述符的分配规则

        我们现在了解了一些文件描述符的概念和使用方式,那么当我们在一个进程中打开一个新出现的文件,这个文件的文件描述符是如何分配的呢?

        我们直接使用代码进行实验:

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

int main()
{
    int fd = open("myfile", O_RDONLY);
    if(fd < 0){
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
    close(fd);
    return 0;
}

        运行结果是得到了一个值为3的文件描述符,先前说过,系统默认缺省的占用了0、1、2三个文件描述符,这里得到的值为3,我们可以合理的猜测fd是直接按照表的顺序从上往下给的值。那么当我们再关闭了之前的文件继续试着打开新的文件:

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

int main()
{
    close(0);
    //close(2);
    int fd = open("myfile", O_RDONLY);
    if(fd < 0){
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
    close(fd);
    return 0;
}

        结果是fd:0。也就是说当我们一开始就将对应键盘的文件描述符0给关闭掉,新打开的文件就直接占据了原来的0的位置。那么现在我们就可以得出结论:文件描述符的分配规则就是在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

        那么现在我们可以完善一下上面的进程控制文件的图片:

重定向

        按照之前的知识,当我们让一个新文件占据显示器的文件描述符,这时候所有原本写入显示器的信息将会被写入至这个新的文件中,让我们运行代码检验一下这样的现象:

//一段伪代码:

// 关闭显示器文件,打开新的文件
close(1);
int fd = open("lion", O_WRONLY|O_CREAT, 00644);

if(fd < 0){
    perror("open");
    return 1;
}

printf("fd: %d\n", fd);
fflush(stdout);

close(fd);

        运行结果如下:

        我们将本应该输入到屏幕上的信息输入到了文件lion中,这种现象就叫做输出重定向!不知道大家是否知道命令行代码中的重定向指令,其实重定向指令就是在进程运行时将对于IO设备的输入输出改成对于某个其他文件的输入输出。

        我们将上述过程以图片形式展现,如下图所示:

        除此之外,让我们重新简单了解一下命令行指令中的一些重定向,这里参考chatgpt的回答:

        除了上面的“通过关闭显示器文件然后用新的文件来占用显示器文件的文件fd1”所产生的输出重定向以外,我们其实主要通过别的方式来完成重定向的动作,这需要一些系统调用接口来完成,它们分别是:

  1. `open()`:用于打开文件。在重定向中,`open()` 被用于打开指定的文件,并返回一个文件描述符。
  2. `close()`:用于关闭文件。在重定向中,`close()` 被用于关闭不再需要的文件描述符。
  3. `dup()`和 `dup2()`:用于复制文件描述符。在重定向中,`dup()` 可以用来复制文件描述符,使得多个文件描述符指向同一个文件。`dup2()` 则可以用于将一个文件描述符复制到另一个特定的文件描述符,从而实现重定向。其中,比较常用的是dup2,我们主要了解的就是它。

使用 dup2 系统调用

        在继续了解重定向中的系统调用的运作方式之前,我们首先来简单的看看dup2的文档解释。

        简单来说,我们可以向dup2中输入两个文件描述符 oldfd 和 newfd ,dup2会将newfd代表的文件改变成oldfd所代表的文件,也就是说,当我们使用了dup2以后,这两个文件描述符将会同时代表原来oldfd所描述的文件。从文件描述符作为数组下标的概念上来说,就是下标本身的值不变,而将下标对应的值都改为oldfd所指向的元素值。

        举例图如下:

  • 输出重定向 (>, >>):

    • 使用 open() 打开指定文件,获得一个文件描述符。
    • 使用 dup2() 将标准输出的文件描述符(1)复制到新打开的文件描述符上,使得标准输出被定向到文件。
  • 输入重定向 (<):

    • 使用 open() 打开指定文件,获得一个文件描述符。
    • 使用 dup2() 将标准输入的文件描述符(0)复制到新打开的文件描述符上,使得标准输入被定向到文件。
  • 错误输出重定向 (2>, 2>>):

    • 类似于输出重定向,但是文件描述符是标准错误输出的(2)。
  • 将标准错误和标准输出合并 (2>&1):

    • 使用 dup2() 将标准错误输出的文件描述符(2)复制到标准输出的文件描述符上,使得标准错误和标准输出指向同一个位置。

        这些操作的组合和顺序使得在Linux系统中能够对输入输出进行高度的控制,从而实现重定向的功能。这是Linux操作系统中强大而灵活的特性之一。

加深对“一切皆文件”的理解

        我们曾经提出过“Linux下一切皆文件”的观点,那么在这篇关于系统IO的博客中,这个观点又可以如何进行重申呢?

        今天我们通过对于进程的文件管理进行对“一切皆文件”进行重新审视,我们知道进程可以通过文件描述符fd对文件进行访问和操作,那么便可以以此入手:

        通过上图,我们可以看见在虚拟文件系统VFS(vitrual file system)对各种不同事物、外部设备的统一描述下,所谓“一切皆文件”的大致实现方式。从这以后,我们对于“一切皆文件”的理解不再只局限于此结论的应用方面,还有底层实现机制方面的理解。

研究语言层IO

用户缓冲区的概念与深刻理解

        通过上面的学习我们对底层的系统调用有了一定的认识,现在重新返回到对于应用层设计的一些研究认识。

        让我们先来看下面的一段代码:

const char* fstr = "hello fwrite!\n";
const char* str = "hello write!\n";

printf("hello printf!\n");
fprintf(stdout,"hello printf!\n");
fwrite(fstr,strlen(fstr),1,stdout);

write(1,str, strlen(str));

        这段代码中调用了三个C语言库中的输出函数和一个系统调用接口write,并将一些字符输入到了屏幕上面,我们将其运行起来:

        结果不出所料,毕竟从刚刚学习编程的时候开始,我们就知道程序中的语句按照从上到下的顺序执行。那么我们现在想要将这里的输出全部都重定向到一个文件中:

        这是怎么回事?为什么程序的最后一个输出语句反而第一个写入到了文件中呢?我想大家的脑海中应该会冒出这样的问题,那么我们现在就正式引入用户缓冲区的知识。

        当我们使用C语言提供的输出函数向一些文件进行输出打印时,其实字符串首先并不是直接被输出到了对应的文件中,而是暂时被存在了一个C语言为我们提供的缓冲区中,这个缓冲区有两种缓冲方式,分别是行缓冲全缓冲。(除此之外,系统调用接口write不受C语言的管束,只要使用者规定了向什么文件中写入,我们可以看做它会直接向其中进行写入操作。)

        行缓冲我们曾经在编写进度条程序的时候遇到过,它的意思是缓冲区会一直不刷新直到有换行符被写入到缓冲区中,缓冲区才会被刷新。我们知道缓冲区对于显示器文件的刷新方式就是行缓冲,而当时我们编写进度条的时候并不想换行,于是采用了fflush函数直接刷新缓冲区中的数据至显示器文件中。

        全缓冲是缓冲区对于除了显示器文件以外的其他普通文件的刷新方式,这种方式是只有当缓冲区被写满时才会进行刷新。

        除此之外,缓冲区的刷新的本质可以看做调用了系统调用接口write,将缓冲区的数据直接写入到某个文件中。从中我们可以看出缓冲区的设计就是一种对于系统调用的封装和规范。

        引入缓冲区的知识以后,让我们将目光重新放回到上面的两种不同输出情况,我们大致可以猜出造成运行结果不同的原因跟写入文件的不同缓冲方式有关。

        第一次操作中,由于我们直接向显示器中打印信息,缓冲区刷新模式是行缓冲,于是所有的输出语句都是当被写入到缓冲区之后就直接刷新到了显示器上。而第二次操作进行了输出重定向,将对显示器文件的输出重定向为向普通文件的输出,于是缓冲区的刷新方式便由行缓冲改变成了全缓冲,那么你肯定就有这样的疑问了?难道是这三条字符串将缓冲区给写满了吗?答案并不是这样,毕竟如果是这样,也不会造成这两种结果输出顺序上的不一致。那么这三条字符串是怎么被刷新到屏幕上的呢?让我们继续对此进行研究。

        我们在原有代码的基础上加上一个fork函数,创建出一个子进程:

const char* fstr = "hello fwrite!\n";
const char* str = "hello write!\n";

printf("hello printf!\n");
fprintf(stdout,"hello printf!\n");
fwrite(fstr,strlen(fstr),1,stdout);

write(1,str, strlen(str));
fork();

        并对这段程序进行上面两种情况的输出:

        我们发现第二种情况非常特殊,不仅C接口的输出在write之后,其中的字符串还被打印了两遍。对比这两段代码,我们可以猜测这里的不同一定与子进程中的某个动作有关,多出的三行打印应该就是子进程造成的。而我们看到fork以后我们并没有主观上的对其做任何的操作,那么子进程之后还做了什么样的动作呢?很显然,子进程最后直接退出了,毕竟这时除了退出已经没有任何的动作。

        现在便揭晓特殊情况的原因,那就是除了缓冲区本身的刷新规则以外,当一个进程退出时,缓冲区也会进行一次刷新。(这里具体的实现可以看做C语言进程调用了退出函数exit(),而exit()由单纯执行结束进程功能的系统调用_exit()和刷新缓冲区的函数fflush()组合而成)

        这样我们便可以分析上面种种情况的成因:我们将四个输出分别编号为1~4。2号输出的成因:首先直接执行write的输出,然后当进程退出时,缓冲区刷新,由于全缓冲而暂时存在于缓冲区的数据最后被刷新到了屏幕上。4号成因,除二号相同的过程以外,子进程会复制一份于父进程相同的缓冲区,然后当最后退出时刷新到显示器上。

        至此上面的四种情况以及C语言提供的缓冲区规则已经基本分析完毕。缓冲区的规则我们大致清楚了,现在来看看它本身的一些性质。

        1.为什么要有缓冲区呢?第一个是缓冲区可以提高用户层输出的效率。当我们直接使用系统调用将数据传输到外设上时对比在内存中的读取是比较慢的,那么我们如果多次直接调用系统调用来进行输出就会造成很多的资源浪费,这就像是山上的蓄水池和山脚下的溪流一样,我们将许多将要输出的数据存起来一次性进行输出会提高输出效率。

        第二个是可以配合格式化,什么是格式化呢?我们使用C语言printf时肯定听说过格式化输出,格式化操作也就是将本身没有特殊意义的各种字符赋予不同的意义、进行不同的处理,比如字符串输出,我们就以'\0'作为输出的结尾。打比方说,我们输出一个数字,就要用%d来确定它是以整数形式进行输出的,但如果我们都直接使用系统调用接口进行输出,那也就没有什么类型差异了,输出的都是字符,这不利于我们对于数据的处理。

        2.缓冲区存在于哪里?缓冲区本质上的作用是C语言设计出来要对文件的输入输出做缓冲,那么它就不可避免的与C语言的文件结构体FIFE扯上关系。事实上,一个FIFE对应的文件的缓冲区的相关字段和维护信息就是被存储在FIFE本身中。FIFE本身就是需要占用一段空间的,当我们调用fopen打开一个文件的过程就是在语言层为我们malloc出一个空间用于存放FIFE对象并初始化对应的信息(文件fd、缓冲区字段等)。

        3.缓冲区属于用户本身,不属于操作系统。在语言层面上的所有东西都是属于用户的,缓冲区是属于用户,FIFE对象也是属于用户的。

🍀结语

        今天的博客就分享到这,谢谢大家!

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

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

相关文章

elementui表格自定义指令控制显示哪些列可以拖动

Vue.directive(tableBorder, function (el, {value}) {// value允许传字符串数字和数组el.classList.add(z_table_hasBorder)let hasStyle el.querySelector(style)if(hasStyle){hasStyle.remove()}let style document.createElement(style)let str .z_table_hasBorder .el…

Sentinel 热点规则 (ParamFlowRule)

Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。 SpringbootDubboNacos 集成 Sentinel&…

Ui自动化概念 + Web自动化测试框架介绍!

1.UI自动化测试概念:我们先明确什么是UI UI&#xff0c;即(User Interface简称UI用户界面)是系统和用户之间进行交互和信息交换的媒介 UI自动化测试: Web自动化测试和移动自动化测试都属于UI自动化测试&#xff0c;UI自动化测试就是借助自动化工具对程序UI层进行自动化的测试 …

远程文件包含演示

远程文件包含 基本介绍 受害机器 10.9.47.181 攻击者机器1 10.9.47.41 攻击者机器2 10.9.47.217 实现过程 受害者机器开启phpstudy 并且开启允许远程连接 攻击者机器1上有一个文件&#xff0c;内容是phpinfo(); 攻击者机器1提供web服务使得受害者机器能够访问到攻击者…

Linux latin1字符集转成UTF-8

latin1字符集&#xff0c;我用命令iconv转换后依旧乱码&#xff0c;但是本地用Notepad转成utf-8再入库数据&#xff0c;却是正常的 查看文件编码 vi WeakcoverReason_20231120.csv:set fileencoding使用编码转换命令&#xff0c;将latin1改成UTF-8 iconv -f latin1 -t UTF-8 W…

Android Termux安装MySQL,内网穿透实现公网远程访问

文章目录 前言1.安装MariaDB2.安装cpolar内网穿透工具3. 创建安全隧道映射mysql4. 公网远程连接5. 固定远程连接地址 前言 Android作为移动设备&#xff0c;尽管最初并非设计为服务器&#xff0c;但是随着技术的进步我们可以将Android配置为生产力工具&#xff0c;变成一个随身…

管家婆订货易在线商城任意文件上传漏洞复现

0x01 产品简介 管家婆订货易&#xff0c;帮助传统企业构建专属的订货平台&#xff0c;PC微信APP小程序h5商城5网合一&#xff0c;无缝对接线下的管家婆ERP系统&#xff0c;让用户订货更高效。支持业务员代客下单&#xff0c;支持多级推客分销&#xff0c;以客带客&#xff0c;拓…

单链表相关面试题--5.将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的

/* 解题思路&#xff1a; 此题可以先创建一个空链表&#xff0c;然后依次从两个有序链表中选取最小的进行尾插操作进行合并。 */ typedef struct ListNode Node; struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){if(l1 NULL)return l2;else if(l2 …

【监控系统】日志可视化监控体系ELK搭建

1.ELK架构是什么 ELK是ElasticsearchLogstashKibana的简称。 Elasticsearch是一个开源的分布式搜索和分析引擎&#xff0c;可以用于全文检索、结构化检索和分析&#xff0c;它构建在Lucene搜索引擎库之上&#xff0c;是当前使用较为广泛的开源搜索引擎之一。 Logstash是一个…

如何实现MATLAB与Simulink的数据交互

参考链接&#xff1a;如何实现MATLAB与Simulink的数据交互 MATLAB是一款强大的数学计算软件&#xff0c;Simulink则是一种基于模型的多域仿真平台&#xff0c;常用于工程和科学领域中的系统设计、控制设计和信号处理等方面。MATLAB和Simulink都是MathWorks公司的产品&#xff0…

软件工程第十一周

面向对象 面向对象编程&#xff08;Object-Oriented Programming, OOP&#xff09;不仅仅是一种程序设计方法&#xff0c;它更是一种深刻的软件工程开发思想。这种思想的核心在于通过抽象和封装来模拟现实世界中的对象和概念&#xff0c;以便更好地管理和解决复杂的软件工程问…

渗透测试流程是什么?7个步骤给你讲清楚!

在学习渗透测试之初&#xff0c;有必要先系统了解一下它的流程&#xff0c;静下心来阅读一下&#xff0c;树立一个全局观&#xff0c;一步一步去建设并完善自己的专业领域&#xff0c;最终实现从懵逼到牛逼的华丽转变。渗透测试是通过模拟恶意黑客的攻击方法&#xff0c;同时也…

数字引领,智慧赋能|袋鼠云与易知微共同亮相2023智慧港口大会

2023年10月19日&#xff0c;由中国港口协会、中国交通通信信息中心、天津港&#xff08;集团&#xff09;有限公司主办&#xff0c;中国港口协会智慧港口专业委员会、《港口科技》杂志社等单位承办的以“数字引领 智慧赋能”为主题的“2023智慧港口大会”在天津顺利召开。 袋鼠…

el-tree 与table表格联动

html部分 <div class"org-left"><el-input v-model"filterText" placeholder"" size"default" /><el-tree ref"treeRef" class"filter-tree" :data"treeData" :props"defaultProp…

用对了吗?正确打开文件传输助手的方式

在这个高速发展的信息时代&#xff0c;我们每天都会面临一个重要的问题&#xff1a;如何在手机和电脑之间快速、高效地传输文件&#xff1f; 有时候&#xff0c;我们需要把工作中的一份报告从电脑传到手机&#xff0c;以便在路上查看&#xff1b;有时候&#xff0c;我们又想把手…

【Flask使用】全知识md文档,4大部分60页第3篇:状态cookie和session保持

本文的主要内容&#xff1a;flask视图&路由、虚拟环境安装、路由各种定义、状态保持、cookie、session、模板基本使用、过滤器&自定义过滤器、模板代码复用&#xff1a;宏、继承/包含、模板中特有变量和函数、Flask-WTF 表单、CSRF、数据库操作、ORM、Flask-SQLAlchemy…

PC 477B西门子触摸屏维修6AV7853-0AE20-1AA0

西门子触摸屏维修故障有&#xff1a;上电黑屏, 花屏,暗屏,触摸失灵,按键损坏,电源板,高压板故障,液晶,主板坏等,内容错乱、进不了系统界面、无背光、背光暗、有背光无字符&#xff0c;上电无任何显示 &#xff0c;Power灯不亮但其他一切正常&#xff0c;双串口无法通讯 &#x…

密码加密解密之路

1.背景 做数据采集&#xff0c;客户需要把他们那边的数据库连接信息存到我们系统里&#xff0c;那我们系统就要尽可能的保证这部分数据安全&#xff0c;不被盗。 2.我的思路 1.需要加密的地方有两处&#xff0c;一个是新增的时候前端传给后端的时候&#xff0c;一个是存到数…

MySQL 之多版本并发控制 MVCC

MySQL 之多版本并发控制 MVCC 1、MVCC 中的两种读取方式1.1、快照读1.2、当前读 2、MVCC实现原理之 ReadView2.1、隐藏字段2.2、ReadView2.3、读已提交和可重复读隔离级别下&#xff0c;产生 ReadView 时机的区别 3、MVCC 解决幻读4、总结 MVCC&#xff08;多版本并发控制&…

公网环境下使用VNC远程连接Ubuntu系统桌面

文章目录 前言1. ubuntu安装VNC2. 设置vnc开机启动3. windows 安装VNC viewer连接工具4. 内网穿透4.1 安装cpolar【支持使用一键脚本命令安装】4.2 创建隧道映射4.3 测试公网远程访问 5. 配置固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址5.3 测试…