[Linux]:文件(下)

news2024/9/20 20:24:18

img

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:Linux学习
贝蒂的主页:Betty’s blog

1. 重定向原理

在明确了文件描述符的概念及其分配规则后,我们就可以解释我们之前所说的重定向的原理。

1.1 输出重定向

输出重定向的本质就是,将我们本应该输出到一个文件的数据重定向输出到另一个文件中,即关闭对应标准输出流的文件描述符1,然后让该文件描述符重新指向新的文件,最后如果我们再对该文件描述符进行写入,本应该打印在屏幕的数据就重定向进入新文件。

画板

同样该操作我们也可以通过代码实现:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
    close(1);//关闭标准输出流
    int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
    if(fd<0)
    {
        perror("open fail:");
        return 1;
    }
    //向屏幕打印信息
    printf("hello betty!\n");
    printf("hello betty!\n");
    printf("hello betty!\n");
    printf("hello betty!\n");
    printf("hello betty!\n");
    fflush(stdout);//刷新缓冲区
    close(fd);
    return 0;
}

从上述输出我们发现,本应该向屏幕打印的数据,结果通过输出重定向打印进了log.txt文件。

其中关闭文件之前我们必须刷新缓冲区,至于为什么,我们在后面讲解缓冲区时专门讲解。

1.2 输入重定向

输入重定向的本质也是与输出重定向同理,将我们本应该输入到一个文件的数据重定向输入到另一个文件中,即关闭对应标准输出流的文件描述符0,然后让该文件描述符重新指向新的文件,最后如果我们再对该文件描述符进行读取,本应该从键盘读取的数据就重定向变为从新文件读取。

画板

同样该操作我们也可以通过代码实现:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
    close(0);//关闭标准输入流
    int fd=open("log.txt",O_RDONLY);
    if(fd<0)
    {
        perror("open fail:");
        return 1;
    }
    //向屏幕打印信息
    char buf[128]={'\0'};
	while (scanf("%s", buf) != EOF)
    {
		printf("%s\n", buf);
	}
    close(fd);
    return 0;
}

从上述输出我们发现,本应该向屏幕读取数据,结果通过输入重定向从log.txt文件读取。

1.3 追加重定向

追加重定向本质就简单的多,只需要写入文件时加入O_APPEND选项即可。

int fd=open("log.txt",O_WRONLY|O_APPEND|O_CREAT,0666);

然后我们可能疑惑的是,标准输出流与标准错误流对应的设备都是显示器,那么这两者之间有什么区别呢?

我们可以先看一下这段代码:

#include<stdio.h>
int main()
{
    printf("stdout:hello printf!\n");
    perror("stderr:hello perror!");
    fprintf(stdout,"stdout:hello fprintf!\n");
    fprintf(stderr,"stderr:hello fprintf!\n");
    return 0;
}

如果直接运行的话,肯定会全部打印。但是如果对该执行文件进行输出重定向的话,标准错误流的文件内容就不会重定向进新文件中。

这是因为输出重定向默认关闭的是1号文件描述符,并没有关闭2号文件描述符。利用这一特性,我们就可以以后将错误信息单独提前出来,打印到日志系统中。

当然如果想将标准输出与标准输出的内容输出到同一文件中,也可以使用类似的指令。

1.4 dup2函数

其中Linux操作系统也为了我们提供了专门的重定向接口——dup2函数

  • 原型:
  • 函数功能:dup2会将fd_array[oldfd]的内容拷贝到fd_array[newfd]当中,如果有必要的话我们需要先关闭文件描述符为newfd的文件。
  • 函数返回值: 如果调用成功,返回newfd,否则返回-1。

画板

使用dup2函数时,需要注意以下两点:

  • 如果oldfd不是有效的文件描述符,dup2就会调用失败,此时文件描述符为newfd的文件没有被关闭。
  • 如果oldfd是一个有效的文件描述符,但是newfdoldfd具有相同的值,则dup2不做任何操作,并返回newfd
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
    int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
    if(fd<0)
    {
        perror("open fail:");
        return 1;
    }
    close(1);
    dup2(fd,1);//进行重定向
    printf("hello printf!\n");
    fprintf(stdout,"hello fprintf!\n");
    close(fd);
    return 0;
}

2. 缓冲区

2.1 语言缓冲区

在计算机领域,缓冲区是一块存储区域。它用于暂存数据,以协调不同速度的设备或操作之间的数据传输。比如我们再来看看下面这段代码:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
    close(1);//关闭标准输出流
    int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
    if(fd<0)
    {
        perror("open fail:");
        return 1;
    }
    //向屏幕打印信息
    printf("hello betty!\n");
    printf("hello betty!\n");
    printf("hello betty!\n");
    printf("hello betty!\n");
    printf("hello betty!\n");
    close(fd);
    return 0;
}         

为什么没有打印信息呢?其实这就与我们C语言的缓冲区有关,因为缓冲区常见的刷新策略有三种:

  1. 无缓冲。
  2. 行缓冲。(常见的对显示器进行刷新数据)
  3. 全缓冲。(常见的对磁盘文件写入数据)

其中对于我们的printf函数,如果没有加\n就是全缓冲,否则就是行缓冲。

因为我们对文件进行了重定向,让本应该向屏幕打印的信息输入进一个磁盘文件,这时缓冲策略就从行缓冲变成了全缓冲,全缓冲需要程序结束之后才会向磁盘刷新文件内容,但是在此之前文件我们已经调用close接口关闭了对于的文件描述符,此时程序结束后就无法找到对应的文件,自然也不会对文件进行任何写入。所以一般为了解决这个问题,我们可以使用fflush函数提前刷新缓冲区。

由于我们使用的printf是C语言提供的接口,所以这个缓冲区也是C语言提供的,其被包含在名为File的结构体中,不光是缓冲区,文件描述符fd也被包含在其中。这也是为什么C语言的文件接口需要返回File*的原因。

//在/usr/include/libio.h
struct _IO_FILE {
 int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
 //缓冲区相关
 /* The following pointers correspond to the C++ streambuf protocol. */
 /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
 char* _IO_read_ptr; /* Current read pointer */
 char* _IO_read_end; /* End of get area. */
 char* _IO_read_base; /* Start of putback+get area. */
 char* _IO_write_base; /* Start of put area. */
 char* _IO_write_ptr; /* Current put pointer. */
 char* _IO_write_end; /* End of put area. */
 char* _IO_buf_base; /* Start of reserve area. */
 char* _IO_buf_end; /* End of reserve area. */
 /* The following fields are used to support backing up and undo. */
 char *_IO_save_base; /* Pointer to start of non-current get area. */
 char *_IO_backup_base; /* Pointer to first valid character of backup area */
 char *_IO_save_end; /* Pointer to end of non-current get area. */
 struct _IO_marker *_markers;
 struct _IO_FILE *_chain;
 int _fileno; //!!!!!!!!!!!!!!!!!!封装的文件描述符!!!!!!!!!!!!!!!!!
#if 0
 int _blksize;
#else
 int _flags2;
#endif
 _IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
 /* 1+column number of pbase(); 0 is unknown. */
 unsigned short _cur_column;
 signed char _vtable_offset;
 char _shortbuf[1];
 /* char* _save_gptr; char* _save_egptr; */
 _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

2.2 系统缓冲区

不仅是我们语言方面存在缓冲区,我们操作系统内部也会存在一个缓冲区,我们一般称为内核缓冲区。同样语言缓冲区刷新到系统缓冲区也遵循三种刷新策略:

  1. 无缓冲。
  2. 行缓冲。(常见的对显示器进行刷新数据)
  3. 全缓冲。(常见的对磁盘文件写入数据)

所以说我们使用语言所提供的接口如printf对文件进行写入数据,首先会将数据存放在语言缓冲区,然后根据不同的刷新规则再刷新到系统缓冲区中,最后才会将系统缓冲区的数据刷新到磁盘或者对应的外设之中。

画板

比如说我们看看下面这段代码:

#include <stdio.h>
#include <unistd.h>
int main()
{
	//c
	printf("hello printf\n");
	fputs("hello fputs\n", stdout);
	//system
	write(1, "hello write\n", 12);
	fork();
	return 0;
}

为什么重定向之后的内容会与之前截然不同呢?

这是因为我们执行可执行程序,打印到屏幕,默认是行缓冲,所以直接打印所以数据。但是如果我们对数据进行重定向的话,向磁盘写入数据,默认为全缓冲,此时数据都会存在语言缓冲区中。而此时我们创建子进程,父子进程之间代码数据共享,进程结束之后对语言缓冲区进行刷新,本质就是对数据进行修改,为了进程之间的独立性,就会发生写实拷贝,所以重定向之后C语言接口的数据打印会答应两份。而因为系统接口write写入的数据是直接写入系统缓冲区的,不需要发生写实拷贝,所以只打印一份。

3. 理解文件系统

前面我们谈论的文件都是加载进内存的内存文件,而接下来我们就来谈谈磁盘文件。

3.1 磁盘

磁盘是一种永久性存储介质,在计算机中,磁盘几乎是唯一的机械设备。与磁盘相对应的就是内存,但是内存是掉电易失存储介质,所以目前所有的普通文件都是在磁盘中存储的。

了解磁盘,我们首先需要了解一下几个常见的基本概念。

如果我们需要确定磁盘的哪个特定的区域,只需要找到对应的扇区,磁道,柱面即可。

3.2 磁盘分区

磁盘通常被称为块设备,一般以扇区为单位,一个扇区的大小通常为512字节。为了方便描述,我们可以将磁盘抽象为线性存储。

画板

在计算机中,为实现高效磁盘管理,常进行分区操作。分区编辑器可在磁盘上划分出多个逻辑部分,如 Windows下常见的C盘和D盘。分区越多,文件管理越精细,不同性质文件可存储于不同分区。这样能更好地组织和管理文件,提升系统运行效率,便于用户快速找到所需文件,优化计算机使用体验。

Linux操作系统中,我们也可以通过指令ll /dev/vda*查看我们磁盘的分区信息。

3.3 磁盘格式化

磁盘格式化是在磁盘完成分区后进行的一项重要操作。从本质上来说,它是对磁盘中的分区进行初始化,旨在为数据存储和管理建立一个规范的基础结构。

其中,初始化会写入相应的管理信息,这些管理信息是由文件系统决定的,不同的文件系统格式化时写入的管理信息是不同的,常见的文件系统有EXT2EXT3XFSNTFS等。

需要强调的是,磁盘格式化具有一定的风险性。在进行这一操作时,通常会导致现有的磁盘或分区中所有的文件被完全清除,且这种清除往往是不可逆的。因此,在决定进行磁盘格式化之前,必须谨慎考虑并确保已对重要文件进行了妥善的备份。

3.4 inode

Linux操作系统中,文件的元信息和内容是分离存储的,其中保存元信息的结构称之为inode,因为系统当中可能存在大量的文件,所以我们需要给每个文件的属性集起一个唯一的编号,即inode编号。

其中我们可以通过指令ls -i,显示当前目录下各文件的inode编号。

Linux下,文件是通过innode标识的,所以在系统层面文件名,后缀都是没有意义的。

3.5 EXT2文件系统的存储方案

对于每一个分区来说,分区的头部会包括一个启动块(Boot Block),对于该分区的其余区域,EXT2文件系统会根据分区的大小将其划分为一个个的块组(Block Group)。其中启动块的大小是确定的,而块组的大小是由格式化的时候确定的,并且不可以更改。

画板

并且,每个组块都有着相同的组成结构,每个组块都由超级块(Super Block)、块组描述符表(Group Descriptor Table)、块位图(Block Bitmap)、inode位图(inode Bitmap)、inode表(inode Table)以及数据表(Data Block)组成。

画板

  • Super Block: 存放文件系统本身的结构信息。记录的信息主要有:Data Blockinode的总量、未使用的Data Blockinode的数量、一个Data Blocksinode的大小、最近一次挂载的时间、等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了。
  • Group Descriptor Table: 块组描述符表,描述该分区当中块组的属性信息。
  • Block Bitmap: 块位图当中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用。
  • inode Bitmapinode位图当中记录着每个inode是否可用。
  • inode Table: 存放文件属性,即每个文件的inode
  • Data Blocks: 存放文件内容。

值得注意的是:

  1. 其他块组当中可能会存在冗余的Super Block,当某一Super Block被破坏后可以通过其他Super Block进行恢复。
  2. 磁盘分区并格式化后,每个分区的inode个数就确定了。

一个文件使用的数据块和inode结构的对应关系,是通过一个数组进行维护的,该数组一般可以存储15个元素,其中前12个元素分别对应该文件使用的12个数据块,剩余的三个元素分别是一级索引、二级索引和三级索引,当该文件使用数据块的个数超过12个时,可以用这三个索引进行数据块扩充。

通过上面的学习,我们就可以回答一下几个问题。

  1. 如何理解创建一个空文件?
  1. 遍历inode Bitmap,找到比特位为0的位置,申请一个未被使用的inode
  2. inode表中找到对应的inode, 并将文件的属性信息填到inode结构当中。
  3. 将该文件的文件名和inode指针添加到目录文件的数据块当中。
  1. 如何理解向文件写入信息?
  1. 通过文件的inode编号找到对应的inode结构。
  2. 通过inode结构找到存储该文件内容的数据块,并将数据写入数据块。
  3. 若不存在数据块或者申请的数据块已经写满了,就需要遍历block Bitmap找到一个空的块号,并在数据区当中找到对应的空闲块,再把数据写入到数据块当中,最后还需要建立数据块和inode结构的对应关系。
  1. 删除文件做了些什么?
  1. 将该文件对应的inodeInode map当中设置为无效。
  2. 将该文件申请过的Data BlockBlock map当中置为无效。

这也是我们为什么删除一个软件的速度比下载同一个软件的速度快的多的原因。当然因为文件内容并没有被删除,所以我们可以在对应内容被其他文件内容覆盖之前,通过一些技术手段复原已删除文件。

  1. 如何理解目录?

目录也是一种文件,是文件就有对应的文件属性与文件内容,其中对应的文件属性就是我们的inode存储的就是目录的大小,目录的拥有者等。而对应的文件内容存储的就是该目录下的文件名以及对应文件的inode指针。

4. 软硬链接

4.1 软链接

软链接又叫做符号链接,软链接文件是一个独立的文件,该文件有自己的inode号,但是该文件只包含了源文件的路径名,所以软链接文件一般就是对应路径文件的一种快捷访问方式。其中在Windows系统中,我们桌面上软件图标就是访问对应程序的快捷方式,本质其实就是一个软连接文件。

Linux中,我们可以通过指令ln -s 文件名 软链接名设置软连接:

并且我们也能通过指令unlink 软连接名取消对应的软连接,并且如果一旦删除软连接所指向的文件,那么该软连接文件也将毫无意义。

4.2 硬链接

硬链接文件就是源文件的一个别名,它与源文件之间具有相同的inode,大小。一旦为某个文件建立硬链接,那么对应的硬链接数就会加一。

硬链接没有独立的inode,并不是一个独立的文件, 本质是在特定的目录下,添加一个文件名和inode编号的映射关系。

Linux中,我们可以通过指令ln 文件名 硬链接名建立对应的硬链接,同样我们也能通过unlink 硬链接名取消对应的硬链接。

但是我们还可以提出一个疑惑就是,我们的普通文件的硬链接数为1,但是目录的硬链接数为什么不为1呢?

因为我们当前目录下还存在一个隐藏文件.指向我们的当前目录,这个.文件其实就是我们的目录的硬件链接文件。

5. 文件时间-ACM

Linux中,我们可以使用指令stat 文件名来查看对应文件的信息。

以下是对应关于文件时间的信息。

  • Access: 文件最后被访问的时间。
  • Modify: 文件内容最后的修改时间。
  • Change: 文件属性最后的修改时间。

当我们修改文件内容时,文件的大小一般也会随之改变,所以一般情况下Modify的改变会影响Change一起改变,但修改文件属性一般不会影响到文件内容,所以一般情况下Change的改变不会影响Modify的改变。

我们若是想将文件的这三个时间都更新到最新状态,可以使用指令touch 文件名

6. Linux下一切皆文件

最后我们学习完文件相关的知识后,再来谈一谈linux下一切皆文件这一观点。

Linux系统中,“一切皆文件”是一个重要的设计理念。这一理念的实现涉及到多个层面的技术和机制。

首先,外设与内存进行交互,像键盘、显示器等外设都有诸如readwrite等读写方法。但由于各种外设的硬件结构不同,这些方法在底层实现是不一样的,并且都是在硬件的驱动层完成。

那么,Linux是如何做到“一切皆文件”的呢?首先Linux引入了软件的虚拟层 VFS(虚拟文件系统)。VFS会统一维护每一个文件的结构体struct file,这个结构体包含了一批函数指针。这些函数指针能够直接指向底层的方法。在上层,我们可以以统一的struct_file的方式去看待文件。因此,“一切皆文件”指的是在 VFS层面上的看待方式,而非在驱动层。

画板

这种实现方式与C++中的多态类似。在 C++中,父类指针指向谁,调用的就是谁的方法。在 C 语言中,可以通过函数指针做到指向不同的对象时执行不同的方法,实现多态的性质。在Linux中,每个struct file中包含很多函数指针,这样在struct file上层看来,所有的文件都是调用统一的接口,而在底层则通过函数指针指向不同硬件的方法,实现与具体硬件对应的逻辑。

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

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

相关文章

C程序设计(7.0安徽专升本函数)

在之前我很多代码执行都是放在函数里的&#xff0c;这样方便我管理和演示&#xff0c;现在能和大家更好的去了解函数了 考纲教材关于这个的理论知识太多了&#xff0c;且废话占大多数&#xff0c;甚至有些对于小白很晦涩难懂或容易搞混&#xff01;所以在这我就尽量缩减理论知…

C语言——希尔排序

希尔排序是对于插入排序的一种优化 代码&#xff1a; #include <stdio.h> #include <stdlib.h> void shell_sort(int* p, int len) { int i; int j; int step; int tmp; for (step len / 2; step > 0; step step / 2) { fo…

手把手教你实现微信小程序定位

实现小程序的定位 框架&#xff1a;uniappvue 1&#xff0c;用户授权配置 在pages.json文件中配置 "pages": [{"path": "pages/home/index","style": {"navigationBarTitleText": "首页","navigationBarBac…

监控-zabbix

1运维监控 是指对计算机系统、网络、服务器等关键IT基础设施进行实时监控&#xff0c;以确保系统的稳定运行和及时发现潜在问题 2老监控框架&#xff08;不会用但需要知道&#xff09; Cacti&#xff1a; Cacti是一款基于PHP、MySQL开发的网络流量监测图形分析工具。主要监…

CSP-J算法基础 计数排序

文章目录 前言计数排序计数排序的过程总结 代码实现计数排序总结 前言 计数排序 计数排序&#xff08;Counting Sort&#xff09;是一种线性时间复杂度的排序算法&#xff0c;适用于范围有限的整数排序。它通过计数每个值出现的次数&#xff0c;依次排列这些值。该算法不通过比…

LVGL 控件之线条(lv_line)

目录 一、概述二、线条1、设置连接点2、自适应大小3、翻转 y 轴4、样式4.1 设置宽度4.2 末端形态 4.3 虚线5、API 函数 一、概述 线条部件只有一个组成部分&#xff1a;主体 LV_PART_MAIN。 通过一组点绘制出相连的直线&#xff0c;通过 lv_line_create 创建相应的对象。 二…

利用深度学习实现验证码识别-4-ResNet18+imagecaptcha

在当今的数字化世界中&#xff0c;验证码&#xff08;CAPTCHA&#xff09;是保护网站免受自动化攻击的重要工具。然而&#xff0c;对于用户来说&#xff0c;验证码有时可能会成为一种烦恼。为了解决这个问题&#xff0c;我们可以利用深度学习技术来自动识别验证码&#xff0c;从…

5.第二阶段x86游戏实战2-认识内存

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

Cesium 展示——Cesium 初始化视角在中国并加载数据(china.json)

文章目录 需求一:初始化视角在中国分析需求二:加载中国数据(china.json)需求一:初始化视角在中国 在初始化 Cesium 的 Viewer 后,视角是在美国,如何让其视角指向中国 分析 viewer.value = new Cesium.Viewer(cesiumContainer.value, {homeButton

redis基本数据结构-string

文章目录 1. redis的string数据结构2. 常见的业务场景2.1 缓存功能案例讲解背景优势解决方案代码实现 2.2 计数器案例讲解背景优势解决方案代码实现 2.3 分布式锁案例讲解背景优势解决方案代码实现 2.4 限流案例讲解背景优势解决方案代码实现 2.5 共享session案例讲解背景优势解…

Docker突然解封,直接拉取!

文章目录 Docker突然解封&#xff0c;直接拉取&#xff01;封禁的原因是什么&#xff1f;解封的原因是什么&#xff1f;封禁对开发的影响经验教训 最近开始公众号文章也开始同步更新了&#xff0c;对Java、大数据、人工智能、开发运维相关技术分享&#xff0c;文章对您有用的话…

Linux 8250串口控制器

1 8250串口类型的识别 Intel HW都使用DesignWare 8250&#xff1a; drivers/mfd/intel-lpss-pci.c drivers/tty/serial/8250/8250_dw.c IIR寄存器的高2位bit7、bit6用来识别8250串口的类型&#xff1a; 0 - 8250&#xff0c;无FIFO 0 - 并且存在SCR&#xff08;Scratch registe…

SQL优化(二)统计信息

收集统计信息 数据库的统计信息非常重要&#xff0c;如果没有正确地收集表的统计信息&#xff0c;或者没有及时地更新表的统计信息&#xff0c;SQL就有可能走错执行计划&#xff0c;也就会出现性能问题。 统计信息主要分为表的统计信息、列的统计信息、索引的统计信息、系统的…

TeamTalk数据库代理服务器

文章目录 main函数主流程关键步骤线程池redis缓存未读消息计数未读消息计数-单聊未读消息计数-群聊 群成员管理 main函数主流程 关键步骤 初始化epoll 线程池数据入口 reactor CProxyConn::HandlePduBuf异步task任务封装&#xff0c;把任务放入线程池&#xff1b;线程池里的…

【AI学习】AI科普:专有名词介绍

这里是阿川的博客&#xff0c;祝您变得更强 ✨ 个人主页&#xff1a;在线OJ的阿川 &#x1f496;文章专栏&#xff1a;AI入门到进阶 &#x1f30f;代码仓库&#xff1a; 写在开头 现在您看到的是我的结论或想法&#xff0c;但在这背后凝结了大量的思考、经验和讨论 目录 1.AI序…

TCP通信三次握手、四次挥手

前言 前面我说到了&#xff0c;UDP通信的实现&#xff0c;但我们经常说UDP通信不可靠&#xff0c;是因为他只会接收和发送&#xff0c;并不会去验证对方收到没有&#xff0c;那么我们说TCP通信可靠&#xff0c;就是因为他会进行验证接收端是否能够接收和发送&#xff0c;并且只…

给大家推荐好用的AI网站

地址&#xff1a;https://ai.ashuiai.com/auth/register?inviteCode8E8DIC1QCR 个人觉得挺好用的&#xff0c;可以免费&#xff0c;免费有限制次数&#xff0c;也可以会员升级200永久免费&#xff0c;我用的200永久免费。 可以在国内使用以下ai模型 回答问题更智能&#xff…

计算机毕业设计 校内跑腿业务系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

分享6个我喜欢的常用网站,来看看有没有你感兴趣的!

分享6个我自己很喜欢的常用网站&#xff0c;平时工作生活中经常会打开&#xff0c;来看看有没有你感兴趣的&#xff01; 1.Crazygames crazygames.com/ 一个超赞的在线小游戏平台&#xff0c;上面有超过7000种游戏任你选。不管你喜欢冒险、解谜、闯关&#xff0c;还是装扮、赛…

概要设计例题

答案&#xff1a;A 知识点&#xff1a; 概要设计 设计软件系统的总体结构&#xff1a;采用某种方法&#xff0c;将一个复杂的系统按照功能划分成模块&#xff1b;确定每个模块的功能&#xff1b;确定模块之间的调用关系&#xff1b;确定模块之间的接口&#xff0c;即模块之间…