【Linux】Linux下的基础IO

news2024/11/14 20:27:08

❤️前言

        大家好!今天这篇博客和大家聊一聊关于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对象也是属于用户的。

🍀结语

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

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

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

相关文章

移动端获取ua头的值

目录&#xff1a; 1、检测地址2、测试结果 1、检测地址 http://www.ip138.com/useragent/ 2、测试结果 成功的拿到了ua头的值了&#xff0c;亲测可行&#xff01;&#xff01;&#xff01;

红海营销时代,内容占位的出海品牌更有机会营销占位

#01 品牌出海&#xff1a;内容占位就是品牌营销占位 红海营销时代&#xff0c;内容信息充斥着用户周边。无论线上还是线下&#xff0c;生活工作、休闲娱乐等不同场景内&#xff0c;广告信息均无孔不入。对于用户来说&#xff0c;能记住的品牌或者商品往往寥寥无几。 占位营销…

Mysql超详细安装配置教程(保姆级)

目录 一、下载Mysql 二、安装Mysql 三、配置Mysql 四、连接Mysql 五、部分疑难问题 一、下载Mysql 从官网下载MySQL&#xff0c;这里我选用的是Mysql8.0.34版本 二、安装Mysql 下载完成后直接双击进行安装&#xff0c;打开后的页面如下所示&#xff1a; “Developer Defa…

二叉树题目:二叉树的最近公共祖先

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;二叉树的最近公共祖先 出处&#xff1a;236. 二叉树的最近公共祖先 难度 5 级 题目描述 要求 给定一个二叉树&…

【LeetCode刷题】--9.回文数

9.回文数 class Solution {public boolean isPalindrome(int x) {if(x < 0){return false;}int tmp x, sum 0;boolean flag false;while(x ! 0){sum sum * 10 x % 10;x / 10;}if(sum tmp){flag true;}return flag;} }

动画师如何选择全身动捕设备制作动画?

随着行业的不断发展&#xff0c;全身动捕设备在动画制作行业的应用已十分普遍。全身动捕设备分为光学动捕设备和惯性动捕设备&#xff0c;动画师该如何选择全身动捕设备制作动画&#xff1f; 与光学动捕设备相比&#xff0c;惯性动捕设备对场地和空间的限制更小&#xff0c;不…

vulnhub靶机Presidential

靶机地址&#xff1a;https://download.vulnhub.com/presidential/Presidential.ova 主机发现 arp-scan -l 端口扫描 nmap --min-rate 10000 192.168.21.150 端口服务扫描 nmap -sV -sT -O -p80 192.168.21.150 漏洞扫描 nmap --scriptvuln -p80 192.168.21.150 只有一个端…

NodeMCU ESP8266构建Web Server网页端控制设备

NodeMCU ESP8266构建Web Server网页端控制设备 前言 NodeMCU ESP8266 内部集成了TCP/IP协议栈&#xff0c;可以快速构建网络功能&#xff0c;搭建联网应用的硬件平台&#xff1b; ESP8266可以作为WiFi接入点&#xff08;Station&#xff09;&#xff0c;这样可以方便连接互联…

如何修改dpi为300?96dpi怎么改成300dpi?

平时使用的图片dpi一般都是96&#xff0c;但是我们在打印的时候&#xff0c;都要求dpi为300以上&#xff0c;这时候就需要修改图片分辨率&#xff0c;如何改图片分辨率成了一个问题&#xff0c;所以今天就教大家一个图片分辨率提高在线处理的方法&#xff0c;一起来了解一下吧。…

右键菜单和弹出菜单的区别

接触windows开发10年了&#xff0c;一直以为"右键菜单"和"弹出菜单"是不同的。 最近刚刚发现&#xff0c;这两种菜单在定义的时候和消息循环处理程序中并没有什么不同&#xff0c;区别只是在于windows底层显示方式。 如下是右键菜单的显示方式&#xff1…

【图像分类】【深度学习】【Pytorch版本】GoogLeNet(InceptionV4)模型算法详解

【图像分类】【深度学习】【Pytorch版本】GoogLeNet(InceptionV4)模型算法详解 文章目录 【图像分类】【深度学习】【Pytorch版本】GoogLeNet(InceptionV4)模型算法详解前言GoogLeNet(InceptionV4)讲解Stem结构Inception-A结构Inception- B结构Inception-C结构redution-A结构re…

4-1三个整数的最大数

#include<stdio.h> int main(){int a,b,c,t,max;printf("请输入三个数&#xff1a;");scanf("%d,%d,%d",&a,&b,&c);t(a>b)?a:b;max(t>c)?t:c;printf("输出三个数中最大是数字是&#xff1a;%d",max);return 0;}

单链表相关面试题--3.给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点

/* 解题思路&#xff1a; 通过快慢指针找到中间节点&#xff0c;快指针每次走两步&#xff0c;慢指针每次走一步&#xff0c;当快指针走到结尾的时候&#xff0c;慢指针正好走到中间位置 */ typedef struct ListNode Node; struct ListNode* middleNode(struct ListNode* head)…

全面解读数据资产入表风口,与企业有什么关系和影响

亿信华辰『数据资产入表实务系列直播』第一期成功举办。我们邀请到了亿信华辰高级顾问专家、湖北省软件企业协会数据要素发展工作委员会专家委员刘晗为大家带来数据入表知识体系分享&#xff0c;全程干货&#xff0c;深度解读&#xff0c;以下是演讲上半部分直播内容。 一、数据…

Netty源码学习4——服务端是处理新连接的netty的reactor模式

零丶引入 在前面的源码学习中&#xff0c;梳理了服务端的启动&#xff0c;以及NioEventLoop事件循环的工作流程&#xff0c;并了解了Netty处理网络io重要的Channel &#xff0c;ChannelHandler&#xff0c;ChannelPipeline。 这一篇将学习服务端是如何构建新的连接。 一丶网络包…

​使用SPDK lib搭建自己的NVMe-oF Target应用​

一. 背景 NVMe是一种抽象的传输协议层&#xff0c;旨在提供可靠的NVMe命令和数据传输。NVMe over Fabric (NVMe-oF) 实现了NVMe标准在PCIe总线上的扩展&#xff0c;可以支持数据中心的网络存储&#xff0c;它支持多种传输方式包括FC&#xff0c;RDMA和TCP。NVMe-oF服务(应用) 是…

【已解决】移动号码在移动网上营业厅更换为8元保号套餐

有很多人的副卡基本是为了接收银行卡短信&#xff0c;平时基本不打电话和用流量&#xff0c;每个月固定消费在18-30左右&#xff0c;很浪费。今天发现在网上营业厅就可以修改8元保号套餐&#xff0c;分享给大家。 保号套餐 有以下两种&#xff1a; 解决办法&#xff1a; 1、…

redis非关系型数据库

redis非关系型数据库&#xff0c;缓存型数据库 关系型数据库和非关系型数据库的区别 关系型数据库 关系型数据库是一个结构化的数据库。 记录方式&#xff1a;行和列 行的作用&#xff1a;记录对象的属性 列的作用&#xff1a;声明对象 表与表之间是有关联的&#xff1a…

LR学习笔记——初识lightroom

文章目录 介绍图库界面修改照片界面 介绍 Lightroom是Adobe公司开发的一款用于图片后期处理制作的软件&#xff0c;被称为Adobe Photoshop Lightroom。其增强的校正工具、强大的组织功能以及灵活的打印选项可以帮助加快图片后期处理速度&#xff0c;将更多的时间投入拍摄。 相…