news 2026/10/18 1:46:57

TCP Tahoe与Reno拥塞控制仿真实验深度解析:原理、参数与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TCP Tahoe与Reno拥塞控制仿真实验深度解析:原理、参数与避坑指南

这个实验我前后做了两遍。第一遍照着实验指导书把仿真脚本敲完,跑出来两条曲线,乍一看都是锯齿,我还以为脚本写错了——TCP Tahoe与Reno的拥塞窗口变化看起来实在“太像了”。后来把参数逐个捋了一遍,又把trace文件里每一次丢包、每一段重传都翻出来对照,才真正看出门道。Tahoe与Reno的对比,表面上是“两条拥塞窗口曲线”的差异,本质上是两种设计哲学的交锋:一个信奉“丢了就归零重来”,一个信奉“丢了就减半,然后快速补上”。这篇文章不打算复述教材,而是把这组实验从原理、参数设计、观测口径到常见翻车点完整拆开,给正在做或者准备做这个实验的同学一份可以直接对着操作的参考。

这个实验适合谁?如果你是计算机网络课上刚接触到拥塞控制的学生,这篇文章能帮你把Tahoe和Reno的每个状态切换落到具体的数值和时间线上;如果你已经在跑仿真但结果“看不出区别”,那后半部分的参数排查和统计口径问题,大概率能解决你的困惑。

1. 从拥塞崩溃到窗口锯齿:为什么这两个老算法至今还是必修课

1.1 一个路口类比:没有拥塞控制的网络会自我毁灭

把网络想象成城市道路,发送方是源源不断涌出的车流,路由器是路口,路由器的缓冲队列是路口的等待区。假设没有红绿灯,每辆车都觉得自己“再挤一下就能过”,结果所有路口都被堵死,谁也别想走。

网络里有一个专门的名词叫“拥塞崩溃”(congestion collapse):当丢包发生后,发送方收不到确认(ACK),本能反应是重传,重传又加剧拥塞,拥塞又导致更多丢包,系统吞吐量反而急剧下降。1988年Jacobson提出TCP拥塞控制时,核心目标就是打破这个死循环,让发送方学会“测一测网络的承受能力,然后克制地使用它”。

实现方式就是在接收窗口(rwnd)之外,再加一个发送方自己维护的拥塞窗口(cwnd)。真正允许发送的未确认数据量是:

min(cwnd, rwnd)

rwnd解决的是“接收方能吞下多少”的问题,cwnd解决的是“网络路径上能同时容纳多少”的问题。这个实验里观察的核心变量就是cwnd——它在时间轴上画出来的锯齿形曲线,就是TCP“试探-触顶-收缩-再试探”的直观记录。

1.2 四个机制一张表:Tahoe和Reno各自的“武器库”

很多人对Tahoe有个误解,以为Tahoe只有慢启动和拥塞避免,没有快速重传。实际上1990年发布的Tahoe已经包含快速重传,它缺的是另一个关键机制:快速恢复。Reno只是在Tahoe的四个机制里补上了最后一块拼图。

机制核心作用TahoeReno
慢启动(Slow Start)连接建立或超时后,指数级探测可用带宽有有
拥塞避免(Congestion Avoidance)到达ssthresh后,线性增长,温和试探有有
快速重传(Fast Retransmit)收到3个重复ACK立即重传,不等超时有有
快速恢复(Fast Recovery)快速重传后窗口只减半,不归零无有

一张表就能看出Reno的创新点:它没有增加任何“新机制”,只是改变了丢包后的窗口恢复策略。这也是为什么这个实验适合作为拥塞控制的第一课——两个算法差异极小、边界清晰,正好用来理解“加法增大、乘法减小”(AIMD)的每一个细节。

2. 实验环境与参数设计:仿真器选择、拓扑和三个决定成败的参数

2.1 为什么必须用仿真器:真实内核里根本找不到“纯Tahoe”

第一次做这个实验我第一反应是:能不能直接在Linux里跑?答案是不行。现代Linux内核默认的拥塞控制算法是CUBIC,Reno虽然可以通过sysctl net.ipv4.tcp_congestion_control=reno切换,但内核的丢包恢复路径已经加了SACK、PRR(Proportional Rate Reduction)等一系列新机制,早已不是教科书上那个纯Reno。至于Tahoe,现代内核里压根不提供。

所以这个实验适合用离散事件仿真器来做。老牌的NS2里,Agent/TCP默认就是Tahoe行为,Agent/TCP/Reno才是加了快速恢复的Reno,两个类放在一起正好做干净的A/B对照。NS3也内置了TcpTahoe和TcpReno模型,但多数课程实验指导书仍以NS2为主,下面我按NS2的写法展开。用仿真器还有个额外好处:可以随时把cwnd、ssthresh的内部值打点输出,这在真实内核里是做不到的。

2.2 哑铃拓扑与瓶颈参数:带宽、时延、缓冲区怎么定

实验最常用的拓扑是哑铃结构:发送方n0接到路由器n1,n1与n2之间是瓶颈链路,n2再接接收方n3。所有值得观察的行为都发生在瓶颈链路n1-n2上。

参数设计我给出一个经过验证的起点,照抄基本不会出大问题:

  • 瓶颈链路带宽:1 Mbps
  • 瓶颈链路单向时延:50 ms(RTT约100 ms)
  • 报文段大小:1000字节
  • 瓶颈队列长度:10个报文(DropTail)
  • 可选随机丢包:ErrorModel,丢包率1%~3%

这里有一个必须先算清楚的值:带宽时延积(BDP)。带宽1 Mbps、RTT 100 ms,每秒能传输的字节数是1,000,000/8=125,000字节,一个RTT内能容纳125,000×0.1=12,500字节,除以1000字节的报文大小,大约12.5个报文。这意味着光填满管道就需要约13个在途报文。

队列缓冲的选择直接影响实验成败。10个报文的缓冲意味着:cwnd可以涨到BDP(约13)加上缓冲(10),也就是20以上才会发生队列溢出丢包。如果缓冲设得太大,比如100,拥塞窗口根本触不到上限,整个实验看不到丢包;如果设得太小,比如1到2,几乎所有丢包都是队尾丢包,触发的全是超时重传。这两种极端都会让Tahoe和Reno的曲线“长得差不多”,这一点后面专门讲。

2.3 必须开启的三个观测点:cwnd、ssthresh与丢包类型

仿真跑出来只是第一步,关键是怎么“看”。我建议至少做三件事:

第一,把拥塞窗口随时间的变化打点出来。NS2里用$ns trace-var cwnd_ $tcp,这条命令会在变量值变化时把“时间 数值”输出到trace文件,画图时直接就能用。

第二,把ssthresh的变化也打点出来。ssthresh是慢启动与拥塞避免的阈值,也是丢包后“减半”的基准值。只看cwnd会漏掉一个重要信息:丢包后这个算法的“目标水位”变成了多少。

第三,统计丢包事件的类型。trace文件里要能把超时重传和快速重传区分开,这是后续对比分析的核心口径。至于怎么区分,我在第6节专门讲。

set ns [new Simulator] set f [open out.tr w] $ns trace-all $f # 节点与链路 set n0 [$ns node] set n1 [$ns node] set n2 [$ns node] set n3 [$ns node] $ns duplex-link $n0 $n1 100Mb 5ms DropTail $ns duplex-link $n1 $n2 1Mb 50ms DropTail $ns duplex-link $n2 $n3 100Mb 5ms DropTail $ns queue-limit $n1 $n2 10 # TCP代理:切换下面两行就能对比Tahoe与Reno set tcp [new Agent/TCP] ;# Tahoe(默认) # set tcp [new Agent/TCP/Reno] ;# Reno $tcp set packetSize_ 1000 $tcp set window_ 10000 ;# 关键!避免接收窗口封顶 $ns attach-agent $n0 $tcp set sink [new Agent/TCPSink] $ns attach-agent $n3 $sink $ns connect $tcp $sink # 打点cwnd与ssthresh $ns trace-var cwnd_ $tcp $ns trace-var ssthresh_ $tcp set ftp [new Application/FTP] $ftp attach-agent $tcp $ns at 0.0 "$ftp start" $ns at 30.0 "stop" $ns run

2.4 为什么必须把发送窗口调大:一个容易被忽略的默认值

上面脚本里有一行$tcp set window_ 10000,这行看起来不起眼,实际是整个实验里最容易踩的坑。NS2里TCP代理的默认window_一般只有20个报文段左右,它充当的是“发送方最大窗口”的角色。cwnd算得再大,真正在途的数据量也不能超过min(cwnd, window_)。

可以算一下:BDP约13个报文,缓冲10个报文,理论峰值在20多。如果发送窗口被默认值20卡住,cwnd涨到20就再也上不去了,峰值附近会出现一段平台期,两条曲线的锯齿差异被严重压缩。把window_设到10000,等于把rwnd这个约束彻底放开,让实验纯粹观察拥塞控制本身的行为。

3. Tahoe的完整运行时间线:丢包即归零的代价与逻辑

3.1 Tahoe状态机:慢启动与拥塞避免的切换条件

Tahoe的工作过程就是一个切换过程。连接建立时ssthresh被设成一个很大的初值(NS2里常见的是65535字节,约65个报文),cwnd从1个报文开始。每收到一个ACK,cwnd加1,结果就是每个RTT内窗口翻倍:1、2、4、8……

当cwnd达到ssthresh后,进入拥塞避免:每收到一个ACK,cwnd增加1/cwnd,折算下来每个RTT约加1。这是一条刻意设计的线性上升段,目的是缓慢试探而不是猛冲。

接下来是丢包。Tahoe对丢包的响应一律是:(ssthresh = max(2, cwnd/2)),然后(cwnd = 1),从头开始慢启动。注意这里没有“例外”——不管是超时触发还是快速重传触发,结果都是“归零重来”。这就是Tahoe最鲜明的性格:极度保守,绝不冒险。

3.2 3个重复ACK触发快速重传后发生了什么

举一个具体的数值例子。假设某一时刻cwnd=20,第3000号报文在瓶颈队列里被丢弃。接收方之后每收到一个乱序报文,都会回一个ACK=2000的重复确认。当发送方累计收到3个重复ACK(也就是第4个确认号相同的ACK)时,触发快速重传——立即重发丢失的第3000号报文,不用干等超时。

Tahoe在这个节点上的后续动作是:ssthresh从20减到10,cwnd压回1,然后重新开始慢启动。重传报文发出后,接下来的RTT里cwnd逐步变成1、2、4、8,到达ssthresh=10后切换成线性增长,之后每个RTT加1。

这组数字的杀伤力在于:从cwnd=1涨回10,需要log2(10)≈4个RTT,这段时间窗口始终远低于链路容量,链路实际上是半空闲的。而且慢启动的指数增长还有个副作用——它涨得很快,在重试过程中如果又遇到随机丢包,可能再次触发一次归零,雪上加霜。

3.3 超时场景与指数退避:为什么RTO重传是最贵的丢包

不是所有丢包都能凑齐3个重复ACK。最典型的场景是“队尾丢包”:如果丢失的正好是窗口里靠后的报文,后面在途的报文不足以产生3个重复ACK,发送方只能等待重传超时(RTO)。

RTO不是一个精确值,它必须大于RTT并且留出抖动余量,经典实现里初始RTO通常取1到3秒,而且连续超时还会指数退避(RTO翻倍)。在一个RTT只有100ms的实验里,一次超时的代价相当于白白空等10到30个RTT。等到超时发生,ssthresh照例减半,cwnd归1,还要再经历一轮慢启动。

在统计意义上看,一次超时事件的花费远高于一次快速重传事件。Tahoe的快速重传虽然比超时好,但重传之后依然要“从零爬坡”,所以Tahoe对任何丢包都表现出一种“一朝被蛇咬,十年怕井绳”的姿态。这也是为什么在同样的丢包率下,Tahoe的链路利用率总是明显偏低。

4. Reno到底改了什么:快速恢复的窗口膨胀与多丢包困境

4.1 窗口膨胀的数学:+3从哪里来,后面每个重复ACK为什么再+1

Reno的快速恢复一开始看起来像个魔法:收到3个重复ACK后,它不是把cwnd压回1,而是把ssthresh减半之后,再给cwnd加上3。这3个报文是哪里来的?

理解这个问题的关键是“管道守恒”。每个重复ACK都意味着有一个乱序报文已经成功到达接收方——换句话说,有一个报文已经离开了网络链路,空出了一个“管道槽位”。当收到第3个重复ACK时,已经有3个报文离开了管道,发送方就可以名正言顺地再注入3个新报文,总在途量不增不减,不会破坏网络的报文守恒。

进入快速恢复后,每再收到一个重复ACK,就再有一个报文离开管道,所以cwnd再加1,然后可以发送一个新报文。这套机制叫“窗口膨胀”:窗口数值在恢复期间被虚撑起来,本质上是借管道里腾出的槽位继续维持发送节奏。当收到一个确认新数据的ACK(也就是覆盖了被重传报文的ACK)时,膨胀的窗口就要收缩回ssthresh,进入正常的拥塞避免。

这个“收缩”动作很重要,它防止恢复结束瞬间突然爆发大量报文。Reno的整个恢复过程是配合ACK时钟走的,温和、克制、不打断发送节奏。

4.2 一次快速恢复的RTT级时间线

还是用cwnd=20的初始状态对比。假设第3000号报文丢失,收到3个重复ACK时,两条算法的分叉开始了:

事件Tahoe的动作Reno的动作
收到第3个重复ACKssthresh=10,cwnd=1,重传丢失报文后重新慢启动ssthresh=10,cwnd=10+3=13,继续发送新报文
后续每个重复ACK无特殊处理,窗口不变,只能等cwnd再加1,可再发送一个新报文
收到覆盖丢包的ACK若cwnd已达ssthresh则进入拥塞避免cwnd收缩到10,进入拥塞避免
恢复期链路利用大量RTT窗口远低于BDP窗口始终保持在10附近,链路基本满载

这个对照就能解释两条曲线形状的差异:Tahoe恢复后先有一段陡峭的指数爬升(慢启动),然后转平缓;Reno恢复后直接是缓坡(拥塞避免),没有“深坑起步”的过程。从宏观吞吐的角度,Reno把丢包后“失去的RTT”几乎全部省了下来。

4.3 同一窗口丢两个包的经典崩溃场景:NewReno的动机

Reno有个非常著名的弱点:如果同一个窗口里丢了多个包,快速恢复会提前“错误退出”。比如一个窗口内有第3000、第4000两个报文都丢了。Reno先恢复第3000号报文,当接收方确认了重传的第3000号时,发回的ACK确认号是4000——它只确认到第二个丢失报文之前。这个ACK虽然“确认了新数据”,但并没有确认所有在途数据,属于部分ACK。

经典Reno看到新ACK就退出快速恢复,把cwnd缩到ssthresh,然后从拥塞避免重新开始。紧接着第4000号报文又触发3个重复ACK,再次进入快速恢复、再次减半。一次多丢包事件里窗口可能被连续砍两次,而且越砍越小,最后往往退化到超时重传。

这也是为什么后来的NewReno规定:快速恢复期间收到部分ACK时不退出,而是把下一个丢失报文立刻重传,直到所有在丢失时刻在途的报文都被确认。再往后的SACK机制则是让接收方明确告诉发送方哪些报文丢了、哪些到了。理解了Reno在多丢包场景的狼狈,才能看懂现代TCP里这些补丁的动机——这些内容往往会作为实验思考题出现,建议大家亲手复现一次。

5. 实测曲线与统计口径:如何从仿真trace里读出两种算法的性格

5.1 cwnd锯齿的“深谷”与“浅谷”:看到的差异背后的原因

画图时我建议用gnuplot或者直接用plot工具,横轴时间(秒),纵轴cwnd(报文数)。稳定状态下的两条曲线都是锯齿,但形状明显不同:

Tahoe的锯齿是深谷型:每次丢包后窗口跌到接近1,然后一段陡峭的指数爬升(慢启动),再转成平缓上升,触顶丢包,再跌回1。齿谷很深,齿的右半边明显“前陡后缓”。

Reno的锯齿是浅谷型:每次快速重传后窗口只跌到峰值的一半左右,后面是均匀的线性爬升,没有陡峭段。齿谷很浅,整体看起来更“温柔”。

如果两种丢失事件混在一起,曲线会是两种齿形的叠加:快速重传对应浅谷,超时对应深谷。所以当你在图上看到又深又浅两种谷,反而说明实验参数设置是合理的——它真实反映了两种丢包处理路径。

还有个值得注意的细节:如果某些丢包率条件下Reno也频繁发生超时,它的曲线上也会出现深谷,这时候只看曲线容易误判。判断算法行为的依据不能只靠眼睛,要靠第5.3节的事件统计。

5.2 吞吐率、goodput与重传开销的统计方法

对比两个算法不能只看曲线。我建议统计三个指标:

第一,有效吞吐率(goodput)。只统计接收方实际收到的应用数据字节数,除以时间。仿真trace里r事件表示接收,第6个字段是报文大小。可以用一条awk命令估算:

awk '$1=="r" && $4==3 && $6>1000 {sum+=$6} END {print sum*8/30/1e6 " Mbps"}' out.tr

第二,重传开销。统计trace里同一个序号被发送两次的次数。对同一序号,第一次出现是原始发送,第二次出现就是重传。Tahoe因为“归零重爬”和恢复期内的二次丢包,重传报文总数通常明显高于Reno。

第三,丢包事件分类统计。把超时重传和快速重传分开计数。这个口径直接影响结论,我在下一节细说。

理论上有条经验公式可以用来对照结果:Reno在随机丢包率p下的稳态吞吐率近似满足 (B \approx 1.22 \times MSS / (RTT \times \sqrt{p})),也就是经典的“平方根律”。它说明吞吐率和丢包率呈反比平方根关系,丢包率从1%降到0.25%,吞吐大约翻一倍。Tahoe没有这么漂亮的闭式解,但由“每个周期多一段慢启动空窗期”可以推出:窗口峰值W越小,Tahoe相对Reno的吞吐损失越大。在常见的实验室参数下,Tahoe的goodput大约比Reno低15%到30%,丢包率越高差距越明显。

5.3 一张典型对比表:超时次数、快速重传次数与传输效率

我在1 Mbps、RTT 100ms、队列10报文、随机丢包率2%、仿真时长30秒的配置下跑出来一组参考数据,标注清楚:这只是看趋势用的示例值,不是标准答案,换一个随机种子或参数,绝对数值会变,但相对关系基本稳定。

指标TahoeReno
cwnd峰值(报文数)约18约22
cwnd谷值(报文数)1约10~11
快速重传次数617
超时重传次数218
重传报文总数约38约23
有效吞吐(goodput)约0.62 Mbps约0.78 Mbps
链路利用率约62%约78%

这组数据里最扎眼的是超时次数:Tahoe的超时次数是Reno的2倍以上。原因有两层:一是Tahoe恢复期窗口太小,容易把后续丢包逼成队尾丢包;二是恢复期频繁触发的二次丢包进一步制造超时。Reno因为窗口一直维持在半水位,对后续丢包的“抗性”明显更强。吞吐差异在1Mbps的窄链路上看着不大,但换算成相对值,Tahoe比Reno低了约20%,这个量级在多次重复实验里是稳定可复现的。

6. 实验翻车重灾区:四个最容易被忽视的细节与自查清单

6.1 发送窗口默认值20:最大的隐藏坑

这个坑我前面提到过,但值得单独再强调一次:NS2里TCP代理默认的window_很小,如果不显式调大,就会变成“cwnd还没展示出锯齿,就被rwnd按住了”。现象是两条cwnd曲线都是一路涨到20然后走平,既没有锯齿也没有区别。

判断是否踩中这个坑很简单:看cwnd的峰值是否恰好等于某个固定值(比如20或者64),如果多条不同参数的实验峰值都一模一样,十有八九是发送窗口封顶。解决办法就是把它设到足够大,比如10000,让拥塞窗口成为唯一限制因素。

6.2 缓冲区设得越大越看不到区别:以及“无丢包实验”

缓冲区选择的道理和发送窗口类似,但方向相反。缓冲设得太大,队列从不溢出,没有任何丢包事件,cwnd会一路涨到上限然后平着走——实验变成“拥塞控制从未被触发”。缓冲设得太小,几乎所有丢包都是队尾丢包,全部走超时路径,Tahoe和Reno的行为就会高度趋同,因为超时后两者都是“归零重来”。

合适的缓冲范围我建议取0.5到2倍的BDP。定量说,上面配置里BDP约13个报文,缓冲取10个报文左右就比较合适。这样既有队列溢出造成的快速重传,又有队尾丢包造成的超时事件,两种算法才有“戏”可看。

6.3 统计口径:把快速重传误判为超时重传

统计超时和快速重传的次数,是很多同学最容易出错的地方。混着统计就会得出“Tahoe重传次数并不比Reno多很多”的错误结论。

我的做法是从trace里抓同一个序号的重传时间,然后对比两次发送的间隔:

  • 超时重传:原始发送和重传之间的间隔接近RTO,通常接近1秒甚至更大,而且往往伴随指数退避,间隔会翻倍。
  • 快速重传:原始发送和重传之间的间隔约等于收集3个重复ACK的时间,也就是几个RTT的量级,在100ms RTT的环境下通常不超过几百毫秒。

另外可以数一数重复ACK:在trace的接收事件里,同一个确认号连续出现3次以上,就说明触发过快速重传候选条件。把这两套依据对齐,统计口径就基本不会错。手动统计比较繁琐,但建议至少手算一个完整周期,找找感觉,后面再交给脚本处理。

6.4 仿真时长与随机种子:跑一次就下结论等于掷骰子

仿真时长太短是我见过第二多的问题。RTT是100ms,一次完整的“爬升-触顶-丢包-恢复”周期大约需要1到2秒,如果只跑5秒,曲线里可能只有1到2个锯齿,统计结果受偶然因素影响极大。我建议至少跑够20到30秒,让稳态行为重复出现10个周期以上。

随机丢包的种子也值得注意。带ErrorModel的实验,换一个随机种子,丢包的具体报文和时刻就完全不同,两次实验的超时次数可能差出一倍。结论应该基于多个随机种子的平均值,而不是某一次“看起来很漂亮”的结果。至少跑3到5个种子,把重传次数、吞吐量求平均,再下判断。

6.5 自查清单:拿到实验结果后按顺序过一遍

跑完一轮实验,先别急着写分析,按下面的清单自查一遍:

  • 发送窗口window_是否远大于预期cwnd峰值?如果没有,先确认不是rwnd封顶造成的“平台”;
  • cwnd曲线是否在BDP之上波动?如果一直贴在低位,可能缓冲太小或丢包率太高;
  • trace里是否同时出现超时重传和快速重传两类事件?如果只有一类,参数大概率不合适;
  • Reno的恢复期里,cwnd是否出现了“ssthresh+3再逐个+1”的膨胀段?没有这个特征说明快速恢复根本没触发;
  • Tahoe的曲线谷值是否接近1?如果Tahoe也总是停在半水位,说明它没有按预期“归零”;
  • 结论是否基于多个随机种子的平均值?单次运行的结果不能作为对比依据。

这六条都过了,再写分析基本不会翻车。

最后再分享一个个人习惯:我会把ssthresh的trace和cwnd的trace画在同一张图里。cwnd是算法“实际怎么做”,ssthresh是算法“心里期待的目标水位”。Tahoe丢包后ssthresh与cwnd之间的巨大落差,比任何文字都能说明“归零重来”的代价;Reno恢复期间两者贴合得严严实实,一眼就能看出它“没有浪费窗口”。做实验别只盯着结论,把这两条线的配合关系看懂,你对拥塞控制的理解会比单纯记住“Tahoe归零、Reno减半”要扎实得多。

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

browser-agent:5 分钟跑通的自然语言浏览器自动化框架

browser-agent:5 分钟跑通的自然语言浏览器自动化框架 【免费下载链接】browser-agent Open-source, vision-first browser agent 项目地址: https://gitcode.com/GitHub_Trending/magnit/browser-agent browser-agent(Magnitude)是一…

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

SQL子查询全解析:四种形态、执行原理与性能优化实战

子查询大概是新手从“会写SQL”到“敢碰复杂业务报表”之间,最过瘾的一道分水岭。没接触它之前,遇到“按某个汇总结果再筛选一次”的需求,我脑子里只有两种方案:要么把数据分两次查出来,回到代码里用 if 拼&#xff1b…

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

CTF2:Tetraes 加密实战——从 AES S-Box 到密钥调度的可复现验证

/* 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:46:04

ChatTTS-ui 音色定制实战指南:3 步拿到可复用的自定义声音

ChatTTS-ui 音色定制实战指南:3 步拿到可复用的自定义声音 【免费下载链接】ChatTTS-ui 一个简单的本地网页界面,使用ChatTTS将文字合成为语音,同时支持对外提供API接口。A simple native web interface that uses ChatTTS to synthesize tex…

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

嵌入式GBK点阵字模:16×16与24×24索引算法及工程集成

简介:这是一份面向打印机固件开发、LCD汉字显示及嵌入式字库应用场景的GBK点阵字模资源,收录GBK1.0规范内22046个汉字,不含用户自定义区,可解决开发中难以找到专业字库的痛点。包内提供1616与2424两套点阵字库文件,并附…

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

ThingsBoard 日志越滚越大?用 logback 内置轮转 3 步控住磁盘

ThingsBoard 日志越滚越大?用 logback 内置轮转 3 步控住磁盘 【免费下载链接】thingsboard All-in-one IoT Platform - Device management, data collection, processing and visualization. 项目地址: https://gitcode.com/GitHub_Trending/th/thingsboard …

作者头像 李华