news 2026/10/23 6:10:26

深入解析TCP-RDT2.2:用UDP实现可靠数据传输的核心机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入解析TCP-RDT2.2:用UDP实现可靠数据传输的核心机制

简介:传输控制协议的可靠数据传输实验代码包,对应计算机网络课程中常用的 RDT 2.2 实验模型,适合正在学习网络原理的学生,以及希望深入理解传输层可靠性的开发人员。压缩包内共 15 个文件,核心是 4 份 Java 源码,还有编译后的字节码文件、文本格式的说明文档,以及 Eclipse 工程相关的配置信息,整体体积约 1.04MB,结构简洁明了。目前已有三百二十三人浏览学习。代码从 RDT 2.0 升级到 RDT 2.2,重点解决了确认包因网络噪声发生位错的问题,采用累计确认、超时重传和校验纠错等手段,帮助观察丢包与损坏场景下收发双方的互动。通过阅读源码和运行说明,可掌握序列号、校验和、确认号等关键概念,并理解可靠数据传输协议在实际工程中的设计思路。适合作为课程实验的参考资料,也可作为 TCP 协议进阶学习的起点。

1. TCP-RDT2.2.zip:这个压缩包到底是干什么的,值不值得打开

读到“TCP-RDT2.2.zip”这个文件名,第一反应通常是:这又是个课程实验交付包。RDT是可靠数据传输(Reliable Data Transfer)的缩写,2.2是教材里定义的协议版本,zip只是打包格式。这类包的使命很简单:让你用UDP套接字、在应用层实现一套类似TCP连接里的可靠传输机制——序号、校验和、ACK确认、超时重传,把TCP协议栈里那个“黑匣子”摊开给你看。它适合三类人:正在做计算机网络lab、卡在状态机里的学生;想把TCP连接和“UDP和TCP协议的区别”讲清楚、顺便自己手写协议栈的工程师;以及要在嵌入式或私有链路上自研可靠传输的开发者。别小看这一个小zip,把它跑通并调对,你对TCP/IP协议栈里最核心那层信任机制的理解,会比背一百遍状态图都扎实。

2. 先搞懂RDT在干嘛:为什么实验偏要拿UDP模拟TCP

2.1 从TCP连接说起:可靠传输到底可靠在哪

TCP连接提供给应用层的是一条“看起来不会丢、不会乱、不会重复”的字节流,但这条字节流背后是几个机制在同时打工:序号字段告诉接收方每个字节在流里的位置;ACK字段让接收方把“我已经收到哪一段”反馈回去;校验和用于探测数据在传输过程中是否被改坏;超时重传在ACK迟迟不来时自发重发数据,发送窗口则限制了同时有多少数据在路上。这四个机制拼起来,才有了TCP连接在不可靠网络上的可靠表象。

RDT实验就是把这些机制拆出来逐个实现。它假设底层信道是这样一个东西:分组可能被比特损坏、可能被丢失、可能乱序、可能重复,但应用层不能感知到这些,应用层只能看到一个可靠的、按序交付的字节流。这个假设和真实互联网是完全对应的——IP层只提供尽力而为的投递,不做任何可靠保证,可靠必须由通信两端自己做。

所以RDT不是一个TCP专属概念,它是传输层协议设计的抽象模型:只要你在不可靠链路上提供面向连接的数据交付,就绕不开序号和确认这套工具。这也是为什么计算机网络教材都把它放在TCP之前讲——先看明白这套机制,再看内核里TCP协议栈的状态机就不懵了。换句话说,UDP和TCP协议的区别不在于谁更快,而在于TCP多做了这四件事,RDT就是教你亲手把这四件事做出来。

2.2 RDT版本演进:从2.0到2.2,序号和ACK是怎么逼出来的

教材上的RDT版本是按信道假设从简单到复杂递进的,这个演进过程本身就是这门课最好的复习材料。

版本信道假设解决手段遗留问题
RDT1.0完全可靠,不损坏不丢失不用做任何事,直接收发现实中不存在
RDT2.0可能比特损坏数据包加校验和,接收端回ACK/NAKACK/NAK本身可能损坏
RDT2.1比特损坏 + ACK/NAK损坏数据包加0/1序号,能识别重复包NAK占一个符号,冗余
RDT2.2同上去掉NAK,重复ACK表示“没收到”信道仍然不能丢包
RDT3.0可能丢包引入超时计时器,超时重传停等协议性能低

RDT2.0阶段会遇到一个经典死锁:发送端等来一个被弄坏的NAK,它不知道接收端到底收到没有,两者互相等,直到超时才能解开。RDT2.1给数据加了一位序号,重发一个包接收端能认出来“这是重复的”,死锁就解开了。但NAK仍是一个多余的符号,RDT2.2把它砍掉,约定“接收端只回ACK,且ACK携带它期望收到的下一个序号”:发送端收到和自己刚发出去的数据序号匹配的ACK,就认为这个包被正确接收;收到不匹配的重复ACK,就当成NAK立刻重发。

这个约定之所以成立,是因为停等协议下序号只需要一位。同一时刻只有一个包在路上,接收端区分“新包还是重发包”,只需要知道它和上一个包序号是否相同,0和1交替翻转就够用。不理解的读者可以这样想:如果你发的序号是0到N的完整递增,接收端反而要维护一个更大的状态才能判断“这个包是不是迟到的旧包”,一位序号恰恰让状态机保持最小。TCP之后抛弃停等、改用字节序号,那是为了窗口流水线付出的代价,RDT2.2阶段不需要。

2.3 为什么用UDP承载:省掉内核协议栈干扰,把状态机摊开看

这里要先和新人解释一个最容易搞混的点:“TCP-RDT2.2”虽然名字里写着TCP,工程实现几乎都是用UDP socket做的。原因有两层。第一层,TCP协议栈已经写在操作系统内核里,你建一个TCP连接后,可靠传输完全由内核代劳,用户在用户态看不见“何时重传、怎么编号、校验和怎么对齐”这些决策;想观察只能抓包反推,那是把协议当黑匣子,学不到设计方法。第二层,UDP的sendto和recvfrom每次调用都对应一个独立数据报,分组边界清清楚楚,天然符合“一个分组就是一个PDU”的实验模型;TCP是字节流接口,想人为造出分组边界还得自己加切片逻辑,偏离了教学重点。

常见做法是让两个进程在本机127.0.0.1的UDP端口上通信,然后在收发之间插入一层人为的不可靠模拟——随机丢包、随机翻转字节。这层模拟就是实验包和真实UDP的差别所在:真实UDP在本地环回下几乎不丢包,你不模拟,RDT2.2里那些重传分支永远跑不到,测试就成了“只测好路、不测坏路”。我在动手前一般会先确认实验包是否自带丢包脚本,没有就自己写一个,第6章会给一个能直接抄的版本。

还要提醒一点:RDT2.2的窗口大小是1,这在链路利用率上和真实TCP连接差距巨大。真实TCP有滑动窗口、有拥塞控制、有累积确认,RDT2.2只是把这些复杂机制剥掉之后剩下的骨架。把骨架先立起来,再往里填窗口和拥塞控制的细节,这正是课程实验安排它出场的理由。

3. 拿到TCP-RDT2.2.zip怎么跑通:解压、编译、最小联调

3.1 解压与工程结构识别:先分清哪个是发送端哪个是接收端

先做一步基础操作:

mkdir -p ~/rdtlab unzip TCP-RDT2.2.zip -d ~/rdtlab cd ~/rdtlab/TCP-RDT2.2 ls -l

unzip的-d参数指定解压目标目录,避免把一堆文件直接撒在当前目录。拿到包先别急着编译,要做两件事:一是看README或实验说明里约定的启动参数;二是看文件命名。这类实验工程通常分成两份独立程序,一份是数据发送端(client/sender),一份是接收端(server/receiver),从名字就能判断哪个该先跑。zip只是打包格式,不涉及传输协议,包内的压缩算法、文件名编码才是解压阶段的主要变量。

Linux下打包的zip文件名多为UTF-8编码,Windows打包的zip可能是GBK,解压出来中文文件名乱码时,用unzip -O GBK TCP-RDT2.2.zip重新解一遍。如果你在Windows上操作,直接用资源管理器右键“解压到当前文件夹”就行,但要注意别把zip当成普通文件夹直接打开复制里面的文件,那样容易丢文件权限位,尤其是实验包里的.sh脚本和可执行文件。原zip保留着别删,后面改出问题还能有个后悔药。

3.2 编译与启动顺序:先起接收端还是先起发送端

实验包用C写很常见,编译两条命令:

gcc -O2 -Wall -o rdt_server rdt_server.c common.h -lpthread gcc -O2 -Wall -o rdt_client rdt_client.c common.h -lpthread

-O2开优化,-Wall把所有警告晾出来,common.h这类公共头文件一般定义了数据包头结构和校验和函数。如果实验包自带Makefile,直接make更快;make失败时优先检查是不是缺-lpthread或-lm,这两个库是C网络程序最容易漏的链接项。

启动顺序有一个普遍约定:先起接收端,再起发送端。原因是接收端要把socket bind到固定端口,起得太晚,发送端的第一包发过去时没人接收,就构成“第一个包丢失”。这个坑在RDT2.2里尤其致命——纯RDT2.2不处理丢包,你会看到发送端一直等ACK,永远等不到,整个程序就像卡死一样。

./rdt_server -p 8888 -o recv.bin & sleep 1 ./rdt_client -h 127.0.0.1 -p 8888 -i send.bin

启动参数里-p是UDP端口,-o是接收端落盘文件名,-i是待发送文件。我在跑批处理脚本时习惯先起server,sleep一秒再起client,给接收端一点bind时间,避免手速太快踩到上面那个坑。这里用的是UDP端口,不是TCP端口,排查占用时注意看UDP那一列。

3.3 用127.0.0.1和本机端口做冒烟测试:第一次跑该看什么

第一次跑通不建议直接跨主机,先在单机走最小闭环。127.0.0.1环回路径不经过真实网卡,链路不出物理差错,适合先验证代码逻辑和工程装配,再跑模拟丢包场景。冒烟测试看三个指标:命令是否正常退出、接收文件大小是否和源文件一致、内容是否一致。文件校验用md5:

md5sum send.bin recv.bin

两条md5一致,说明序号、校验和、ACK三套机制至少没有互相打架。不一致时先看文件大小:大小一致但md5不同,问题几乎一定出在校验和逻辑——收到的内容被改了但没拦住;大小都不一致,基本是丢包或重复交付导致文件写错位置。

补充一个实验习惯:第一次冒烟别放几十MB的大文件,放几KB的小文件,出问题时能在Wireshark里逐个包核对。跑通后再换1MB以上的大文件测,看程序在大净荷下会不会把UDP缓冲区撑爆。UDP和TCP协议区别里的缓冲区行为就在这里体现:UDP每个recvfrom只取一个数据报,发送端发太快、接收端收太慢,内核缓冲区满了就直接丢包;TCP有流控不会这么粗暴。RDT实验只做可靠传输,不做流控,所以大文件测试时你会看到重传概率明显上升。如果要在Linux上重新打包目录交作业,记住压缩命令是zip -r TCP-RDT2.2-new.zip TCP-RDT2.2/,别把外层目录一起卷进去。

4. 核心实现:RDT2.2的收发包状态机与三个关键参数

4.1 发送端状态机:等待ACK0和等待ACK1之间来回切换

RDT2.2的发送端从状态机视角看只有两个稳定状态:等待ACK0和等待ACK1。停止等待协议决定了同一时刻只有一个数据包在路上,数据包序号只需要一位,0和1交替翻转。下面用Python示意发送状态机,C工程里只是把socket收发换成阻塞的sendto/recvfrom,逻辑完全一致:

import socket, struct sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) peer = ("127.0.0.1", 8888) seq = 0 # 当前数据包序号,只有0/1 TIMEOUT = 0.2 # 200ms,环回路径足够宽松 def make_packet(seq, payload): # 包结构:1字节序号 + 2字节校验和 + 数据 check = sum(payload) & 0xFFFF return struct.pack("!BH", seq, check) + payload def parse_ack(pkt): if len(pkt) < 3: return None return pkt[0] # ACK序号 = 接收端期望收到的下一包序号 with open("send.bin", "rb") as f: while True: data = f.read(1400) if not data: break pkt = make_packet(seq, data) sock.sendto(pkt, peer) sock.settimeout(TIMEOUT) while True: try: ack, _ = sock.recvfrom(64) ack = parse_ack(ack) if ack is None: # ACK结构损坏,按未确认处理,重发当前包 sock.sendto(pkt, peer) continue if ack == 1 - seq: # 接收端期望下一个包,说明当前包已收好 seq = 1 - seq break else: # 重复ACK等价于NAK,重发当前包 sock.sendto(pkt, peer) except socket.timeout: # 实验包实际跑在UDP上会丢包,需要超时重传兜底 sock.sendto(pkt, peer)

这段代码里最关键的一行判断是ack == 1 - seq。seq=0时,接收端收到0号包后就会期望1号包,返回ACK1;发送端收到ACK1,知道0号包已经安全到达,把seq翻成1,去发下一个新包。反之收到ACK0,说明接收端还在等0号包,当前包没被正确交付,必须原样重发。刚开始做这个实验的人经常把判定写反,写成ack == seq就认为成功,结果每次收到ACK就急切推进序号,接收端还没拿到正确数据,发送端就把序号换掉了,两边永远对不上。超时分支在理论RDT2.2里并不存在,我加它是因为真实UDP信道会丢包,实验包为了在真实机器上演示通常也内置定时器,这相当于把RDT3.0的能力提前借进来用,不影响你理解2.2的序号和ACK关系。

4.2 接收端逻辑:只回带序号的ACK,用ACK告诉对端该给几号包

接收端比发送端简单,它只有一个状态:自己期望的序号expected。每收到一个包,先做校验,再判断序号是否等于期望值:相等就交付给上层并翻转expected,不等就说明是重复包或损坏包,不交付。无论哪种情况,接收端都只回一个ACK,ACK里携带它当前期望的下一个包序号:

import socket, struct sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(("127.0.0.1", 8888)) expected = 0 # 期望收到的包序号 def check_pkt(pkt): if len(pkt) < 3: return None, None pseq, check = struct.unpack("!BH", pkt[:3]) if check != sum(pkt[3:]) & 0xFFFF: return None, None return pseq, pkt[3:] def send_ack(seq, addr): sock.sendto(struct.pack("!BH", seq, 0), addr) out = open("recv.bin", "wb") while True: pkt, addr = sock.recvfrom(2048) pseq, payload = check_pkt(pkt) if pseq is None: send_ack(expected, addr) # 包损坏,回期望序号,发送端会重发 continue if pseq == expected: out.write(payload) # 顺序正确,交付到上层文件 expected = 1 - expected send_ack(expected, addr) # 重复包也回当前期望,让发送端知道没推进

这是一举两得的设计:校验失败时回ACK expected,重复包时也回ACK expected。发送端一旦收到“不是自己期待的那个ACK号”,就知道当前数据包没有被接收,立即重发。也就是说RDT2.2把所有否定反馈都编码成“重复的肯定确认”。ACK带的是期望序号,不是已收到的序号,这是它和最直觉的“收到什么回什么”做法的最大差别。

我见过不少同学在这里翻车,接收端代码里写send_ack(pseq),收到0号包回ACK0、收到1号包回ACK1,表面看没问题,但一旦出现重复0号包,接收端回ACK0,发送端以为0号包刚被确认,继续推进发新包,实际上接收端对第一个0号包已经交付过了,第二个0号包是垃圾数据,状态机整体乱掉。所以接收端回ACK时只准用expected,不准用收到包的序号。

4.3 校验和、端口与超时参数:三个值怎么设才不容易翻车

先看校验和。教材实现是16位求和取反:发送端把序号、校验和字段、数据作为一个整体按16位一组求和,把结果求反填进校验和字段;接收端把包括校验和在内的整个包再加一遍,结果应该是0xFFFF。两个细节容易出错:求和前校验和字段必须置0,否则把上一次的旧校验和也加进去,永远对不上;字节序要统一约定,Windows小端机器如果直接按本地整数求和,跨主机通信会出现“本地自测通过、两台机器不通”的怪事,一般约定网络字节序。

端口选择上,RDT实验跑在UDP端口上,挑一个1024以上的空闲端口,常见用8888这类易记值。排查占用用netstat -unlp | grep 8888,注意看UDP那一列,不要习惯性去看TCP监听——这个实验跑的是UDP,netstat里看不到“LISTEN”状态的TCP连接是正常的。

超时参数最容易被当成无关紧要的常数忽略,实际上它是重传性能的命门。本机环回测试RTT不到1ms,设200ms足够;跨主机在有交换机甚至公网链路上,如果超时小于RTT,发送端会在ACK回来之前盲目重发,造成“重传轰炸”。我一般把超时做成启动参数-t,默认200ms,跨主机调试时把实测RTT乘2再填进去。净荷大小值得给一个固定上限:以太网MTU是1500字节,IPv4头加UDP头一共28字节,应用层payload超过1472就会触发IP分片;在模拟丢包的信道上,一个分片丢了整个IP包作废,RDT重传效率会断崖下跌,所以文件读取分片大小统一限制在1400字节。

参数建议值说明
校验和16位求和取反,网络字节序本地自测通过不代表跨机通过
UDP端口1024以上,如8888netstat -unlp 排查占用
超时RTO本机200ms,跨机实测RTT×2太小重传风暴,太大效率低
分片大小≤1400字节超过1472触发IP分片,丢包模型崩溃

5. TCP-RDT2.2常见坑:5个典型翻车现场与排查

5.1 现象:发送端一直收不到ACK,程序卡在第一个包

原因:接收端还没来得及bind端口,发送端第一个数据报已经发出,这构成“第一个包丢失”。RDT2.2的教材模型不处理丢包,而真实UDP即使走环回路径,也会在时序上制造出这种丢包。解决:固定启动顺序,先server后client,client启动前sleep 0.5秒;更稳的实验包做法是让sender先发一个空握手包,等接收端回一个就绪包再发正式数据。这个坑的复现率非常高,基本十个跑RDT的人有一半第一次都卡在这。

5.2 现象:两边都能收到包,但数据交付乱序或文件写出来一半

原因:ACK编号语义不一致。发送端把ACK当“已收到的序号”,接收端把ACK当“期望收到的序号”,两边对0/1的理解正好差半拍。现象是每次ACK一回来,发送端立刻推进seq,接收端实际还没拿到新数据,文件大小始终小于预期。解决:把ACK语义写进头文件注释统一成“ACK携带期望序号”;收发两侧各打一行调试日志,打印seq、ack、expected,跑一轮就能定位是谁在反着理解。这种问题靠看代码很难看出来,打日志是最快的归因方式——调试RDT最实用的工具不是调试器,是print。

5.3 现象:文件大小一致但md5对不上,内容全是乱码

原因:校验和逻辑写错了但偶然通过。常见错法有三种:发送端把旧校验和或整个包头也卷进求和,导致接收端算出固定的“假通过”;接收端只校验数据部分不校验序号,序号被改坏时漏网;校验和字节序不一致,跨机器测试时每包必挂。解决:发送端计算前先把校验和字段填0;接收端把“序号+校验和+数据”整体重新求合并断言结果为0xFFFF;再做一个破坏性测试,写一个脚本把payload第一个字节取反再发,强制走到校验失败分支,看接收端是否拒收。这个坑的特点是本地环回往往测不出来,两台机器一联就暴露,属于标准的“玄学”问题,实际95%是字节序。

5.4 现象:程序明明在跑,netstat却看不到监听,排查半天发现方向错了

原因:想当然用看TCP连接的习惯去排查UDP实验。这个实验用UDP socket,bind后端口处于UNCONN状态,不像TCP那样有LISTEN和ESTABLISHED,习惯找“监听端口”和“established连接”的人会误判程序没起来。解决:用netstat -unlp | grep 8888,UDP监听显示为udp 0.0.0.0:8888且没有状态列,这才是正常形态。另一个相关坑:实验包名字写着TCP,新人会去Wireshark里过滤tcp.port==8888,怎么抓都抓不到包。这个实验一切过滤条件都是udp.port,不是tcp.port,方向错了一晚上白搭。

5.5 现象:发送端重传风暴,日志里全是重复发送,同一个ACK频繁出现

原因:超时设太小,ACK还在路上就被判定超时重发;或者却用了固定定时器,每次重发后没有复位,老超时不断触发。本质上是把丢包判断的敏感度调乱了。解决:先量RTT再设超时——本地抓包看一次完整往返的时间,超时至少比RTT大两倍以上;进入重传等待时,每次重发后都要重新启动定时器,而不是沿用上一次老定时器;另外确认程序有没有在收到合法ACK后正确停掉定时器,停了依旧超时才是逻辑bug。重传风暴看着吓人,但根因往往就一行代码:定时器没复位。

6. 进阶验证:用抓包和模拟器确认你的RDT2.2实现真的可靠

6.1 构造丢包和损坏场景:加一个不可靠UDP代理

光在好路上跑通不算数,验证协议能不能扛雷,得把丢包和损坏主动塞进去。我一般会在客户端和服务端之间加一个UDP代理,代理不参与状态机,只负责随机丢、随机改,二十行Python写完:

import random, socket src = ("127.0.0.1", 9101) # 客户端把目标地址改成这个端口 dst = ("127.0.0.1", 8888) # 代理再转发给真实服务端 loss, corrupt = 0.2, 0.05 # 20%丢包,5%损坏 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(src) while True: data, _ = s.recvfrom(2048) r = random.random() if r < loss: continue # 模拟丢包 if r < loss + corrupt: b = bytearray(data) b[random.randrange(2, len(b))] ^= 0xFF # 从序号/校验和之后翻一个字节 data = bytes(b) s.sendto(data, dst)

注意损坏只从第2个字节之后开始翻,这样能避开序号和校验和字段,直接攻击数据区——否则你把校验和本身改坏,接收端照样能发现,测不出“校验和被绕过”的真实行为。代理只处理单方向,要模拟双向不可靠就再起一个src/dst对调的反向代理。拉高丢包率到20%还能稳定跑完文件传输,才算验证到位。

6.2 用Wireshark抓包验证

抓包验证三件事:正常时数据包序号0/1交替;损坏或丢包发生后,发送端是否原样重发同一个序号的数据;接收端是否在重复包到来时回同一个期望ACK。过滤表达式就是udp.port == 8888。把Wireshark的时间显示格式设为相对时间,和程序的超时参数比对,确认重传间隔始终大于一个RTT、小于超时上限。如果看到重传间隔刚好等于超时值,说明每次都是超时兜底触发重传,而不是重复ACK触发,窗口该优化的信号已经出来了。

6.3 从RDT2.2往前走半步:改成流水线协议要动哪里

RDT2.2的短板是停等——发一个包必须等ACK回来才能发下一个,链路利用率极低。想改成GBN或选择重传,改动集中在四处:序号从0/1变成n位循环窗口;发送端增加一个未确认包窗口数组;接收端为乱序包加缓冲区,按序交付;定时器从单个变成按包管理。改完再用代理把丢包率分别调到5%、10%、20%,观察吞吐量变化,你就亲眼看到了可靠传输性能和窗口大小的关系。

我自己的习惯是:跑通一个RDT实验,先打日志确认状态机,再上抓包确认线序,最后才动窗口。顺序反了,出问题时你会分不清是协议设计错还是状态机错。这套方法我后来做私有传输协议时也一直在用,每一次都帮我把问题定位时间砍掉大半。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 2:26:26

完整开源一台空心杯微型飞控:从硬件设计到PID调参全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 1:28:01

Claude Code superpowers实战:可插拔技能集如何提升AI编程效率

1. superpowers到底解决了什么问题1.1 先说清楚它是什么我第一次装superpowers这个技能包&#xff0c;是在一个连续加班修bug的深夜。当时正在用Claude Code帮我改一个老项目的模块&#xff0c;代码生成速度没问题&#xff0c;但每次都要我反复强调“先看上下文、列改动方案、再…

作者头像 李华
网站建设 2026/10/24 2:26:27

Modbus协议原理与PLC实战:从接线到调试全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/19 4:52:09

十进制加减法硬件电路设计与Multisim实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/19 8:26:28

基于Java的学生在线考试系统:组卷判分与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 17:29:57

yolov5剪枝与量化一键运行:模型瘦身加速实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华