基于Python的UDP可靠传输实现:从停等协议到GBN滑动窗口

简介:这是一份基于Python实现的可靠数据传输协议课程设计资源包,以UDP为底层通道,完整演示了停等协议、GBN滑动窗口协议及SR协议的设计与实现过程。面向计算机网络课程学生、需要完成类似实验或对传输层协议感兴趣的学习者,可借此掌握可靠传输原理、分组丢失处理、滑动窗口机制以及C/S文件传输应用的构建方法。包内共14个文件,核心为8个Python源码文件,涵盖服务器、客户端、配置及工具模块,另有txt测试数据、docx实验报告、markdown说明和license许可文件,压缩包仅723KB,便于下载和快速部署。资源围绕实验场景展开,包含模拟丢包验证协议有效性、将停等协议扩展为双向传输、基于停等协议实现文件传输、将GBN改进为SR等经典环节,配套文档可帮助梳理实现思路与排错方法。目前已有596人学习使用,适合作为课程设计参考和协议学习入门素材,尤其适合需要提交完整实验报告的学生。

1. 基于 Python 的 UDP 可靠传输实现:先跑通停等,再谈 GBN

如果你正被基于 Python 实现可靠数据传输协议的课程设计卡住,这份以 UDP 为底座的协议实现包值得花一个下午拆开跑通。它没有直接扔给你一份读不懂的 GBN 源码,而是把“UDP 本身不可靠”这件事摆上台面,从最简单的停等协议开始,逐步升级到滑动窗口、双向传输和文件传输。打开资源时最直接的感受是代码分工清楚:server.py 管发送,client.py 管接收,config.py 预留丢包率和超时参数,非常适合边改边验证。无论你在做停等、GBN 还是 SR,这套代码都能当作可运行的地基,尤其适合网络课设、面试梳理和想彻底搞懂可靠传输细节的开发者。

2. 停等协议落地:校验、超时重传与丢包模拟的代码骨架

2.1 停等协议背后的状态机时序

停等协议是 ARQ 家族里最容易建模的模型:发送方每发一个分组就停下来,等待接收方回 ACK;收到 ACK 才发下一个分组,超时没收到就重传当前分组。整个发送过程只有两个状态,要么处于 ready 状态可以发新数据,要么处于 waiting-for-ACK 状态,所有数据都卡在当前序号上。接收方更简单,能正确收到期望序号的分组就回 ACK,否则直接丢弃,什么都不回。

停等时序里最容易被忽略的是“重复旧分组”的场景。假设接收方刚处理完分组 0 并回 ACK,但 ACK 在链路上丢失,发送方超时重传分组 0。此时接收方期望的序号是 1,收到旧分组 0 时必须再次回 ACK 0,否则发送方会一直等待一个永远不会到来的 ACK 1。这就是为什么停等协议里的序号不是可有可无的装饰:它用来区分新分组和重传分组,避免接收方把重传数据当成新数据重复处理。

课程设计选 UDP 而不是 TCP,原因恰恰是 UDP 不保证不丢、不重、不乱。你需要在应用层自己实现校验和、序号判定、超时重传和确认机制。如果直接用 TCP,这些逻辑都被内核接管了,反而看不到可靠传输到底是怎么拼起来的。基于 UDP 做一份自己的可靠传输,才能把协议的每个环节暴露在日志和断点下。对回环测试来说,UDP 丢包率极低,所以实验要求模拟丢包,这一点我会在 2.3 节单独展开。

停等还有一个明显的工程上限:如果链路时延高,发送方绝大部分时间都在等 ACK,带宽利用率约等于 1 / (1 + 2 * RTT / 发送时间)。在本地环回测试也许看不出差距,但把模拟 RTT 加到 50ms 到 100ms 后,吞吐会跌到几个 KB/s。这个短板正好是下一章引入 GBN 的理由,但先把停等代码写对,GBN 的坑你才能看得明白。

2.2 基于 UDP 的停等代码骨架:包结构、发送循环与接收判定

资源里的 src 目录是协议主体,我们先给出一个可以直接替换进 server.py 和 client.py 的最小包结构。我用 4 字节序号加 8 字节校验码作为协议头,payload 放在后面,当基础骨架使用:

import hashlib import socket import struct SEQ_SIZE = 4 # 序号字段占 4 字节,使用无符号整型 CHECKSUM_SIZE = 8 # 校验码取 MD5 前 8 字节 PKT_HEADER_LEN = SEQ_SIZE + CHECKSUM_SIZE def make_packet(seq: int, payload: bytes) -> bytes: # 用 MD5 前 8 字节做应用层校验,便于演示,实际工程可以换 CRC32 checksum = hashlib.md5(payload).hexdigest()[:8].encode() return struct.pack("!I", seq) + checksum + payload

struct.pack("!I", seq)把序号转成网络字节序的无符号整型,保证收发包解释一致。校验码放在序号后面,接收端解析时按照固定步长切分,避免出现字节偏移错位。MD5 前 8 字节在演示场景里已经能拦截绝大部分数据篡改问题,虽然不如 CRC 高效,但教学资源里更容易看出校验逻辑。

发送端采用循环发送、超时重传的写法:

def stop_wait_send(sock: socket.socket, addr, seq: int, payload: bytes, timeout: float = 0.5) -> None: pkt = make_packet(seq, payload) while True: sock.sendto(pkt, addr) sock.settimeout(timeout) try: ack_bytes, _ = sock.recvfrom(SEQ_SIZE) ack_seq = struct.unpack("!I", ack_bytes)[0] if ack_seq == seq: print(f"[T] seq={seq} acknowledged") return except socket.timeout: print(f"[T] seq={seq} timeout, retransmit")

每次sendto之后设置settimeout(timeout),阻塞在recvfrom上等待 ACK。这里有一个关键点:recvfrom(SEQ_SIZE)只读 4 字节,因为在停等模型里 ACK 包不需要带校验和,只需要序号告诉发送方“我确认的是哪一包”。如果收到的是上一轮迟到的 ACK,序号对不上就继续循环等待,不能让旧 ACK 推动状态机。

接收端更需要小心,因为 UDP 包可能损坏,也可能重复到达:

import random def stop_wait_recv(sock: socket.socket, expected_seq: int, loss_rate: float = 0.0, timeout: float = 3.0): while True: data, addr = sock.recvfrom(65535) if len(data) < PKT_HEADER_LEN: continue seq = struct.unpack("!I", data[:SEQ_SIZE])[0] checksum = data[SEQ_SIZE:PKT_HEADER_LEN].decode() payload = data[PKT_HEADER_LEN:] md5 = hashlib.md5(payload).hexdigest()[:8] if seq != expected_seq or md5 != checksum: print(f"[R] drop packet seq={seq} expected={expected_seq}") continue if random.random() < loss_rate: print(f"[R] simulated loss seq={seq}") continue sock.sendto(struct.pack("!I", seq), addr) return payload, addr

接收端同时判断序号和校验码,两者有一个不满足就丢弃。注意丢包模拟我放在序号和校验都通过之后,这模拟的是“数据包在链路上消失”而不是“包损坏”,语义更干净。返回时同时给出 payload 和对端地址,便于上层拿到数据并知道该回 ACK 给谁。如果recvfrom设置了 timeout 也要处理超时,但在接收端被阻塞的情况下,一般使用更长的等待时间,或者干脆先不设 timeout,等发送方主动超时重传。

2.3 模拟丢包:把丢包放在接收端,才能触发超时重传

模拟丢包不是简单地在发送端跳过某个包。如果发送端用continue不发某个分组,接收端会因为一直等不到数据而阻塞,不会给出任何协议层面的反馈,实验记录里只能看到时序卡死,看不到“超时重传”这个关键行为。

我的做法是在接收端判断是否“假装这个包没收到”:

random.seed(42) # 固定随机种子,让实验可复现 loss_rate = 0.2 # 20% 丢包率,适合观察重传又不至于卡死

把丢包判断放在收到合法包之后、回 ACK 之前。当random.random()小于loss_rate时,直接跳过sendto回 ACK 的逻辑,发送方收不到 ACK,自然会在 timeout 后重传。这样日志中会出现[R] simulated loss seq=0,随后发送方打印[T] seq=0 timeout, retransmit,两个端一对照,协议的有效性就直观可见了。

丢包率的选择有讲究。0.1 太低,偶尔才触发一次重传,容易让你误以为协议很稳定;0.5 太高,整个流程频繁回退,日志里大半是 retransmit,反而掩盖了正常收发逻辑。我通常先用 0.2 调试,看窗口行为;确认逻辑无误后再把丢包率调成 0.3 到 0.4 做压测。如果丢包率设成 0.3 后文件传输还能完整结束,说明协议在较恶劣条件下依然可用。

代码跑通后建议做一次对比实验:固定随机种子,分别统计 loss_rate=0 和 loss_rate=0.2 时的 timeout 次数。前者应该为 0,后者应该正好等于模拟丢掉的包数。这一步能快速验证“丢包是否真的被重传救回来”,也是后续 GBN 版本对照的基线数据。

3. 从停等到 GBN:滑动窗口、累计确认与参数调整

3.1 GBN 和停等的本质差别:窗口不只是批量发送

GBN(Go-Back-N)把发送方从停等的串行模型里解放出来。发送方维护一个长度为 N 的发送窗口,窗口内所有序号都可以连续发送,不必等每个 ACK 回来再发下一个。但接收端的逻辑并没有变复杂:仍然只接受期望序号的分组,一旦发现某个序号丢失,后面到达的乱序分组会被全部丢弃,并继续回送最后一个有序分组的 ACK。发送方超时后会回退到最老的未确认序号,重传整个窗口内所有分组。

用窗口大小为 4 举一个直观例子:发送方连续发 0、1、2、3,假设分组 2 在链路中丢失。接收方收到 0 和 1 后回 ACK 0 和 ACK 1,收到分组 3 发现不是期望的 2,直接丢弃并回 ACK 1。发送方收到超时信号后,把发送指针回退到 2,重传 2 和 3,而不是只重传 2。这个“回退整段窗口”的动作就是 GBN 名字的由来。

GBN 的接收方不缓存乱序分组,这一点是它与 SR 协议最明显的分界线。乱序分组到了就丢,表面上看很浪费,却换来接收端逻辑的极度简单:只需要维护一个expected_seq变量,不会出现半包缓存和乱序重组的问题。代价是丢包率升高时吞吐会断崖式下降,因为一个包的丢失会导致后面所有在途分组全部重传。课程设计里先做 GBN 再做 SR,就是为了让你直观感受这个性能差异。

3.2 GBN 发送端代码改造:发送窗口、累计确认与超时回退

发送端需要维护两个变量:base表示窗口内最老的未确认序号,next_seq表示下一个可以发送的新序号。每次收 ACK 都尝试把base向前推进,超时则把next_seq拉回base。核心循环如下:

base = 0 next_seq_num = 0 window_size = 4 total = len(data_packets) # data_packets 是已经切片好的数据块 while base < total: # 发送窗口内所有未发送的分组 while next_seq_num < base + window_size and next_seq_num < total: sock.sendto(make_packet(next_seq_num, data_packets[next_seq_num]), addr) next_seq_num += 1 try: ack_seq = recv_ack(sock, timeout=0.6) if ack_seq >= base: base = ack_seq + 1 # 累计确认:ack_seq 及之前的分组都确认了 except socket.timeout: print(f"[GBN] timeout, go back to {base}") next_seq_num = base # 从 base 开始重传窗口内所有分组

ack_seq >= base的过滤很关键。GBN 接收方回送的 ACK 是最后连续收到的那一个序号,如果收到的旧 ACK 小于当前base,说明已经收到过更新的确认,直接忽略即可。如果把旧的 ACK 当成新确认,base可能倒退,窗口会重复发送数据。

next_seq_num = base是 GBN 重传的固定动作,不需要单独考虑哪个包丢了,统一重传整个窗口。这是实现最简单的策略,代价也是显而易见的。为了配合重传,data_packets必须保留窗口内尚未确认的数据副本,发送端不能像停等模式那样发出一包就释放一包。

接收端代码比停等版本还规则,只要维护一个连续期望序号:

expected_seq = 0 last_ack = -1 while True: data, addr = sock.recvfrom(65535) seq, payload = parse_packet(data) if seq == expected_seq: # 接收正确,推进期望序号 expected_seq += 1 last_ack = seq sock.sendto(struct.pack("!I", seq), addr) handle_payload(payload) else: # 乱序或重传,直接丢弃,并回送最后一个有序 ACK if last_ack >= 0: sock.sendto(struct.pack("!I", last_ack), addr)

注意接收方在遇到乱序分组时并不保持沉默,而是回一个last_ack。这样发送方虽然知道有包没对,但具体丢的是哪个包它并不关心,反正超时后整个窗口都会重传。如果last_ack初始是 -1,且期望序号是 0 却收到了分组 2,此时不应该回 -1,直接丢弃即可。实践中建议在分组头加一个类型字段区分 ACK 和 DATA,不过课程设计用单独的方法回 ACK 也完全够用。

3.3 GBN 参数调整:窗口大小、超时时间与丢包率的配合

GBN 涉及的参数主要三个,调试时建议先固定丢包率,再调窗口和超时。

参数推荐范围调试说明
window_size4 ~ 16窗口太小等于没发挥 GBN 优势,太大在丢包率较高时重传成本也高
timeout0.5s ~ 1.0s至少要大于一个 RTT,本地回环跑 0.3s 以下容易误超时
loss_rate0.1 ~ 0.30.2 最容易看到重传行为和窗口回退日志

window_size = 1时,GBN 其实退化成停等协议,代码逻辑依然能跑,但效率没有区别。验证 GBN 是否生效,最简单的方法是固定同一份数据,分别用 window_size=1 和 window_size=8 跑一遍,对比完成时间。如果吞吐没有明显提升,检查发送端是否每次循环都因为网卡缓冲或套接字设置而阻塞住了。

超时时间的设置是很多实现翻车的高发区。本地回环 RTT 通常不到 1ms,但 Python 线程调度、日志打印和随机数计算会引入抖动,timeout 设成 0.1s 可能仍然频繁超时,导致窗口不停回退。我一般先把 timeout 设成 1s,验证逻辑无误之后再逐步缩小到 0.5s 或 0.3s,并观察日志里 timeout 次数是否显著增加。

丢包率升高时,GBN 的窗口越大,重传风暴越明显。比如丢包率 0.3、窗口 16,一个包丢失后最多触发 15 个分组一起重传,再加上网络中原本就存在的重复包,日志量会爆炸。课程设计报告里可以专门做一张表,记录不同窗口大小下的重传次数,以此说明 GBN 在丢包环境下的瓶颈,并自然引出 SR 协议。

4. 双向通道与文件传输:把协议从 Demo 变成 C/S 应用

4.1 单向改双向:独立序号空间与捎带确认

大多数课程设计要求把单向改成双向,很多人第一反应是开两个 socket,一个发一个收,但这种方案会在连接管理和端口分配上多出不少边界问题。我的习惯是保持一个 UDP socket,让两端各自维护自己的发送序号和接收序号。数据包头对应扩展成两个序号字段:一个代表当前数据包的序号,另一个代表我要确认对端哪个序号。

import struct HEADER = struct.Struct("!II") # 前 4 字节是发送序号,后 4 字节是确认序号 def make_data_packet(seq: int, ack_seq: int, payload: bytes) -> bytes: return HEADER.pack(seq, ack_seq) + payload def parse_data_packet(pkt: bytes): seq, ack_seq = HEADER.unpack(pkt[:8]) payload = pkt[8:] return seq, ack_seq, payload

接收方收到数据后,需要把回复内容里带上当前要确认的序号。如果正好有数据要发给对方,就把数据包的ack_seq字段填成对方已经连续收到的序号,这叫捎带确认。如果没有数据要发,也要回一个只有头部没有 payload 的纯 ACK 包。捎带确认能省掉一部分 ACK 流量,但复杂度在于纯 ACK 包也要能识别类型。可以继续用 payload 长度来判断:长度 0 就是纯 ACK。

双向模式下两端协议逻辑其实完全对称。将原先的send_loop和recv_loop改成两个线程运行,一个负责发送数据,一个负责接收并确认。这里要注意线程共享同一个 socket 时的并发问题:sendto和recvfrom是线程安全的,但日志输出和状态变量要加锁。否则发送线程在改base,接收线程也在读base,会出现窗口推进不一致。

4.2 文件传输应用:把字节流切成分组,再按序写盘

把协议从传输文本字符串升级成文件传输时,第一步是把文件切成固定大小的块。文件块大小需要和 UDP 最大载荷协调,一般取 1024 字节到 2048 字节比较稳妥。

def split_file(path: str, chunk_size: int = 1024): with open(path, "rb") as f: seq = 0 while True: chunk = f.read(chunk_size) if not chunk: # 发送结束标记,由上层处理 yield ("EOF", seq) break yield ("DATA", seq, chunk) seq += 1

这里我统一把数据块交给上层的可靠发送函数,由它内部决定用停等还是 GBN。注意chunk_size不能太大,因为 UDP 单包最大理论长度是 65535 字节,头部还要去掉 8 字节以上,写 4096 就会让 IP 层产生分片,链路层再一丢整个包就危险了。

接收端写文件时不能边收边写,因为 GBN 接收方只接受有序分组,但 SR 版会收到乱序分组。稳妥的做法是维护一个字典缓存乱序包,等期望序号到达后按序写盘:

recv_file = open(recv_path, "wb") recv_cache = {} expected = 0 while True: seq, payload = get_valid_next_packet() if seq == expected: recv_file.write(payload) expected += 1 # 把缓存中连续的包补写进去 while expected in recv_cache: recv_file.write(recv_cache.pop(expected)) expected += 1 else: recv_cache[seq] = payload

这个写法对 GBN 不产生实际作用,因为 GBN 不会推后乱序包,但如果实验要求把 GBN 升级成 SR,这个缓存结构可以直接复用。文件传输场景还有一个坑:不能靠文件大小判断传输是否结束,因为最后一个块可能恰好是完整块大小。正确做法是在协议头增加一个类型字段,比如DATA=0、FIN=1。发送方发完最后一个数据块后单独发一个 FIN 包,接收方收到 FIN 并确认完所有数据块之后才关闭文件。

4.3 资源里的代码结构:server.py、client.py 与 config.py 各管什么

这份课程设计资源的目录划分很清晰,直接看路径就能明白实验流程:

路径作用
src/protocol/server.py发送端协议逻辑,包含停等/GBN(或 SR)的发送循环
src/protocol/client.py接收端协议逻辑,负责校验、回 ACK、写入文件
src/util/config.py统一配置端口、窗口大小、超时时间、丢包率
data/server_data.txt服务器端待发送的测试数据
data/client_recv.txt客户端收到的数据,默认从client_data.txt改名得到
report.docx课程设计报告模板,可把实验数据抄进去
README.md运行步骤和设备环境说明

我拿到这份包时最喜欢的一点是config.py单独存在。把端口、窗口、丢包率抽出来之后,做对比实验不用反复改协议代码,只改配置文件就能跑多组参数。如果你的实验报告要求记录参数影响,建议把每组参数的时间、重传次数、最终文件 MD5 都写进一个 CSV 表格,报告阶段直接用数据说话,比纯截图更有说服力。

5. 协议实现中的坑:丢包位置、超时风暴与序号回绕

5.1 丢包模拟写在发送端,重传永远不触发

现象:设置了 20% 丢包率,但日志里完全看不到 timeout,数据照样发完,实验看起来“一切正常”。

原因:把random.random() < loss_rate的判断放在了发送端sendto之前,相当于发送端自己吞掉了部分包。接收端并没有经历数据包丢失,期望序号依然按顺序推进,自然会给每个到达的包回 ACK。发送端不会感知到丢包,超时重传机制完全没有被激活。

解决:把丢包判断放到接收端收到合法包之后、回 ACK 之前。判断通过后假装这个包没收到,直接continue跳过 ACK 发送。发送端因为收不到 ACK 而超时,才会重传。要验证丢包位置是否正确,最直接的办法是丢包率设为 1.0,观察发送端是否出现持续 timeout 且重传计数不断增长。如果日志里 timeout 一次都没有,说明丢包放错了端。

5.2 超时时间比 RTT 还短,产生重传风暴

现象:GBN 窗口开到 8 之后,吞吐反而比停等还低,终端刷屏都是retransmit,整个流程像蜗牛一样慢。

原因:超时时间设置不合理。本地回环 RTT 不一定恒定,Python 的解释器开销、接收端的解析打印、随机丢包判断都会让响应时间抖动。timeout 设成 0.1s 甚至更短时,很多本来马上就要收到的 ACK 提前被判为超时,于是触发重传;重传的包又污染接收端序号判断,造成更多超时。

解决:先把 timeout 调成 1s,跑通后再逐步下降。如果 0.3s 开始出现 timeout 密集增长,说明当前设备负载偏高,可以考虑把丢包率调低或者窗口调小。另一个技巧是用time.monotonic()记录每次发送到收到 ACK 的实际耗时,把统计出来的最大 RTT 再乘 1.5 到 2 作为超时阈值,这是我在实际调试里比较稳的取值方式。

5.3 序号回绕导致合法分组被当成重复包丢弃

现象:传输超过一定数据量后,接收方突然开始大量丢弃本来合法的分组,丢包率没有变,但多次重传仍然卡在一个区域过不去。

原因:序号字段是固定的 4 字节无符号整型,最大到 4294967295,序号回绕到 0 后,简单的seq == expected_seq判断会错乱。比如expected_seq是 4294967294,下一个合法分组是 4294967295,再下一个是 0,但很多人对0的期望判断变成了“小于当前值,属于旧分组”,于是直接丢弃。

解决:对序号采用模2^32的差值比较。只要(seq - expected_seq) % (2**32) < window_size,就可以认为该序号在当前窗口范围内,属于合法的后续分组。对 ACK 也要做同样的窗口判定:只有确认序号落在当前窗口内,才能用来推进base。如果你只是课程设计、数据量不大,可能不会碰到回绕,但只要把文件块调小、文件调大,回绕是必然会来的。这个坑我在面试复盘时也经常遇到,属于协议实现里典型的“看着简单但真实存在”的问题。

6. 再进一步:SR 改造与一个高信噪比的验证技巧

SR 和 GBN 的代码差异其实很小,核心只有两个动作。GBN 超时时是next_seq = base,重传整个窗口;SR 超时时只重传最小的未确认序号,接收端不再丢弃乱序分组,而是把它缓存到recv_cache里等前面的序号补上。如果你已经完成了第 4 章的双向文件传输,那么只需要在接收端增加一个乱序包缓存,在发送端把超时回退改成定向重传,就能从 GBN 升级到 SR。

验证 SR 正确性时,我常用一个比“最终文件能打开”更有效的技巧:让发送端在发送每个分组时记录seq -> md5(payload)的映射,接收端收到每个分组时也记录同样的映射,最后比对两张字典是否完全一致。

sent_log[seq] = hashlib.md5(payload).hexdigest() recv_log[seq] = hashlib.md5(payload).hexdigest() assert recv_log == sent_log

这个验证方式能精准定位“到底是哪个序号丢了、哪个序号被错误缓存”,而不是等文件全部传完才发现数据错位。把丢包率从 0 到 0.5 逐档递增,固定随机种子跑三遍以上,每次都比对这个映射。以前我以为协议跑通就算完,后来有一次 SR 改造后文件能正常打开,但 20% 丢包率下中间某个块内容对不上,整个日志比对才发现是缓存写入顺序写反了。从那以后我每写一个协议,都会强制先跑一遍丢包对比矩阵,并把每档丢包率下的 MD5 校验结果存进实验报告,这个习惯帮我绕开过不少玄学问题,希望帮到你。

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

相关新闻

搞懂MySQL存储引擎:InnoDB与MyISAM选型调优指南

搞懂MySQL存储引擎:InnoDB与MyISAM选型调优指南

1. 从一个线上事故说起&#xff0c;为什么必须先搞懂存储引擎 你有没有遇到过这种情况&#xff1a;线上的某个统计表数据量涨到几百万行之后&#xff0c;select 查询突然慢得离谱&#xff0c;insert 也开始排队&#xff0c;整个页面卡死。我最早踩这个坑是在给某模拟项目X做一套…

2026/10/19 21:04:40 阅读更多 →
大模型选型指南:从模型维度到应用维度的完整决策框架

大模型选型指南:从模型维度到应用维度的完整决策框架

1. 大模型赛道的分野逻辑与选型困局 过去两年我一直在做企业侧的模型选型咨询&#xff0c;经手过的项目从智能客服、代码辅助到多模态内容审核&#xff0c;覆盖了十来个行业。每次开新项目&#xff0c;团队问得最多的永远是同一个问题&#xff1a;市面上这么多模型&#xff0c;…

2026/10/19 21:03:38 阅读更多 →
Spring Boot四层XSS防御实战:过滤器+包装器+CSP+输出编码

Spring Boot四层XSS防御实战:过滤器+包装器+CSP+输出编码

我处理项目里那个"搜索框变成按钮"的Bug&#xff0c;才真正理解XSS不是文档里的一句名词&#xff0c;而是能在几分钟内把Cookie、页面内容、用户操作全部劫走的入口。当时用Spring Boot做的后台管理系统&#xff0c;前端同学随手往标题栏输入了一段带尖括号的内容&am…

2026/10/19 21:02:35 阅读更多 →

最新新闻

IM系统用户管理子服务架构设计与踩坑实践:从表结构到缓存策略

IM系统用户管理子服务架构设计与踩坑实践:从表结构到缓存策略

做IM系统&#xff0c;用户管理子服务往往是看起来最简单、坑却最多的那一块。很多人一开始会觉得&#xff0c;无非就是注册、登录、改个昵称、拉个好友列表&#xff0c;有什么好写的&#xff1f;但真正上手做才发现&#xff0c;一个IM系统里最基础的用户服务&#xff0c;牵扯到…

2026/10/19 22:03:35 阅读更多 →
DUT-OMRON数据集上的U-Net二值分割实战:边缘鲁棒与工业落地

DUT-OMRON数据集上的U-Net二值分割实战:边缘鲁棒与工业落地

简介&#xff1a;本资源是一套面向深度学习初学者与图像分割实践者的Unet多尺度分割实战项目&#xff0c;聚焦二值图像分割任务&#xff0c;特别适配DUT-OMRON数据集的端到端训练与推理需求。压缩包共2000个文件&#xff0c;主体为1979张PNG格式的训练/测试图像及对应mask&…

2026/10/19 22:03:35 阅读更多 →
XXE漏洞剖析:从XML外部实体注入到文件读取与SSRF防御

XXE漏洞剖析:从XML外部实体注入到文件读取与SSRF防御

大概在五年前做某次授权测试&#xff0c;目标系统有个不起眼的文件上传接口&#xff0c;响应报文里带着一段 XML 结构。当时随手改了改请求体&#xff0c;把实体声明换成了外部实体&#xff0c;结果直接把服务器上的/etc/passwd读了出来。那是我第一次真正意识到 XXE&#xff0…

2026/10/19 22:02:34 阅读更多 →
编译通过≠代码健康:C++静态检测原理与工具落地指南

编译通过≠代码健康:C++静态检测原理与工具落地指南

编译通过不等于代码健康&#xff1a;C静态检测到底在查什么我做C开发这些年&#xff0c;越来越觉得一个道理&#xff1a;编译器不报错&#xff0c;只是说你的代码符合语法和类型规则&#xff0c;它压根不关心你的逻辑是不是对的、指针是不是悬空的、资源有没有泄漏。很多问题要…

2026/10/19 22:02:34 阅读更多 →
基于Prometheus的MySQL自适应优化系统:从手工调参到自动闭环

基于Prometheus的MySQL自适应优化系统:从手工调参到自动闭环

1. 起因&#xff1a;静态参数与动态负载之间的拧巴 我维护的一套订单查询系统&#xff0c;跑在两个8C16G的实例上&#xff0c;平时QPS稳定在两千左右&#xff0c;配置也自认为调得“差不多了”。某天凌晨两点多&#xff0c;告警平台连续弹了三条&#xff1a; Threads_connecte…

2026/10/19 22:01:31 阅读更多 →
基于Python实现可靠数据传输协议:从停等到滑动窗口的完整课设指南

基于Python实现可靠数据传输协议:从停等到滑动窗口的完整课设指南

简介&#xff1a;这是计算机网络课程设计场景下的一份Python实现资源包&#xff0c;围绕UDP设计可靠数据传输协议&#xff0c;完整覆盖停等协议、GBN和SR的改进思路。内容对应实验要求&#xff0c;包括单向可靠传输、丢包模拟、双向数据传输以及基于协议的C/S文件传输应用&…

2026/10/19 22:01:31 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介&#xff1a;本资源为电源技术方向的学术研究文档&#xff0c;聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略&#xff0c;面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* 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 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址&#xff1a; https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集&#xff0c;共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* 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 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* 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 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* 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 12:16:16 阅读更多 →