《Redis 设计与实现》读书概要

注:

  1. 《Redis 设计与实现》一书基于 Redis 2.9 版本编写,部分内容已过时,过时之处本文会有所说明。
  2. 本文为读书笔记,部分简单和日常使用较少的知识点未记录。
  3. 原书网页版地址 https://redisbook.com/

一、底层数据结构

  1. SDS(Simple Dynamic String):简单动态字符串,优化了的C语言字符串的实现,最显著的特征是内部保存了字符串长度len和可用长度free,且字符数组buf和C语言一样,结尾为\0字符,因此可复用C语言的字符串函数。

  2. 链表:Redis中用到的链表是双向无环链表,是实现列表键List的数据结构(仅当一个列表键包含了数量比较多的元素,或者列表中包含的元素都是比较长的字符串时),此外发布与订阅、慢查询、监视器等功能也用到了链表。链表节点listNode与普遍的链表节点相同,包含prev、next和value,但是redis又将listNode包装在了list中,记录了head、tail、节点数len及实现多态链表的功能性函数。

  3. 哈希表(dictht):实现字典(dict)数据类型的底层数据结构,同时,字典也是redis数据库数据存储的数据结构。dict的内部包含两个dictht,便于当 load factor 达到一定值时进行rehash 操作。rehash 不是一次性的,而是渐进式的。

  4. 跳跃表(skiplist):是实现有序集合zset的数据结构。和原本跳跃表只通过节点连接的实现不同,redis的跳跃表节点包装在一个zskiplist类型中,其包括zskiplistNode类型的header、tail、表示表中节点数的length和最大层数的level组成。zskiplistNode不仅包含sds类型的元素值对象、节点分值score,还包含一个zskiplistLevel类型的数组level和一个指向前一个节点的后退指针backward,level数组的长度是节点的层高,在创建节点时确定,是根据幂次定律随机生成的一个介于1-32之间的数。zskiplistLevel表示节点中的每个层级,包括一个指向其后面其他节点的指针forward和当前层的跨度span。

  5. 整数集合(intset):是集合对象的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,redis就会使用intset作为集合键的底层实现。当有比当前集合中最大的值更大一级的元素需要保存时,intset可以通过元素类型的升级来改变元素存储的类型,这可以尽量节约内存。但intset不会降级。

  6. 压缩列表(ziplist):仅用在7.0版本之前,是一种为节约内存而开发的用一块连续的内存空间来紧凑地保存数据的顺序型数据结构(内存紧凑型数据结构),被用作列表、哈希和有序集合的底层实现之一。压缩列表节点previous_entry_length、encoding和content构成,content可以保存字节数组或整数值,保存的值类型由encoding决定,previous_entry_length可以用于向前回溯任意节点,达到从表尾遍历表头的目的。但是压缩列表存在“连锁更新”的问题,7.0版本之后由listpack取代。

  7. listpack
    5.0版本中引入,7.0版本之后完全替换ziplist成为列表、哈希和有序集合的底层数据结构之一。listpack 的每个节点不再保存前一个节点的长度从而避免“连锁更新”问题。

二、对象系统

使用TYPE命令查看值对象的数据类型:

SET、RPUSH、HSET、SADD、ZADD
使用OBJECT ENCODING 命令可查看值对象的底层数据结构:

字符串对象

编码可以是int、raw 和 embstr
int:整数值且该值可以用long类型来表示,对其进行字符串相关操作时,其会被转为raw编码,例如 APPEND 操作
embstr:字符串值且其长度小于或等于32,一次分配内存,能更好地利用缓存,但是embstr为只读字符串,不可修改,APPEND 一个embstr会被转为raw编码
raw:字符串且其长度大于32,两次分配内存
浮点数被当作字符串保存,但在需要的时候还是会转回浮点数
字符串对象操作命令:
在这里插入图片描述

列表对象

编码可以是 ziplist/listpack 或 linkedlist
ziplistlistpack:默认情况,所有字符串长度都小于64字节,或保存的元素数量小于512个,可修改配置
linkedlist:除ziplist的情况
当ziplist编码的任一条件不满足时,列表会被转为linkedlist编码。
列表对象操作命令:
在这里插入图片描述

哈希对象

编码可以是 skiplist/listpack 或 hashtable,编码转换条件和列表对象相同。
哈希对象操作命令:
在这里插入图片描述

集合对象

存疑
编码可以是 intset/listpack/hashtable。
intset:集合中所有元素都是整数且元素个数小于512时
listpack/hashtable:除了intset的情况。hashtable 实现集合的方式是将键值对的值置为NULL。
集合对象操作命令:在这里插入图片描述

有序集合对象

编码可以是ziplist/listpack 或 skiplist + dict
ziplist/listpack:默认配置下,集合元素小于128或集合成员长度小于或等于64字节
skiplist:除 ziplist/listpack 情况下
有序集合对象操作命令:
在这里插入图片描述

三、持久化

有两种,RDB 和 AOF,如果服务器开启了AOF持久化,那么会优先使用AOF文件来还原数据库状态,否则使用RDB文件来还原数据库状态。

RDB

直接以键值对的形式保存数据库数据信息。
有两个命令可以用于生成RDB文件,SAVEBGSAVE。SAVE 会阻塞服务器进程,而 BGSAVE 会创建子进程执行,不会阻塞。

BGSAVE命令可以自动间隔性执行,以达到自动持久化的目的,其执行间隔由save配置的secondschanges两个值确定。代码为:

if(sever.dirty >= saveparam.changes && save_interval > saveparam.seconds) {
    BGSAVE();
}

即,服务器状态在 seconds 秒内发生了 changes 次修改时,自动执行 BGSAVE。

AOF

AOF 通过保存Redis执行的最新写命令来记录数据库数据信息。
appendonly:是否开启AOF,默认为 no
appendfsync:aof_buf 缓冲同步策略,有 always、everysec、no三个值
AOF重写(名字有歧义):(aof_rewrite函数)以最新的数据库状态直接生成一个新的AOF文件代替原有AOF文件,避免原有文件过大。由于AOF重写是在子进程和服务器主进程同时工作的,因此会出现AOF重写子进程状态滞后的问题,为此,在AOF缓冲区的基础上引入AOF重写缓冲区,将AOF重写之后的命令同步追加到这两个缓冲区,在AOF重写完成后再将AOF重写缓冲区的内容写入到AOF文件中,再将AOF文件重命名为特定文件名,完成新旧文件的替换。

四、事件

redis服务器是一个 事件驱动 程序,服务器处理的事件分为文件事件和时间事件。

文件事件

文件事件处理器是基于 Reactor模式 实现的IO多路复用网络通信程序

时间事件

分为定时和周期性事件,一般只执行serverCron()周期性事件
文件事件和时间事件的调度和执行都是由ae.c/aeMain中调用aeProcessEvents函数负责,先遍历执行文件事件列表,再遍历时间事件链表处理时间事件。

五、客户端和服务端

客户端

redisClient/client 对象保存在redisServer的 list* clients 链表中。
客户端有输入缓冲区和输出缓冲区,都有大小限制(可配置)。

服务端

服务端接收客户端传入的命令并保存在客户端输入缓冲区querybuf中,然后解析命令,提取出 argv 和 argc (client对象中),通过argv[0]获取命令,在命令表字典中查找对应的redisCommand,并将客户端结构的cmd指向该redisCommand,此后再进行一系列权限、参数、状态等检查,最后再调用命令对应的实现函数redisCommandProc(client)即可。执行完后将结果保存在客户端输出缓冲区buf中,经过客户端套接字关联的回复处理器处理后返回给客户端。此外,执行完实现函数后,服务器还需要做一些后续工作以使服务端状态完整。
服务端的serverCron函数定时处理很多更新数据库状态的操作。

  1. 服务器初始化
  2. 初始化服务器状态结构redisServer server:执行redis.c/initServerConfig函数(变量赋初值,生成命令表)
  3. 载入配置项:执行loadServerConfig函数(加载配置参数和配置文件,用其覆盖server属性)
  4. 初始化数据结构:执行initServer函数(创建相应数据结构并赋初值,设置一些操作)
  5. 还原数据库状态:载入RDB或AOF文件
  6. 执行事件循环

六、主从复制

5.0.0 版本之后用REPLICAOF代替SLAVEOF命令创建redis从服务器,且如果主服务器设置了密码,则在从服务器的 masterauth 配置中也要配置主服务器的密码,否则无法同步。
从服务器同步主服务器的状态分为两种情况,一是初始化时的全量复制同步,另一种是初始同步后的增量同步。2.8版本之前的初始全量同步使用SYNC命令(从向主发送SYNC命令),但SYNC命令比较耗时,2.8版本之后使用PSYNC命令,以达到在主从断开重连后的部分重同步需要。增量同步通过命令传播的方式实现,即从服务器(作为服务端)也执行一次主服务器(作为客户端)发送来的写命令。PSYNC的部分重同步是通过在主服务器端维护一个复制积压缓冲区(replication backlog)实现的,复制积压缓冲区是一个固定长度FIFO的队列,记录最近传播的命令和命令的每个字节的偏移量。从服务器重连到主服务器后,会通过PSYNC命令告诉自己目前的复制偏移量,主服务器会根据这个偏移量之后字节是否在缓冲区中来决定执行全量重同步(full resync)还是从偏移量之后开始部分重同步(partial resync)。复制积压缓冲区的大小由repl-backlog-size配置。此外,还有一种情况会执行全量重同步,即主服务器的ID发生变化(主服务器重启),当从服务器重连主服务器后,主服务器发现从服务器发送的主服务器ID和自己当前ID不一致,则执行全量重同步,否则认为可以执行部分重同步(依然视从服务器的偏移量决定)。

七、Sentinel 模式

Sentinel,哨兵,可以用来监视主从服务器,在系统出现异常时进行重新选主以保障系统继续运行。Sentinel 会根据配置文件为每个要监视的主服务器创建相应的实例,并创建连接主服务器的命令连接和订阅连接;
Sentinel也会创建主服务器的从服务器的实例结构,通过向主服务器发送INFO命令获取从服务器信息来实现,同样,也会创建连接到从服务器的命令连接个订阅连接。
此外,监视同一主服务器的sentinel之间会以每秒一次的频率向被监视服务器的__sentinel__:hello 频道发送自身信息,这样每个Sentinel 也会知道其他 Sentinel 的存在,并为对方创建相应实例结构和命令连接(没有订阅连接)。
Sentinel 会以每秒一次的频率向实例(主、从、其他sentinel)发送PING命令并根据实例的回复判断实例是否在线,如果在指定时长连续收到无效回复则该实例被判断为主观下线(OD),然后再向同样监视该服务器的其他sentinel询问是否同意该实例进入主观下线状态,如果收集到一定数量(quorum)的主观下线投票,则判断该实例为客观下线(SD),并发起一次针对主服务器的故障转移(failover)。
故障转移的第一步就是选举领头 Sentinel,由它来完成故障转移工作。选举领头 Sentinel 的方法是对Raft算法的实现,总结就是“快者为主”。

八、集群模式

Redis集群是Redis提供的分布式数据库方案,集群通过分片(sharding)来进行数据共享,并提供复制和故障转移功能。

创建集群

  • 首先,打开集群模式配置,即将配置文件中的cluster-enabled修改为 yes
  • 指定集群节点文件,即设置配置文件中的cluster-config-file为指定文件,例如nodes.conf。如果是同一个节点中在同一个Redis目录下启动伪集群,且需要为每个配置文件都单独指定。例如在 6379 和 6380 端口创建伪集群节点,则 6379 的配置可以为cluster-config-file nodes-6379.conf,6380 的配置文件为cluster-config-file nodes-6380.conf
  • 启动所有集群节点
  • CLUSTER MEET命令添加节点,添加后可用CLUSTER NODES命令查看
  • 分配槽。添加完节点后,集群仍处于下线状态(可用CLUSTER INFO命令查看状态,如果cluster_state:fail则为下线状态,如果为cluster_state:ok则为正常状态),要使用CLUSTER ADDSLOTSCLUSTER ADDSLOTSRANGE命令为每个节点分配槽(槽号范围为 0~16383 共16384个),只有所有的节点都分配完16384个槽后,集群才会处于上线。
  • 分配完后用CLUSTER INFO命令查看,如果出现cluster_state:ok,则表示集群已上线。

分配哈希槽

CLUSTER KEYSLOT key命令可查看 key 所在的槽号。例如,查看 name 会分配到哪个槽可用CLUSTER KEYSLOT命令,例如查看键为 name 的槽号,命令为CLUSTER KEYSLOT name

添加槽可用CLUSTER ADDSLOTS命令,删除槽可用CLUSTER DELSLOTS命令。7.0 版本之后可以用CLUSTER ADDSLOTSRANGE命令按范围分配槽,用CLUSTER DELSLOTSRANGE可以按范围移除槽。

运行命令

运行集群模式命令时,最好以集群参数进入客户端,即加上-c参数:

redis-cli -c ...

这样,当操作的键的槽号不在当前节点时,会自动转向键所在槽的结点执行。

修改集群

CLUSTER RESET // 重置集群
CLUSTER FORGET node-id // 将节点从集群中移除

故障转移

集群中的节点分为主节点(master)和从节点(slave),其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点故障转移,继续处理命令请求。

在从节点上执行CLUSTER REPLICATE <node_id>命令,可以让当前从节点复制指定的主节点。因为节点的复制功能和单机Redis服务器的复制功能使用了相同的代码,所以让从节点复制主节点相当于向从节点发送命令SLAVEOFREPLICAOF)。

当一个从节点发现自己正在复制的主节点进入了已下线状态时,从节点将开始对下线主节点进行故障转移。故障转移前会从下线的主节点的从节点中先选举出新的主节点,选举算法和选举领头 Sentinel 一样,都基于 Raft 算法。选举出新的主节点后,新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己,并向集群中其他节点广播一条PONG命令,让其他节点知道其已经接管原来下线的节点称为新的主节点。

九、发布与订阅

Redis的发布与订阅功能与 MQ 消息的生产与消费类似,其消息的订阅分为频道订阅和模式订阅。

  • 频道订阅
    频道订阅频道名固定,其信息保存在redisServer.pubsub_channels中,使用时由SUBSCRIBE channel命令指定,例如客户端 client1 订阅频道为 hello 的频道:
    > SUBSCRIBE hello
    运行该命令后,命令行将会进入阻塞,等待接收其他客户端向该频道发送的消息。发送消息用PUBLISH message命令,例如,客户端 client2 向该频道发送 “My name is Redis”:
    > PUBLISH "Hello, My name is Redis"
    则 client1 的窗口会立即接收到该消息。

  • 模式订阅
    模式订阅即按正则表达式模式匹配频道名,其信息保存在redisServer.pubsub_patterns中,使用时由PSUBSCRIBE命令指定,例如客户端 client1 订阅模式为go[h|th]ere的频道:
    > PUBSCRIBE go[h|th]ere
    则表示,该客户端同时订阅了名为goheregothere的频道,有其他客户端向其中任一频道发送消息,client1 都会接收到。

取消订阅用UNSUBSCRIBEPUNSUBSCRIBE命令,用法同上。

如果想查看发布订阅相关的信息,可以用PUBSUB命令的三个子命令
PUBSUB CHANNELS:查看当前被订阅的频道
PUBSUB NUMSUB channel:查看频道 channel 的订阅者数
PUBSUB NUMPAT:查看订阅模式数

十、事务

Redis的事务和传统的关系型数据库事务的最大区别在于,Redis不支持事务回滚机制(rollback),即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。

MULTI命令开始事务,然后输入一个事务的多条命令,这些命令的执行将会按顺序保存在一个队列中(QQUEUED),在最后用EXEC命令提交事务。
在这里插入图片描述

WATCH命令是一个乐观锁(optimistic locking),它可以在EXEC命令执行之前,监视任意数量的数据库键,并在EXEC命令执行时,检查被监视的键是否至少有一个已经被修改过了,如果是的话,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复。
Redis事务实现了 ACID 特性。

十一、Lua 脚本

Lua 脚本函数化执行,即 Redis 会将输入的 Lua 脚本转换为函数调用在 Lua 环境中执行。
可以使用EVAL命令执行 Lua 脚本。

// 执行 PING 命令
EVAL "return redis.call('PING')" 0
// 执行 SET 命令
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 "msg" "hello world"

SCRIPT LOADSCRIPT EXISTSSCRIPT FLUSH以及SCRIPT KILL命令可用于管理 Lua 脚本执行。

参考

《Redis 设计与实现》

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

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

相关文章

微信小程序固定头部-CSS实现

效果图 代码逻辑&#xff1a;设置头部的高度&#xff0c;浮动固定后&#xff0c;再加个这个高度的大小的外边距 .weui-navigation-bar {position: fixed;top: 0px;left: 0px;right: 0px;height:90px; } .weui-navigation-bar_bottom{height:90px; }

【React架构 - Scheduler中的MessageChannel】

前序 我们都知道JS代码是在浏览器5个进程(下面有介绍)中渲染进程中的Js引擎线程执行的&#xff0c;其他还有GUI渲染线程、定时器线程等&#xff0c;而页面的布局和绘制是在GUI线程中完成的&#xff0c;这些线程之间是互斥的&#xff0c;所以在执行Js的同时会阻塞页面的渲染绘制…

仿牛客网项目---社区首页的开发实现

从今天开始我们来写一个新项目&#xff0c;这个项目是一个完整的校园论坛的项目。主要功能模块&#xff1a;用户登录注册&#xff0c;帖子发布和热帖排行&#xff0c;点赞关注&#xff0c;发送私信&#xff0c;消息通知&#xff0c;社区搜索等。这篇文章我们先试着写一下用户的…

数据可视化之旅:电商销售看板的制作与心得

作为一名电商工作者&#xff0c;每天都需要与海量的销售数据打交道。在数据海洋中&#xff0c;如何快速准确地找到关键信息&#xff0c;优化销售策略&#xff0c;是摆在我面前的一大挑战。在了解市面上众多可视化产品后&#xff0c;我选择尝试了山海鲸可视化软件&#xff0c;这…

2024年小程序云开发CMS内容管理无法使用,无法同步内容模型到云开发数据库的解决方案,回退老版本CMS内容管理的最新方法

一&#xff0c;问题描述 最近越来越多的同学找石头哥&#xff0c;说cms用不了&#xff0c;其实是小程序官方最近又搞大动作了&#xff0c;偷偷的升级的云开发cms&#xff08;内容管理&#xff09;以下都称cms&#xff0c;不升级不要紧&#xff0c;这一升级&#xff0c;就导致我…

【架构之路】糟糕程序员的20个坏习惯,切记要改掉

文章目录 强烈推荐前言&#xff1a;坏习惯:总结&#xff1a;强烈推荐专栏集锦写在最后 强烈推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站:人工智能 前言&#xff1a; 优秀的程序员…

matlab实现不同窗滤波器示例

1 汉明窗低通滤波器 &#xff1a; 在Matlab中使用汉明窗设计低通滤波器可以通过fir1函数实现。汉明窗通常用于设计滤波器&#xff0c;可以提供更突出的频率特性。 下面是一个示例代码&#xff0c;演示如何在Matlab中使用汉明窗设计低通滤波器&#xff1a; % 定义滤波器参数 fs …

Scrapy与分布式开发:框架原生去重机制源码解析与不足分析

框架原生去重机制源码解析与不足分析 导语 在网络爬虫和数据采集领域,去重机制是一个至关重要的环节。随着互联网的迅速发展,数据量呈爆炸式增长,如何在海量数据中高效地筛选出有价值且唯一的信息,成为了一个亟待解决的问题。去重机制正是为了解决这一问题而诞生的。 Sc…

docker中hyperf项目配置虚拟域名

在使用hyperf框架时&#xff0c;直接用了docker环境进行开发 下载镜像运行容器 docker run --name hyperf -v /data/project:/data/project -p 9501:9501 -itd -w /data/project --privileged -u root --entrypoint /bin/sh 镜像ID配置docker-compose.yml version: "3.…

东崎仪表案例-中国新能源汽车产业全面崛起

以下部分数据信息来源&#xff1a;澎湃新闻 1月9日&#xff0c;韩国研究机构SNE Research公布了全球动力电池市场的新一轮统计数据。2023年1—11月&#xff0c;全球登记的电动汽车&#xff08;EV、PHEV、HEV&#xff09;电池装车量约为624.4GWh&#xff0c;比2022年同期增长41.…

Qt SQLite的创建和使用

重点&#xff1a; 1.SQLite创建数据库内容方法 链接&#xff1a;SQLite Expert Personal的简单使用-CSDN博客 2.和数据库进行链接方法 QSqlDatabase DB; //数据库连接bool MainWindow::openDatabase(QString aFile) {DBQSqlDatabase::addDatabase("QSQLITE"); /…

高刷显示器 - HKC VG253KM

&#x1f525;&#x1f525; 今天来给大家揭秘一款电竞神器 - HKC VG253KM 高刷电竞显示器&#xff01;这款显示器可是有着雄鹰展翅般的设计灵感&#xff0c;背后的大鹏展翅鹰翼图腾让人过目难忘。那么&#xff0c;这款显示器到底有哪些过人之处呢&#xff1f;一起来看看吧&…

vue中使用prettier

前言&#xff1a;prettier是一款有态度的代码格式化工具&#xff0c;它可以集成在IDE中&#xff0c;如VS Code、Web Storm等&#xff0c;也可以安装到我们开发的项目里面。本文主要讲解在Vue中集成prettier的过程&#xff0c;可以便于代码检测和格式化。 prettier官网 从官网的…

使用MyBatisPlus实现向数据库中存储List类型的数据

使用MyBatisPlus实现向数据库中存储List类型的数据 问题描述 建表时&#xff0c;表中的这五个字段为json类型 但是在入库的时候既不能写入数据&#xff0c;也不能查询出数据。 解决方案&#xff1a; 1.首先明确&#xff0c;数据存入的时候是经过了数据类型转化&#xff0c…

ElementUI修改el-tab-pane自定义动态添加class并修改组件样式

参考&#xff1a;ElementUI修改el-tab-pane自定义添加class并修改组件样式_el-tab-pane更换样式-CSDN博客 需求&#xff1a;tab 列表 动态添加class 标识当前版本 1&#xff1a;在调用列表接口的接口里面 初始化调用handleClick()方法 2&#xff1a;tab 点击时 再调用一下…

Mysql索引3--索引优化规则

目录 1、索引失效场景 1、1、不遵循最左前缀法则 &#xff0c;导致索引失效 1、2、范围查询 &#xff0c;导致失效 1、3 索引列进行运算&#xff0c;导致失效 ​1、4字符串不加引号&#xff0c;到账失效 1、5头部模糊匹配&#xff0c;导致失效 1、6 or连接条件只有一个有…

10分钟SkyWalking与SpringBoot融合并整合到Linux中

1.依赖配置 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.2.0.RELEASE</version></dependency><dependency><groupId>org.springframe…

CV论文--2024.2.29

1、ShapeLLM: Universal 3D Object Understanding for Embodied Interaction 中文标题&#xff1a;ShapeLLM: 用于具身交互的通用三维物体理解 简介&#xff1a;这篇论文介绍了ShapeLLM&#xff0c;它是专为具体交互设计的首个三维多模态大语言模型&#xff08;LLM&#xff09…

Galaxy基础教程:从列表集合中提取元素标识符

如何从一个列表集合中提取元素标识符&#xff1f; 解决方案 网站&#xff1a;UseGalaxy.CN 工具&#xff1a; Extract element identifiers of a list collection (Galaxy Version 0.0.2) Dataset collection *: 列表集合 讨论 该工具接受一个列表类型的集合作为输入&#xff0…

11.以太网交换机工作原理

目录 一、以太网协议二、以太网交换机原理三、交换机常见问题思考四、同网段数据通信全过程五、跨网段数据通信全过程六、关键知识七、调试命令 前言&#xff1a;在网络中传输数据时需要遵循一些标准&#xff0c;以太网协议定义了数据帧在以太网上的传输标准&#xff0c;了解以…