计算机网络——传输层(TCP)
传输层
在计算机网络中,传输层是将数据向上向下传输的一个重要的层面,其中传输层中有两个协议,TCP,UDP 这两个协议。
TCP
话不多说,我们直接来看协议报头。
源/目的端口号:表示数据从哪个进程来,到哪个进程去;
序号(Sequence Number)
在数据的传输中,传输报文的数据部分的每一个字节,都有一个他自己的编号。序号(Sequence Number),简称SN。
SN与SYN标志控制位的值有关,SYN值不同,SN表达不同的含义:
当SYN为1时,说明此时为连接建立阶段,这时的SN为初始序号:ISN(Intial Sequence Number),通过随机生成SN。
当SYN为0时,说明现在是数据传输阶段,第一个报文的序号是ISN+1,后面的报文的序号, 当前的报文的SN值+Tcp报文的净荷字节数(不包括Tcp报头) 。eg.如果发送端发送的报文的SN为3,他的净荷字节数为20,那么发送端发送的下一个报文的SN为20 。
在实际的数据传输中,SN的作用是当我们主机收到很多报文时,我们可以利用SN值对当前报文进行一个去重效果。
确认序号(Acknowledge Number)
对当前收到的序号进行一个确认。如果设置了一个ACK控制位,确认序号表示一个准备接受的包的序列号,注意,他的序列号指向的是准备接受的包,也就是下一个期望接受的包的序列号。
举个例子,假设发送端(如Cient)发送3个净荷为1000byte、起始SN序号为1的数据包给Server四服务端,Server每收到一个包之后需要回复一个ACK响应确认数据包给Client。ACK响应数据包的ACKNumber值,为每个Client包的为SN+包净荷,既表示Server已经确认收到的字节数,还表示期望接收到的下一个Cient发送包的SN序号。(三次握手详细图解)。
数据偏移(首部长度)
4位Tcp报头长度:表示该TCP 头部有多少个32 位bit(有多少个4 字节); 所以TCP 头部最大长度是15 * 4 = 60
标志控制位
· URG:占一位,表示紧急指针字段有效。在实际中,优先处理紧急字段,此时紧急字段指针才有用,并且指向紧急数据(应用较少,一般用来错误码,eg.在传输中,突然不要某个数据了)。
· ACK:置位ACK=1表示确认号字段有效:TCP协议规定,连接建立后所有发送的报文的ACK必须为1;当ACK=0时,表示该数据段不包含确认信息。当ACK=1时,表示该报文段包括一个对已被成功接收报文段的确认序号Acknowedgment Number,该序号同时也是下一个报文的预期序号。
· PSH:表示当前报文需要请求推(push)操作;当PSH=1时,接收方在收到数据后立即将数据交给上层,而不是直到整个缓冲中区满。(在窗口检测中,发送PSH,将消息进行交互)。
· RST:置位RST=1表示复位TCP连接;用于重置已经混乱的连接,也可用于拒绝一个无效的数据段或者拒绝一个连接请求。如果数据段被RST置了RST位,说明报文发送方有问题发生。
· SYN:在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接清求报文,对方若同意建立连接,则应在响应报文中使SYN=1和ACK=1。综合下,SYN置1,就表示这是一个连接请求或连接接受报文。
· FIN:用于在释放TCP连接时,标识发送方比特流结束,用来释放一个连接。当 FIN=1时,表明此报文的发送方的数据已经发送完毕,并要求释放连接。
窗口大小
通俗来讲,就是表示自己的接收缓冲区的剩余空间大小。用来进行流量控制。
校验和
对整个TCP报文段,即TCP头部和TCP数据进行校验和计算,接收端用于对收到的数据包进行验证。
紧急指针
它是一个偏移量,和SN序号值相加表示紧急数据最后一个字节的序号。以上内容是TCP报文首部必须的字段,也称固有字段,长度为20个字节。接下来是TCP报文的可选项和填充部分。
可选项和填充部分
可选项和填充部分的长度为4n字节(n是整数),该部分是根据需要而增加的选项。如果不足4n字节,要加填充位,使得选项长度为32(4字节)的整数倍,具体的做法是在这个字段中加入额外的零,以确保TCP头是32位(4字节)的整数倍。
Tcp三次握手
三次握手是Tcp面向连接的重要过程,和确保了数据传输的可靠性。
(1)第一次握手:CIient进入SYN SENT状态,发送一个SYN帧来主动打开传输通道,该帧的SYN标志位被设置为1,同时会带上Client分配好的SN序列号,该SN是根据时间产生的一个随机值,通常情况下每间隔4ms会加1。除此之外,SYN帧还会带一个MSS(最大报文段长度)可选项的值,表示客户端发送出去的最大数据块的长度。
(2)第二次握手:Server接受SYN帧之后,会进入SYN RCVD,同时返回SYN+ACK帧给Client,主要目的在于通知Client,Server端已经收到SYN消息,现在需要进行确认。Server端发出的SYN+ACK帧的ACK标志位被设置为1,其确认序号AN(Acknowledgment Number)值被设置为Client的SN+1,SYN+ACK帧的SYN标志位被设置为1,SN值为Server端生成的SN序号,SYN+ACK帧的MSS(最大报文长度)
(3)第三次握手:Client在收到Server的第二次握手的SYN+ACK确认帧之后,首先将自己的状态会从SYN SENT变成ESTABLISHED,表示自己方向的连接通道已经建立成功,Client可以发送数据给Server端了。然后,Client发AGK帧给Server端,该ACK帧的ACK标志位被设置为1,其确认序号AN(Acknowledqment Number)值被设置为Server端的SN序号+1,还有一种情况,Client可能会将ACK帧和一帧要发送的数据,合并到一起发送给Server端。
(4)Server端在收到Client的ACK帧之后,会从SKN RCVD状态会进入ESTABLISHED状态,至此,Server方向的通道连接建立成功,Server可以发送数据给Client,TCP的全双工连接建立完成。
如下图:

四次挥手
(1)第一次挥手:主动断开方(可以是客户端,也可以是服务器端),向对方发送一个FIN结束请求报文,此报文的FIN位被设置为1并且正确设置Sequence Number(序列号)和Acknowledgment Number(确认号)。发送完成后,主动断开方进入FIN_WAIT_1状态,这表示主动断开方没有业务数据要发送给对方,准备关闭SOCKET连接了。
(2)第二次挥手:正常情况下,在收到了主动断开方发送的FIN断开请求报文后,被动断开方会发送一个ACK响应报文,报文的Acknowledqment Number(确认号)值为断开请求报文的SN+1,该ACK确认报文的含义是:“我同意你的连接断开请求”。之后,被动断开方就进入了(CLOSE_WAIT)状态,TCP协议服务会通知高层的应用进程,对方向本地方向的连接已经关闭,对方已经没有数据要发送了,若本地还要发送数据给对方,对方依然会接受。被动断开方的==CLOSE_WAIT(关闭等待)==还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。主动断开方在收到了ACK报文后,FIN _WAIT_1转换成FIN_WAIT_2状态。
(3)第三次挥手:在发送完成ACK报文后,被动断开方还可以继续完成业务数据的发送,待剩余数据发送,或者CLOSE-WAIT(关闭等待)截止后,被动断开方会向主动断开方发送一个FIN+ACK结束响应报文,表示被动断开方的数据都发送完了,然后,被动断开方进入LAST_ACK状态。
(4)第四次挥手:主动断开方收在到FIN+ACK断开响应报文还需要进行最后的确认,向被动断开方发送一个ACK确认报文然后,自己就进入TIME_WAIT状态,等待超时后最终关闭连接。处于TIME_WAIT状态的主动断开方,在等待完成2MSL的时间后,如果期间没有收到其他报文,则证明对方已正常关闭,主动断开方的连接最终关闭。
被动断开方在收到主动断开方的最后的ACK报文以后,最终关闭了连接,自己啥也不管了。

小问
为什么建立连接是三次握手?为什么不是一次两次?
因为三次握手,C/S双方都有一次的确定的收发。并且中间两次(SYN和ACK被捎带应答了)。它也能确定通信双方是健康的。
四次挥手可以是三次挥手吗?
可以,当C发送close请求时,刚好S也发送了close请求,这种情况就和三次握手一样,同样被做捎带应答了,但是这种情况就很少。
洪水攻击
SYN洪水攻击(SYN Flood Attack) 是一种常见的 DDoS攻击(分布式拒绝服务攻击),利用 TCP协议的三次握手缺陷 耗尽服务器资源,使其无法正常响应合法用户的请求。防御需结合协议优化(如SYN Cookie)和流量过滤技术。
Tcp特性
滑动窗口
如果我们每次都是一发一接受,他的效率就会低很多,性能就会低一点。
如下图:

如果我们同时发很多数据呢,这样不就解决了效率低下的问题(其实是将多个段的等待时间重叠在一起了)。

• 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。上图的窗口大小就是4000 个字节(四个段)。
• 发送前四个段的时候, 不需要等待任何ACK, 直接发送;
• 收到第一个ACK 后, 滑动窗口向后移动, 继续发送第五个段的数据; 依次类推;
• 操作系统内核为了维护这个滑动窗口, 需要开辟发送缓冲区来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉;
• 窗口越大, 则网络的吞吐率就越高;
如下图:

快重传
在上面的传输过程中,在同时传输多个数据报文时,如果遇到某个数据包丢包了,这种情况下Tcp会怎么做呢?
Tcp首先会让确实的数据包进行一个重传。其余的同时传输的数据包也会收到,但是不会进行一个重发。
如下图:
流量控制
接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送, 就会造成丢包, 继而引起丢包重传等等一系列连锁反应.因此TCP 支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);
接收端将自己可以接收的缓冲区大小放入TCP 首部中的"窗口大小" 字段, 通过ACK 端通知发送端;表示自己能接受的数量大小。
窗口大小字段越大, 说明网络的吞吐量越高;
接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
发送端接受到这个窗口之后, 就会减慢自己的发送速度;
如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端。
发送窗口探测包被接受后,发送端会发送一个Ack其中它的一个标志位PSH也将置1,表示我需要尽快收到数据。
拥塞控制
因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据, 是很有可能引起雪上加霜的,少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞。
拥塞控制, 归根结底是TCP 协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案。
TCP 引入慢启动机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;这样也确保了一个可靠性。
拥塞控制的窗口=min(拥塞窗口大小,接收端反馈的窗口大小)。
因为他作为一个高效的传输,需要一个快速的拥塞窗口增长速度。
因为接受的能力有限,因此不能单纯使拥塞窗口变大。就需要一个阈值,来限制一下他的增长速度。让他的增长的速度减缓。
在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1;少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;当TCP 通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降;
如下图
延迟应答
如果接收数据的主机立刻返回ACK 应答, 这时候返回的窗口可能比较小。
假设接收端缓冲区为1M. 一次收到了500K 的数据; 如果立刻应答, 返回的窗口就是500K;
但实际上可能处理端处理的速度很快, 10ms 之内就把500K 数据从缓冲区消费掉了;
在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
如果接收端稍微等一会再应答, 比如等待200ms 再应答, 那么这个时候返回的窗口大小就是1M;
一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
如下图:

捎带应答
在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应用层也是"一发一收"的。意味着客户端给服务器说了"How are you", 服务器也会给客户端回一个"Fine,thank you";那么这个时候ACK 就可以搭顺风车, 和服务器回应的"Fine, thank you" 一起回给客户端。
我们的三次握手,也是采用的是捎带应答,接收端收到数据后,我们会将一个ACK+SYN一起发送给发送端。
面向字节流
创建一个TCP 的socket, 同时在内核中创建一个发送缓冲区和一个接收缓冲区;
• 调用write 时, 数据会先写入发送缓冲区中;
• 如果发送的字节数太长, 会被拆分成多个TCP 的数据包发出;
• 如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;
• 接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
• 然后应用程序可以调用read 从接收缓冲区拿数据;
如下图:
eg.
由于缓冲区的存在, TCP 程序的读和写不需要一一匹配, 例如:
• 写100 个字节数据时, 可以调用一次write 写100 个字节, 也可以调用100 次write, 每次写一个字节;
• 读100 个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100 个字节, 也可以一次read 一个字节, 重复100 次;
粘包问题
因为他是一个全双工的,并且面向字节流,就会导致它的一个发送的数据不全或者是发送的数据多了一点。
那么如何避免粘包问题呢? 归根结底就是一句话,明确两个包之间的边界。
对于定长的包, 保证每次都按固定大小读取即可; 例如上面的Request 结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可;对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是我们自己来定的, 只要保证分隔符不和正文冲突即可);
对UDP来说不会存在粘包问题,因为他每次发送的就是一个数据报格式的。UDP发送的数据要么收到,要么不收到。
小结
TCP 复杂,是因为要保证可靠性, 同时又尽可能的提高性能。
可靠性:
校验和
序列号(按序到达)
确认应答
超时重发
连接管理
流量控制
拥塞控制
提高性能:
滑动窗口
快速重传
延迟应答
捎带应答
这就是TCP的主要内容。
相关文章:
计算机网络——传输层(TCP)
传输层 在计算机网络中,传输层是将数据向上向下传输的一个重要的层面,其中传输层中有两个协议,TCP,UDP 这两个协议。 TCP 话不多说,我们直接来看协议报头。 源/目的端口号:表示数据从哪个进程来࿰…...
英伟达与通用汽车深化合作,澳特证券am broker助力科技投资
在近期的GTC大会上,英伟达CEO黄仁勋宣布英伟达将与通用汽车深化合作,共同推进AI技术在自动驾驶和智能工厂的应用。此次合作标志着自动驾驶汽车时代的加速到来,同时也展示了英伟达在AI技术领域的最新进展。 合作内容包括:…...
Visual Studio 2022静态库与动态库创建及使用完全指南
在C开发中,库(Library)是代码复用的重要方式。本教程将详细介绍如何在Visual Studio 2022中创建和使用静态库(.lib)和动态库(.dll),每种库类型都会有完整的创建步骤和实际示例。 第…...
C++中常见符合RAII思想的设计有哪些
文章目录 **一、标准库中的 RAII 类**1. **智能指针**2. **文件操作类**3. **锁管理类**4. **容器类**5. **线程管理** **二、自定义 RAII 类的常见场景**1. **数据库连接**2. **图形资源管理(如 OpenGL 纹理)**3. **网络套接字**4. **事务处理**5. **临…...
CUDA Memory Fence 函数的功能与硬件实现细节
CUDA Memory Fence 函数的功能与硬件实现细节 Memory Fence 的基本功能 CUDA中的memory fence函数用于控制内存操作的可见性顺序,确保在fence之前的内存操作对特定范围内的线程可见。主要功能包括: 排序内存操作:确保fence之前的内存操作在…...
CSS学习笔记5——渐变属性+盒子模型阶段案例
目录 通俗易懂的解释 渐变的类型 1、线性渐变 渐变过程 2、径向渐变 如何理解CSS的径向渐变,以及其渐变属性 通俗易懂的解释 渐变属性 1. 形状(Shape) 2. 大小(Size) 3. 颜色停靠点(Color Sto…...
[Java微服务架构]4_服务通信之客户端负载均衡
欢迎来到啾啾的博客🐱,一个致力于构建完善的Java程序员知识体系的博客📚,记录学习的点滴,分享工作的思考、实用的技巧,偶尔分享一些杂谈💬。 欢迎评论交流,感谢您的阅读😄…...
基于SpringBoot实现的高校实验室管理平台功能四
一、前言介绍: 1.1 项目摘要 随着信息技术的飞速发展,高校实验室的管理逐渐趋向于信息化、智能化。传统的实验室管理方式存在效率低下、资源浪费等问题,因此,利用现代技术手段对实验室进行高效管理显得尤为重要。 高校实验室作为…...
吴恩达深度学习复盘(1)神经网络与深度学习的发展
一、神经网络的起源与生物学动机 灵感来源 神经网络的最初动机源于对生物大脑的模仿。20 世纪 50 年代,科学家试图通过软件模拟神经元的工作机制(如树突接收信号、轴突传递信号),构建类似人类大脑的信息处理系统。 生物神经元的简…...
用Python实现资本资产定价模型(CAPM)
使用 Python 计算资本资产定价模型(CAPM)并获取贝塔系数(β)。 步骤 1:导入必要的库 import pandas as pd import yfinance as yf import statsmodels.api as sm import matplotlib.pyplot as plt 步骤 2࿱…...
Linux进程管理之子进程的创建(fork函数)、子进程与线程的区别、fork函数的简单使用例子、子进程的典型应用场景、父进程等待子进程结束后自己再结束
收尾 进程终止:子进程通过exit()或_exit()终止,父进程通过wait()或waitpid()等待子进程终止,并获取其退出状态。?其实可以考虑在另一篇博文中来写 fork函数讲解 fork函数概述 fork() 是 Linux 中用于创建新进程的系统调用。当…...
妙用《甄嬛传》中的选妃来记忆概率论中的乘法公式
强烈推荐最近在看的不错的B站概率论课程 《概率统计》正课,零废话,超精讲!【孔祥仁】 《概率统计》正课,零废话,超精讲!【孔祥仁】_哔哩哔哩_bilibili 其中概率论中的乘法公式,老师用了《甄嬛传…...
虚幻基础:UI
文章目录 控件蓝图可以装载其他控件蓝图可以安装其他蓝图接口 填充:相对于父组件填充水平框尺寸—填充—0.5:改变填充的尺寸填充—0.5:改变与父组件的距离 锚点:相对于父组件的控件坐标系原点,屏幕比例改变时ÿ…...
【MySQL篇】事务管理,事务的特性及深入理解隔离级别
目录 一,什么是事务 二,事务的版本支持 三,事务的提交方式 四,事务常见操作方式 五,隔离级别 1,理解隔离性 2,查看与设置隔离级别 3,读未提交(read uncommitted&a…...
项目实战-角色列表
抄上一次写过的代码: import React, { useState, useEffect } from "react"; import axios from axios; import { Button, Table, Modal } from antd; import { BarsOutlined, DeleteOutlined, ExclamationCircleOutlined } from ant-design/icons;const…...
fetch`的语法规则及常见用法
fetch() 是 JavaScript 用于发送 HTTP 请求的内置 API,功能强大,语法简洁。以下是 fetch 的语法规则及常见用法。 1. fetch 基本语法 fetch(url, options).then(response > response.json()) // 解析 JSON 响应体.then(data > console.log(data))…...
如何排查java程序的宕机和oom?如何解决宕机和oom?
排查oom 用jmap生成我们的堆空间的快照Heap Dump(堆转储文件),来分析我们的内存占用 用可视化工具,例如java中的jhat分析Heap Dump文件 ,它分析完会通过一个浏览器打开一个可视化页面展示分析结果 根据oom的类型来调…...
26_ajax
目录 了解 接口 前后端交互 一、安装服务器环境 nodejs ajax发起请求 渲染响应结果 get方式传递参数 post方式传递参数 封装ajax_上 封装ajax下 了解 清楚前后端交互就可以写一些后端代码了。小项目 现在写项目开发的时候都是前后端分离 之前都没有前端这个东西&a…...
代理模式(Proxy Pattern)实现与对比
代理模式(Proxy Pattern)实现与对比 1. 虚拟代理(Virtual Proxy) 定义:延迟加载对象,避免资源浪费。 适用场景:大文件或资源的加载(如图片、数据库连接)。 代码示例 /…...
MySQL - 数据库基础操作
SQL语句 结构化查询语言(Structured Query Language),在关系型数据库上执行数据操作、数据检索以及数据维护的标准语言。 分类 DDL 数据定义语言(Data Definition Language),定义对数据库对象(库、表、列、索引)的操作。 DML 数据操作语言(Data Manip…...
Spring Boot热部署插件
在实际开发中,我们修改某些代码或页面都需要重启应用后才能生效,如果每次都手动重启,会降低了开发效率;热部署是指当我们修改代码后,服务能自动重启加载新修改的内容,这样大大提高了我们开发的效率…...
pip install cryptacular卡住,卡在downloading阶段
笔者安装pip install cryptacular卡在downloading阶段,但不知道为何 Collecting cryptacularCreated temporary directory: /tmp/pip-unpack-qfbl8f08http://10.170.22.41:8082 "GET http://repo.huaweicloud.com/repository/pypi/packages/42/69/34d478310d6…...
AI大模型从0到1记录学习 day09
第 8 章 面向对象之类和对象 8.1 面向过程和面向对象 面向过程编程(Procedural Programming)和面向对象编程(OOP)是两种不同的编程范式,它们在软件开发中都有广泛的应用。 Python是一种混合型的语言,既支持…...
【FW】ADB指令分类速查清单
1. 设备管理 指令核心作用adb devices列出已连接设备adb reboot重启设备adb reboot bootloader进入Bootloader模式adb reboot recovery进入Recovery模式adb root获取Root权限(需设备支持)adb remount挂载系统分区为可读写 2. 应用管理 指令核心作用adb…...
Kafka中的消息是如何存储的?
大家好,我是锋哥。今天分享关于【Kafka中的消息是如何存储的?】面试题。希望对大家有帮助; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 Kafka 中,消息是通过 日志(Log) 的方式进行存储的。…...
Altium Designer——同时更改多个元素的属性(名称、网络标签、字符串标识)
右键要更改的其中一个对象,选择查找相似… 进入到筛选界面,就是选择你要多选的对象的共同特点(名字、大小等等),我这里要更改的是网络标签,所以我选择Text设置为一样。 点击应用就是应用该筛选调节&#…...
当模板方法模式遇上工厂模式:一道优雅的烹饪架构设计
当模板方法模式遇上工厂模式:一道优雅的烹饪架构设计 模式交响曲的实现模板方法模式搭建烹饪骨架(抽象类)具体菜品(子类) 工厂模式 模式协作的优势呈现扩展性演示运行时流程控制 完整代码 如果在学习 设计模式的过程中…...
c++位运算总结
在C中,位运算是对二进制位进行操作的运算,主要有以下几种: 1. 按位与( & ):两个操作数对应位都为1时,结果位才为1,否则为0。例如 3 & 5 , 3 二进制是 0000 0011…...
企业级知识库建设:自建与开源产品集成的全景解析 —— 产品经理、CTO 与 CDO 的深度对话
文章目录 一、引言二、主流产品与方案对比表三、自建方案 vs. 开源产品集成:技术路径对比3.1 自建方案3.2 开源产品集成方案 四、结论与个人观点 一、引言 在当今数据驱动的商业环境中,构建高质量的知识库已成为企业数字化转型的关键一环。本博客分别从…...
Python小练习系列 Vol.6:单词搜索(网格回溯)
🧠 Python小练习系列 Vol.6:单词搜索(网格回溯) 🔍 本期我们来挑战一道 LeetCode 上经典的网格型回溯题 —— 单词搜索,考察对 DFS 状态恢复的掌握! 🧩 一、题目描述 给定一个 m x…...




