news 2026/10/27 2:07:23

libnids源码解析:IP分片与TCP流重组的底层实现原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
libnids源码解析:IP分片与TCP流重组的底层实现原理

简介:本资源是一份深度注释版的libnids-1.20源码包,面向网络安全开发者、协议分析学习者及NIDS系统定制人员,聚焦解决原始源码可读性低、TCP/IP重组逻辑难理解等核心痛点。包内共65个文件,涵盖15个C源文件(如tcp.c、libnids.c、ip_fragment.c)、7个头文件(nids.h、tcp.h、hash.h等)、5个构建配置文件(configure.in、Makefile.in等)及测试/文档类文件(API.html、TESTS、README、CREDITS等),完整保留原始工程结构与跨平台适配能力;压缩包仅245KB,轻量易解压,便于逐模块研读。已有469人学习下载,读者可直接获取带全量中文注释的IP解析、TCP流重组(含add_seq、tcp_reassemble等关键函数详解)、事件回调机制等核心模块源码,注释覆盖数据结构设计意图、字段语义、分片处理逻辑及典型攻击检测上下文,显著降低源码阅读门槛,支撑二次开发与教学实践。

1. libnids:为什么一个20年前的C库,至今仍是网络协议栈解析的“底层标尺”

你可能没直接用过 libnids,但只要你调试过 TCP 重组、分析过 IP 分片异常、或者在 IDS/IPS 场景下抓包时发现「明明发了完整 HTTP 请求,却收不到应用层 payload」——那背后极大概率站着它。这不是一个被时代淘汰的古董,而是一把被反复淬炼过的手术刀:它不处理加密、不兼容 IPv6(原生)、不提供 REST API,却能在单线程里以接近零拷贝的方式,把乱序、重传、分片、超时的原始网卡数据流,稳稳地还原成一个个可读的 TCP 流会话。它的核心价值从来不是“功能多”,而是“边界清晰”——只做三件事:IP 层重组、TCP 流重组、TCP 连接状态机建模。所有上层逻辑(如协议识别、规则匹配、告警生成)都必须自己插桩进去。这种“克制”,恰恰让它成了理解网络协议栈真实行为的最短路径。读懂它,你就不再把tcpdump -A当真相;你会开始问:Wireshark 显示的“Reassembled TCP”到底在哪一步完成的?SYN-ACK 丢失后,libnids 是如何判定连接已死的?为什么某些分片攻击能让它内存暴涨?这些不是玄学,是它源码里白纸黑字写死的状态转移和内存管理策略。


2. IP 分片重组:不是拼图游戏,而是一场带超时与冲突仲裁的内存博弈

libnids 的 IP 重组模块(ip_fragment.c)远非简单缓存 + 拼接。它面对的是真实网络中无序到达、重复发送、部分缺失、甚至恶意构造的分片报文。其设计核心是两个结构体:struct ip_frag(每个待重组的 IP ID 对应一个)和struct ipq(每个分片节点)。关键不在“怎么存”,而在“怎么裁决”。

2.1 分片队列的生命周期:从插入到销毁的四重门控

每个新分片进入时,libnids 先查ip_frag哈希表(基于ip_src,ip_dst,ip_id,ip_p四元组),若未命中则新建;若命中,则进入ipq链表插入逻辑。但插入不是无条件的:

// ip_fragment.c: insert_fragment() if (offset + len > IP_MAXPACKET) { nids_params.no_mem("IP fragment too big"); return; } if (offset + len > ipq->len) { ipq->len = offset + len; // 动态伸缩预期总长 }

这里ipq->len不是固定值,而是随最大偏移+长度动态增长的“预期总包长”。这避免了为每个分片预分配 64KB 内存(IPv4 最大包长),但引入了新的风险:恶意攻击者发送大量高偏移小分片,可让ipq->len虚高,后续合法分片因内存不足被丢弃。

提示:IP_MAXPACKET定义为 65535,但 libnids 默认nids_params.max_packet为 16384 —— 这个参数实际限制了ipq->len的上限,是防御资源耗尽的第一道闸。

2.2 超时与老化:不是定时器轮询,而是链表头尾双指针驱动

libnids 不用setitimer()或epoll_wait()管理分片超时,而是采用“惰性老化”(lazy aging):每次有新分片到达或调用nids_run()时,遍历ip_frag哈希桶,检查每个ip_frag的last_seen时间戳。若超时(默认nids_params.ip_timeout = 30秒),则释放整个ipq链表。

更精妙的是ip_frag的哈希组织方式:它用ip_src ^ ip_dst ^ ip_id ^ ip_p做 hash key,并维护一个全局fragments链表头。nids_run()中的age_ip_fragments()函数并非遍历全部哈希桶,而是从fragments头开始,按插入顺序扫描——这意味着最新插入的分片永远在链表尾部,最老的在头部。超时检查从头开始,一旦遇到未超时项即停止。这保证了平均 O(1) 老化开销,而非 O(N) 全量扫描。

2.3 冲突仲裁:当两个分片声称同一偏移时,谁胜出?

RFC 791 规定:同一偏移的分片,后到者应覆盖先到者(因无法判断哪个更“新”)。libnids 严格遵循:

// ip_fragment.c: insert_fragment() for (prev = &ipq->fragments; (curr = *prev); prev = &curr->next) { if (curr->offset == offset) { // 直接替换内容,不释放旧 buf memcpy(curr->data, data, len); curr->len = len; return; } if (curr->offset > offset) break; } // 否则插入到 prev 之后

注意:它不做free(curr->data),而是直接memcpy覆盖。这是性能关键点——避免频繁 malloc/free。但这也意味着:若攻击者持续发送同一偏移的垃圾数据,curr->data缓冲区将长期被污染,直到该分片最终重组或超时。

2.4 避坑:IP 分片重组的四大翻车现场

现象 1:nids_params.ip_timeout设为 0,程序内存持续上涨

原因:超时机制被禁用,ip_frag和ipq永不释放,分片缓存无限堆积。libnids 不做内存上限硬限(不像现代 DPDK 库有 ring buffer size 控制),全靠超时兜底。
解决:绝不设为 0;生产环境建议设为 15–30 秒,配合监控nids_stats.ip_frags计数器。

现象 2:收到完整分片,但nids_handle_ip()返回的ip_data为空

原因:ipq->len被恶意分片拉高后,malloc(ipq->len)失败(nids_params.no_mem被触发),整个重组流程中止。此时ipq仍存活,但无有效 payload。
解决:降低nids_params.max_packet(如设为 8192),并确保no_mem回调能记录日志而非静默返回。

现象 3:同一 IP ID 的多个分片流混杂,导致错误拼接

原因:libnids 仅用ip_id+ip_src/dst判断是否同属一包,但某些 NAT 设备会复用ip_id。当两个不同源的分片共用同一ip_id,它们会被塞进同一个ipq。
解决:启用nids_params.scan_num_hosts = 0(禁用主机扫描模式),或在nids_register_ip()回调中增加五元组校验逻辑。

现象 4:分片重组成功,但应用层协议解析失败(如 HTTP 解析器收不到完整请求)

原因:IP 重组完成,但 TCP 流尚未重组(见第 3 章)。开发者误以为nids_handle_ip()返回即代表“完整 L4 报文”,实则只是“完整 L3 包”。
解决:必须等待nids_tcp_callback触发,且tcp->state == NIDS_JUST_ESTABLISHED || tcp->state == NIDS_DATA时才处理应用层数据。


3. TCP 流重组:状态机驱动的双向缓冲,不是 FIFO 队列

libnids 的 TCP 重组(tcp.c)是整套库最精悍的部分。它不依赖内核协议栈,完全在用户态模拟 TCP 接收窗口行为,用两个环形缓冲区(struct half_stream)分别管理 client→server 和 server→client 的数据流。其本质不是“缓存数据”,而是“重建 TCP 接收窗口的滑动语义”。

3.1half_stream:一个带滑动窗口语义的环形缓冲区

每个struct half_stream包含:

  • buf: 实际数据存储区(nids_params.n_tcp_streams * 2 * nids_params.tcp_workarounds字节)
  • offset: 当前已确认的起始序列号(即rcv_nxt)
  • count: 当前已缓存的有效字节数
  • r_mem: 指向buf的读指针(逻辑上)
  • w_mem: 指向buf的写指针(逻辑上)

关键点在于:offset不是固定起点,而是随 ACK 动态推进的“滑动基点”。当收到seq=1000, len=100的报文,且offset=1000,则直接写入buf[0];若offset=900,则需写入buf[100](偏移 100 字节处);若offset=1100,则此报文是“未来数据”,需暂存于future_queue(链表,非环形缓冲)。

// tcp.c: add_to_stream() if (seq == stream->offset) { // 紧跟当前窗口,直接写入 memcpy(stream->buf + stream->w_mem, data, len); stream->w_mem = (stream->w_mem + len) % stream->size; stream->count += len; stream->offset += len; } else if (seq > stream->offset && seq < stream->offset + stream->size) { // 在窗口内但有空洞,写入对应位置 int pos = (seq - stream->offset + stream->r_mem) % stream->size; memcpy(stream->buf + pos, data, len); // 更新 count:需扫描 buf 判断连续段(见 3.2 节) } else { // 超出窗口,入 future_queue queue_future(stream, seq, data, len); }

3.2 连续数据提取:不是遍历整个 buf,而是按“空洞链表”跳转

nids_tcp_callback触发时,libnids 并非把整个buf交给上层。它先调用get_tcp_data(),该函数执行两步:

  1. 填充空洞:遍历future_queue,将seq落在[offset, offset + size)内的分片,按序写入buf对应位置;
  2. 提取连续段:从r_mem开始,扫描buf找最长连续数据块(即buf[r_mem]到buf[r_mem + n]均非空),返回n字节指针。

这个“连续段”就是上层看到的tcp->payload。它天然满足 TCP 的“按序交付”语义——哪怕中间有重传、乱序,只要seq连续,就合并输出。

注意:get_tcp_data()不修改r_mem,只返回指针。真正消费数据由上层回调决定:若上层处理了k字节,则调用nids_discard(k),libnids 才推进r_mem和count。

3.3 连接状态机:11 种状态,但只有 4 个真正影响重组

libnids 定义了enum nids_state(NIDS_JUST_ESTABLISHED,NIDS_DATA,NIDS_CLOSE,NIDS_RESET,NIDS_TIMED_OUT, ...),但只有前 4 种会触发nids_tcp_callback。其余状态(如NIDS_EXITING,NIDS_EXITED)仅用于内部清理。

状态迁移由tcp_event()驱动,核心逻辑在tcp_state_change()中。例如:

  • 收到 SYN →NIDS_JUST_ESTABLISHED
  • 收到第一个 DATA(且 ACKed)→NIDS_DATA
  • 收到 FIN 或 RST →NIDS_CLOSE/NIDS_RESET
  • 超时无活动 →NIDS_TIMED_OUT

关键约束:只有NIDS_JUST_ESTABLISHED和NIDS_DATA状态下,tcp->payload才非 NULL。NIDS_CLOSE时tcp->payload为空,但tcp->state可告知上层“连接关闭原因”。

3.4 避坑:TCP 重组的五大血泪经验

现象 1:tcp->payload长度忽大忽小,甚至为 0,但抓包显示数据正常

原因:tcp->payload是“当前连续段”,不是“累计接收量”。若中间有丢包(seq 空洞),即使后续重传到达,get_tcp_data()仍只返回从r_mem到第一个空洞前的数据。
解决:不要假设payload是完整请求;需在回调中累积数据,直到检测到\r\n\r\n或 Content-Length 边界。

现象 2:nids_params.n_tcp_streams设为 1024,但实际只支持约 500 个并发连接

原因:每个struct tuple4(连接四元组)占 32 字节,每个struct tcp_stream占约 1KB(含两个half_stream),总内存 =1024 * (32 + 2*1024)≈ 2MB。但nids_params.tcp_workarounds(默认 1024)会为每个流额外分配2 * 1024字节缓冲区,实际内存翻倍。
解决:按实际并发量设n_tcp_streams,并调低tcp_workarounds(如 256);用nids_stats.tcp_streams监控实时占用。

现象 3:收到 RST 后,nids_tcp_callback仍被多次调用,tcp->state为NIDS_RESET

原因:RST 是不可靠的,可能重传。libnids 会在收到 RST 后标记stream->nids_state = NIDS_RESET,但不立即销毁流——需等待tcp_timeout()清理。期间若再收到 RST,仍会回调。
解决:在回调中检查if (tcp->nids_state == NIDS_RESET) { /* 忽略后续 */ return; },或依赖NIDS_TIMED_OUT终态。

现象 4:nids_discard(0)导致后续payload永远为 0

原因:nids_discard(0)是合法调用,表示“本次不消费数据”,但r_mem不推进,count不减。若连续多次discard(0),buf被填满后新数据无法写入(因count >= size),get_tcp_data()返回空。
解决:discard(0)仅用于协议解析未完成时的“暂存”,必须在下次回调中明确discard(n);或改用nids_params.tcp_early_retransmit = 1启用早期重传探测。

现象 5:HTTPS 流量中,tcp->payload有数据,但 TLS 握手解析失败

原因:libnids 不解析 TLS。payload是原始 TCP 数据,包含 TLS record header(5 字节)+ encrypted data。若上层直接当明文解析,必然失败。
解决:在nids_tcp_callback中,先检查tcp->payload[0]是否为0x16(TLS handshake),再调用 TLS 解密逻辑;或使用nids_params.tcp_func注册专用 TLS 处理器。


4. 内存管理与资源控制:没有 RAII,只有裸指针和显式生命周期

libnids 生于 C90 时代,没有智能指针、没有 GC、没有异常。它的内存模型是“显式分配 + 显式释放 + 全局池复用”的三段式结构。理解这套机制,是避免 segfault 和内存泄漏的前提。

4.1 全局内存池:struct proc_node的两级复用设计

libnids 用proc_node结构管理所有动态分配对象(ip_frag,tcp_stream,half_stream)。其核心是struct proc_node *proc_nodes全局链表,以及struct proc_node *proc_free空闲链表。

分配逻辑(alloc_proc_node()):

  1. 若proc_free非空,取首节点,memset清零后返回;
  2. 否则malloc(sizeof(struct proc_node))新建;
  3. 新节点插入proc_nodes头部(便于统一释放)。

释放逻辑(free_proc_node()):

  1. 将节点插入proc_free头部;
  2. 不调用 free(),仅回收到空闲池。

这种设计带来两大优势:

  • 避免高频 malloc/free 开销(尤其在高吞吐场景);
  • 所有对象生命周期由nids_run()统一管理:当调用nids_exit()时,遍历proc_nodes释放全部,再清空proc_free。

4.2 缓冲区分配:tcp_workarounds与max_packet的耦合陷阱

nids_params.tcp_workarounds参数常被误解为“TCP 重传尝试次数”,实则是每个half_stream的环形缓冲区大小(字节)。而nids_params.max_packet是 IP 层最大包长(影响ipq->len上限)。

二者耦合点在于:tcp_stream初始化时,half_stream.size = nids_params.tcp_workarounds。若tcp_workarounds过小(如 256),而实际 HTTP 请求超 1KB,则half_stream.count很快达上限,新数据被丢弃,tcp->payload永远只有前 256 字节。

更隐蔽的陷阱是:tcp_workarounds也用于future_queue中单个分片的malloc大小。若设为 256,而某重传分片长 512 字节,则malloc失败,该分片被静默丢弃。

4.3 错误处理:no_mem回调不是可选项,而是生存线

libnids 所有malloc失败均调用nids_params.no_mem(char *msg)。默认实现是fprintf(stderr, "%s\n", msg); exit(1);。这意味着:任何内存分配失败都会终止进程。

常见no_mem触发点:

  • ipq->len过大导致malloc(ipq->len)失败;
  • tcp_workarounds过大,n_tcp_streams过多,malloc总量超限;
  • nids_params.syslog_level启用后,syslog()内部 malloc 失败(罕见但致命)。

提示:生产环境必须重写no_mem回调,记录日志并返回(不 exit),同时触发降级逻辑(如暂停新连接接入)。

4.4 避坑:内存与资源的四大隐形杀手

现象 1:程序运行数小时后突然 crash,core dump 显示malloc失败于ip_fragment.c

原因:ip_timeout过长(如 300 秒),ip_frag积压过多,ipq->len被恶意分片撑大,malloc(ipq->len)耗尽堆内存。
解决:ip_timeout设为 15–30 秒;监控nids_stats.ip_frags,超阈值时主动nids_kill_ip_fragments()。

现象 2:nids_run()CPU 占用 100%,perf top显示memcmp占比极高

原因:ip_frag哈希冲突严重(ip_src^ip_dst^ip_id^ip_p在某些流量下分布不均),导致链表过长,find_frag()频繁memcmp。
解决:升级到 libnids 1.24+(修复哈希算法),或在nids_register_ip()中预过滤广播/组播地址。

现象 3:nids_params.pcap_desc设置为NULL,但nids_run()仍尝试读取 pcap 文件

原因:nids_params.pcap_desc为NULL时,libnids 默认打开eth0(Linux)或\\Device\\NPF_{...}(Windows)。若无权限或设备不存在,pcap_open_live()失败,nids_run()返回错误,但未设errno。
解决:调用nids_init()后,检查nids_errbuf内容;或显式nids_params.device = "any"并确保权限。

现象 4:nids_tcp_callback中tcp->addr字段为全 0

原因:tcp->addr是struct tuple4指针,在tcp_stream创建时初始化。若nids_params.n_tcp_streams不足,新连接无法分配tcp_stream,tcp结构体本身为NULL,自然访问tcp->addr会 segfault。
解决:nids_stats.tcp_streams必须 <nids_params.n_tcp_streams;若接近上限,需扩容或限流。


5. 源码级调试与验证:用gdb和pcap构建你的 TCP 黑匣子

读懂 libnids 源码不是终点,而是为了掌控它。真正的信服来自亲手验证:当你说“它会这样处理”,你能立刻gdb断点、注入特定 pcap、观察内存变化。本章给出一套可复现的验证方法论。

5.1 构建可调试版本:符号、断点、内存快照三件套

编译时务必加-g -O0,并禁用-DNDEBUG(否则assert被移除):

./configure CFLAGS="-g -O0" --disable-shared make # 验证符号存在 nm -C .libs/libnids.a | grep ip_frag_find # 输出应含:0000000000000000 T ip_frag_find

关键断点位置:

  • ip_frag_find():观察分片查找逻辑;
  • add_to_stream():跟踪 TCP 数据写入half_stream的位置;
  • get_tcp_data():查看连续段提取结果;
  • tcp_state_change():捕捉状态迁移瞬间。

提示:在gdb中用watch *(int*)$rdi(x86_64)监视tcp_stream->nids_state变化,比break tcp_state_change更精准。

5.2 构造验证 pcap:用scapy精准投喂边界 case

不要依赖真实流量——它太随机。用scapy构造确定性测试用例:

# test_frag_overlap.pcap:同一偏移的两个分片 from scapy.all import * ip = IP(src="192.168.1.100", dst="192.168.1.200", id=12345, flags="MF") frag1 = ip/UDP(dport=80)/Raw(load="HELLO" + "A"*100) frag1.frag = 0 frag2 = ip/UDP(dport=80)/Raw(load="WORLD" + "B"*100) # 同 offset=0 frag2.frag = 0 wrpcap("test_frag_overlap.pcap", [frag1, frag2])

加载此 pcap 到 libnids,断点insert_fragment(),观察curr->data是否被WORLD覆盖HELLO。

5.3 内存布局可视化:用pahole解析tcp_stream实际结构

libnids 的struct tcp_stream在不同编译器下内存布局可能不同。用pahole查看真实偏移:

pahole -C tcp_stream .libs/libnids.a # 输出示例: # struct tcp_stream { # struct tuple4 addr; /* 0 8 */ # enum nids_state nids_state; /* 8 4 */ # struct half_stream client; /* 16 1040 */ # struct half_stream server; /* 1056 1040 */ # ... # }

这告诉你:client.buf从偏移 16+16=32 开始(half_stream含 16 字节头),server.buf从 1056+16=1072 开始。调试时可直接x/32xb &stream->client.buf查看环形缓冲内容。

5.4 避坑:调试过程的三大幻觉与破除法

幻觉 1:“tcp->payload指向buf起始,所以payload[0]就是第一个字节”

破除:payload是buf + r_mem,而r_mem随discard()推进。用p/x $rdi->client.r_mem(x86_64)查看当前读指针,再计算payload实际地址。

幻觉 2:“断点nids_tcp_callback就能看到所有 TCP 数据”

破除:nids_tcp_callback只在NIDS_JUST_ESTABLISHED或NIDS_DATA时触发。用watch *(int*)($rdi+8)(nids_state偏移)捕获所有状态变更,包括NIDS_CLOSE。

幻觉 3:“nids_stats计数器实时准确,可直接用于监控”

破除:nids_stats是全局变量,libnids 本身不加锁。多线程调用nids_run()时,计数器会竞争。验证方法:在gdb中watch nids_stats.tcp_streams,观察是否被多线程同时修改。


6. 设计取舍的代价与馈赠:为什么 libnids 拒绝拥抱现代 C 标准

libnids 的代码风格会让现代 C 工程师皱眉:全局变量泛滥、无模块化头文件、#define满天飞、goto随处可见。但它不是“写得烂”,而是在 1999 年的硬件与软件约束下,做出的一系列清醒取舍。理解这些取舍,才能判断它是否适合你的场景。

6.1 取舍 1:用宏替代函数,换 CPU 周期

libnids.h中大量#define,如:

#define NIDS_WARN(fmt, args...) do { \ if (nids_params.syslog_level) \ syslog(nids_params.syslog_level, fmt, ##args); \ } while(0)

这避免了函数调用开销(压栈/跳转),在每秒万级分片处理时,省下的是真实 microseconds。代价是:无法用gdb单步进入NIDS_WARN,调试时需展开宏。

6.2 取舍 2:用裸指针替代结构体封装,换内存局部性

struct tcp_stream中,client和server是内联结构体,而非struct half_stream *client。这保证了tcp_stream所有字段在内存中连续,CPU cache line 一次可加载更多数据。代价是:sizeof(struct tcp_stream)固定为 ~2KB,无法动态调整half_stream大小。

6.3 取舍 3:用全局状态替代参数传递,换简洁 API

nids_run()无参数,所有配置通过nids_params全局结构体设置。这简化了 API(用户无需传 context),但彻底放弃线程安全。代价是:无法在单进程内运行多个独立 libnids 实例(如不同 pcap 源)。

6.4 验证你的取舍:一份 checklist

当你考虑在项目中集成 libnids,请逐条核对:

条目符合?说明
流量特征:主要是 IPv4、TCP/UDP、无隧道封装☐libnids 不解析 GRE、VXLAN、IPsec
性能要求:单核 1Gbps+ 吞吐,延迟敏感☐其零拷贝设计在此场景仍有优势
协议深度:只需到 TCP payload,不需 TLS/HTTP 解析☐上层需自行实现,libnids 只保序交付
运维能力:能监控nids_stats、能定制no_mem、能gdb调试 C☐缺一不可,否则线上问题无法定位
安全边界:流量来源可信,或已前置过滤恶意分片☐否则需自行加固ip_frag和tcp_stream分配逻辑

我曾在一个某高校网络行为审计系统中,用 libnids 替换掉 Python 的 Scapy 解析层,QPS 从 800 提升到 12000,但代价是花了整整两周时间,把ip_fragment.c的每一行注释都重写了一遍,并用scapy构造了 37 个边界 pcap 测试用例。从那以后我每次评审网络解析模块,第一句话就是:“先画出你的 TCP 重组状态机,再告诉我为什么不用 libnids 的half_stream。”——不是因为它完美,而是因为它的不完美,恰好暴露了 TCP 协议栈最真实的褶皱。希望帮到你。

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

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

800V高压快充全面爆发:比亚迪1000V/10C、小鹏800V、理想5C、小米5.2C,谁的技术更强?从SiC功率器件到兆瓦闪充深度解析

800V高压快充全面爆发&#xff1a;比亚迪1000V/10C、小鹏800V、理想5C、小米5.2C&#xff0c;谁的技术更强&#xff1f;从SiC功率器件到兆瓦闪充深度解析 新能源汽车的竞争&#xff0c;正在从续航里程转向补能速度。 过去消费者最关心的是一辆电动车能跑多少公里&#xff0c;现…

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

AI产业周度动态:从“更强模型”走向“可执行、可治理、可计价”的系统竞争 (2026年10月19日—10月25日)

目录 一、开源模型的竞争&#xff0c;正在向推理基础设施下沉 二、Agent 进入执行层&#xff0c;但“能做事”带来新的边界问题 三、安全攻防进入双向加速&#xff1a;AI 既是研究员&#xff0c;也是攻击面 四、资本继续追逐算力&#xff0c;但市场开始要求“算力能产生什么”…

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

Go零基础实战第24期:综合项目实战(四)用版本号处理并发更新,避免旧请求覆盖新状态

文章目录 引言:没有数据竞争,为什么还会改错? 一、本篇学习目标与准备 二、锁和版本号,分别解决什么问题? 1. Mutex 保护正在发生的内存操作 2. 版本号判断读取之后有没有发生修改 3. 两个页面的请求会怎样变化? 三、先把版本规则约定清楚 1. 什么时候增加版本? 2. 旧版…

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

3. 栈-lc-python

20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须…

作者头像 李华