news 2026/10/4 5:40:45

从pcap抓包提取国标PS流:RTP载荷重组与GB/T 28181流分析实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从pcap抓包提取国标PS流:RTP载荷重组与GB/T 28181流分析实践

简介:一份面向网络管理员、安全分析人员及协议开发者的实用工具包,用于从tcpdump或Wireshark生成的pcap抓包文件中提取符合国标的网络数据流。资源内置pcap2ps-main的C语言源码(pcap2ps.c)与说明文档(README.md),完整呈现了读取pcap、解析协议字段、按国标规则筛选数据的实现思路。压缩包仅2个文件,体积约5KB,代码精简,适合直接阅读或二次改造。目前已有221人学习下载,对需要掌握抓包文件解析、国标流提取及网络故障排查技能的读者具有参考价值。通过学习该源码,可理解pcap文件格式、关键解析流程以及如何在混合流量中精准过滤目标数据流,为后续网络性能分析、安全检测或格式转换提供基础手段。

1. 从抓包文件里提取国标流:这到底是解决什么问题的工具

做国标设备联调的人,大概率都有过这种经历:平台显示通道在线却拉不出画面,抓包一看,UDP 端口上密密麻麻全是 RTP 包,wireshark 能告诉你这是媒体流,却没法直接告诉你里面到底是 H.264 还是 H.265、帧有没有破、SPS/PPS 在不在。pcap2ps 这类工具就是干这个的:把 tcpdump 或 wireshark 抓到的 pcap 文件,按会话重组 RTP 载荷,剥掉传输层头,把国标 GB/T 28181 承载的 PS 流原样提取出来,落成一个能丢给 ffprobe、VLC、ffmpeg 的 .ps 文件。适合正在做 28181 网关接入、摄像头推流调试,或者被「设备在线但无视频」逼到要抓包的人。不需要解码器也能确认设备到底有没有在推流,这就是它的价值。

2. 国标流为什么装进 PS 壳:抓包前先看懂 RTP 里的载荷

国标 GB/T 28181 在传输视频时走的是 SIP 信令加 RTP 媒体。SIP 负责协商端口、编码格式和 RTP payload type,媒体面把视频切成一包一包的 RTP 数据。这里有个容易让新手懵的点:RTP 的 payload 里装的不是裸 H.264,而是 PS 流(Program Stream)。也就是说,抓包文件里看到的每个 RTP 包,其实是 PS 流的一个切片,PS 流本身又被摄像头编码器按 GOP 切开装进多个 RTP 包。搞不清这层封装关系,后面提取出来的文件长什么样、能不能播,全凭运气。

2.1 PS、TS、ES 三种封装:国标选 PS 是沿袭安防行业习惯

视频编码后的裸数据叫 ES(Elementary Stream),也就是一长串 H.264/H.265 NAL 单元。但裸 ES 没有时间信息、对丢包零容错,所以很少直接塞进 RTP。TS(Transport Stream)固定 188 字节一个包,带 PID 和 PCR,设计目标是广播和易错信道,适合卫星、有线电视那种链路。PS(Program Stream)则是不定长打包,适合可靠信道、本地存储和内网传输,安防行业从早期的 MPEG-2 时代就一直用 PS 做封装,国标 28181 沿用了这条老路,SDP 里通常这样描述媒体:

m=video 10000 RTP/AVP 96 a=rtpmap:96 PS/90000

所以国标流=PS 流切片,RTP 只是搬运工。PS 流里有几个关键的起始码,提取时要靠它们找边界,列成一张表最直观:

起始码(Hex)名称在国标 PS 里的作用
00 00 01 BAPS pack header每个 PS 包的起点,提取时从这里对齐
00 00 01 BBsystem header描述流属性,包括视频编码类型
00 00 01 E0PES video header视频 PES 包的起点,真正的 H.264/H.265 数据在这里面
00 00 01 BDPES private私有流,部分摄像头用它推音频或扩展数据

记住这张表就够了。BA 告诉你「一个 PS 包从哪开始」,E0 告诉你「视频数据在哪」,BB 告诉你「编码器声明自己是什么格式」。提取国标流的本质工作,就是把 RTP 载荷拼回去之后,靠这些起始码重新找到边界。

2.2 抓包前置准备:镜像口、过滤条件与抓多长的判断

抓包位置很讲究。如果问题出在摄像头侧,直接在摄像头出口网卡抓;如果问题在平台侧,需要在交换机上做端口镜像,把平台下行口的数据复制一份出来。镜像口配置不在本文范围,但记住一个原则:宁可在平台侧抓,不要在设备侧抓完再拿两台机器对时间,否则信令和媒体的对应关系要靠猜。

抓包命令用 tcpdump 最省事,职业做法是直接抓全部 UDP 而不预先过滤端口,因为国标媒体端口是动态协商的,你事先不知道它落在哪个端口:

tcpdump -i eth0 -s 0 -w gb28181.pcap udp

参数说明:-s 0表示整包捕获,不截断。默认 snaplen 是 96 字节,只够抓 IP/UDP 头,RTP payload 全丢,pcap2ps 提取出来就是一堆空壳。udp过滤掉了 TCP 干扰。如果你已经通过 SIP 抓包知道了媒体端口,可以收紧成udp port 10000,但大多数情况下我没这么做——多抓一点,事后在脚本里过滤更方便。

抓多久合适?我的经验是复现问题前后各抓 10 到 30 秒就够,不要抓几小时。pcap 文件一旦超过几个 GB,解析脚本跑得慢不说,wireshark 打开都卡。而且国标流分析只需要看「有没有视频、有没有花屏、有没有丢包」,几十秒的数据足够判断故障模式。

注意:wireshark 默认保存的是 pcapng 格式,不是 classic pcap。pcap2ps 这类按 pcap 结构写的解析脚本读到 pcapng 会直接报错,先用 tshark 转一下格式再喂给脚本。

tshark -r capture.pcapng -F pcap -w capture.pcap

-F pcap指定输出格式为老式 pcap。这一步建议养成习惯,抓完包先看一眼后缀,.pcapng就直接转,别等脚本跑挂了才想起来。

另外说一句,wireshark 自带的 Telephony -> RTP -> Stream Analysis 也能导出 RTP payload,但它的导出是为音频分析设计的,会做重排序和 jitter 统计,对 PS 流这种面向解码器的数据来说,导出结果经常带额外处理痕迹,不如自己按原始顺序拼一遍干净。真到了要精确提取的时候,还是自己写脚本可控。

3. pcap2ps 第一步:解析 pcap 并按会话分出干净的 RTP 载荷

理解了 PS 封装结构,现在动手写一个最小可用的 pcap2ps。我的习惯是只用 Python 标准库,不用 scapy 这类第三方包。原因很现实:现场机器可能没有外网,pip install 都可能绕半天,struct 解析是零依赖方案,代码也就一百来行。这一章先搞定 pcap 解析和 RTP 载荷提取,下一章再做 PS 组包。

3.1 读 pcap 文件:magic、字节序与每包记录结构

classic pcap 文件结构很简单:24 字节全局头,后面跟着一帧一帧的数据。全局头里最重要的就是前 4 字节的 magic,它决定了整个文件的字节序。小端机器上写入的 magic 是d4 c3 b2 a1,大端则是a1 b2 c3 d4。这段判断写错,后面解析出来的包长全乱,这是 pcap 解析里最容易翻车的点。

import os, struct def read_pcap(path): """读取 classic pcap,返回 (linktype, frame_bytes) 列表""" frames = [] with open(path, 'rb') as f: g = f.read(24) if len(g) < 24: raise ValueError('文件太小,不是有效 pcap') magic = g[:4] if magic == b'\xd4\xc3\xb2\xa1': le = '<' # 小端 elif magic == b'\xa1\xb2\xc3\xd4': le = '>' # 大端 else: raise ValueError('不是 classic pcap,可能是 pcapng,先用 tshark 转换') # linktype 在全局头偏移 20 处,4 字节 linktype = struct.unpack(le + 'I', g[20:24])[0] while True: rh = f.read(16) if len(rh) < 16: break ts_sec, ts_frac, incl_len, orig_len = struct.unpack(le + 'IIII', rh) data = f.read(incl_len) if len(data) < incl_len: break frames.append((linktype, bytes(data))) return frames

参数说明:le是文件字节序,只用于解析 pcap 头里的长度字段。incl_len是实际写入文件的帧长度,orig_len是原始帧长度,正常情况下两者相等;不等说明抓包时做了截断,这种情况下面解析 UDP 头时要小心。linktype是链路层类型,以太网是 1,Linux cooked 是 113。如果你在抓包机上用了tcpdump -i any这种抓所有接口的写法,得到的就是 113,不是以太网。我一般建议直接指定物理网卡,避免 cooked 头带来的额外解析工作。

3.2 剥到 UDP 载荷:跳过以太网头、IP 头

拿到链路层帧后,要一路剥到 UDP。以太网头 14 字节,其中frame[12:14]是 EtherType,0x0800才是 IPv4。IPv4 头里版本和 IHL 共用一个字节,ip[0] >> 4是版本,ip[0] & 0x0F是头部长度(单位 4 字节)。协议字段在ip[9],17 是 UDP。

def udp_payloads(frames): """从帧列表里提取 UDP 载荷,返回 (sport, dport, payload) 生成器""" for linktype, data in frames: if linktype != 1 or len(data) < 14: continue # 非以太网直接跳过 if data[12:14] != b'\x08\x00': continue # 只要 IPv4 ip = data[14:] if len(ip) < 20 or (ip[0] >> 4) != 4: continue ihl = (ip[0] & 0x0F) * 4 if ihl < 20: continue if ip[9] != 17: continue # 17 = UDP udp = ip[ihl:] if len(udp) < 8: continue sport, dport = struct.unpack('!HH', udp[0:4]) # 取出 UDP payload,RTP 从这开始 yield sport, dport, bytes(udp[8:])

这里有个细节:UDP 头里有个 length 字段,按规范它等于 UDP 头加 payload 的总长度。但实际帧可能被截断,所以直接按udp[8:]取更稳妥。另外 RTP/UDP 头里的字段一律是大端序,和 pcap 文件的字节序是两码事,都用!前缀解。这个混淆过不少人,文件头用le(由 magic 决定),IP/UDP/RTP 头用!(网络序),别混。

3.3 按 SSRC 分组并剥离 RTP 头:滤掉 RTCP,处理扩展头

RTP 头至少 12 字节:版本(2 bit)、填充位 P、扩展位 X、CSRC 计数 CC(4 bit)、payload type(7 bit)、序列号、时间戳、SSRC。其中最容易忽略的是扩展头——X 位置 1 时,固定头后面还跟着 4 字节扩展头描述和若干扩展数据,不跳过它,拼出来的 PS 流会花成败絮。

def split_sessions(udp_packets): """按 SSRC 把 RTP 载荷归组,返回 {ssrc: bytearray} 和会话元信息""" sessions = {} meta = {} for sport, dport, pkt in udp_packets: if len(pkt) < 12: continue if (pkt[0] >> 6) != 2: # RTP/RTCP 版本都是 2 continue pt = pkt[1] & 0x7F if 192 <= pt <= 223: # RTCP 的 PT 范围,必须滤掉 continue ssrc = struct.unpack('!I', pkt[8:12])[0] cc = pkt[0] & 0x0F # CSRC 数量 x = (pkt[0] >> 4) & 0x01 # 扩展头标志 hlen = 12 + cc * 4 # 固定头 + CSRC if x: if len(pkt) < hlen + 4: continue ext_len = struct.unpack('!H', pkt[hlen:hlen + 2])[0] hlen += 4 + ext_len * 4 # 扩展头描述 4 字节 + 扩展数据 if ssrc not in sessions: sessions[ssrc] = bytearray() meta[ssrc] = {'sport': sport, 'dport': dport, 'count': 0} sessions[ssrc].extend(pkt[hlen:]) meta[ssrc]['count'] += 1 return sessions, meta

逻辑说明:pkt[0] >> 6 == 2校验版本,pkt[1] & 0x7F取 payload type 时要清掉最高位的 marker 位。RTCP 的 PT 分布在 192 到 223,最常遇到的是 200(SR)和 201(RR),直接用这个范围滤。网上流传的「RTP 是偶数、RTCP 是奇数」是按端口老约定总结的,不是 PT 规律,不要套用。

分组为什么用 SSRC 而不是 IP 加端口?因为一个抓包里经常同时存在多路摄像头,四元组会变(摄像头 NAT 出去后源端口可能变化),但 RTP 层的 SSRC 是会话级唯一标识,一路流从头到尾不会变。这就是后续多会话批处理的基础。

跑完这一步,sessions字典里的每个 key 就是一路媒体流,对应的 bytearray 是所有 RTP 载荷按抓包顺序拼接的原始数据。但这个数据还不能直接播——开头可能不是 PS 包起始处,中间可能有丢包留下的空洞。下一章处理这两个问题。

4. pcap2ps 第二步:PS 起始码对齐,把载荷落成可播放文件

上一章得到的拼接数据是「RTP 载荷直接拼接」,存在两个天然缺陷:抓包开始的时刻大概率落在某个 PS 包中间,文件开头是半个包;丢包会在中间留下损坏的拼接点。PS 流自带起始码,解码器遇到损坏处会找下一个 BA 重新同步,所以我们的任务很明确:把数据对齐到第一个 BA,然后原样落盘,剩下的交给播放器容错。

4.1 用 BA 起始码对齐:为什么不是从 E0 开始

对齐的逻辑就是找00 00 01 ba这个四字节序列。找到后把之前的内容全部丢弃,从 BA 处开始写文件。如果整个载荷里一个 BA 都找不到,说明这路流根本不是国标 PS 流,可能是裸 H.264 RTP 或者其它私有封装,直接跳过。

def align_ps(data: bytes): """把拼接数据对齐到第一个 PS pack header,找不到返回 None""" start = data.find(b'\x00\x00\x01\xba') if start < 0: return None return bytes(data[start:])

为什么不从 E0 对齐?E0 只是 PES 包的起点,不是 PS 包的起点。从 E0 开始丢掉了前面的 pack header 和 system header,虽然解码器也能解出视频,但部分播放器会因为没有 system header 而无法确定流的时长和编码参数,显示成未知时长。从 BA 开始是唯一正确的对齐方式,这也解释了为什么抓包时不用刻意卡在某个起始码位置——对齐是提取阶段的事,抓包阶段只要保证包完整就行。

顺手写一个统计函数,提取完输出几个关键指标,用来判断「提取是否成功」:

def analyze_ps(data: bytes): """统计 PS 流的关键起始码数量,用于判断流是否完整""" return { 'ps_packets': data.count(b'\x00\x00\x01\xba'), 'system_headers': data.count(b'\x00\x00\x01\xbb'), 'pes_video': data.count(b'\x00\x00\x01\xe0'), }

参数说明:ps_packets是 PS 包总数,正常 10 秒的视频大概有 100 到 300 个,取决于关键帧间隔和码率。system_headers理论上至少有 1 个,为 0 说明抓包起点在流中间或者流本身不标准。pes_video是要重点关注的值,可以理解为视频帧切片数,如果为 0 说明这路流里没有视频,只有音频或者私有数据。

4.2 完整的 pcap2ps 主流程:遍历会话、命名、落盘

把前两章的代码串起来,一个最小工具就成型了:

def pcap2ps(pcap_path: str, out_dir='out'): """从 pcap 中提取所有国标 PS 流,每个会话输出一个 .ps 文件""" frames = read_pcap(pcap_path) sessions, meta = split_sessions(udp_payloads(frames)) os.makedirs(out_dir, exist_ok=True) for ssrc, payload in sessions.items(): aligned = align_ps(bytes(payload)) if aligned is None: print(f'ssrc={ssrc:08x}: 未找到 PS 起始码,跳过') continue stat = analyze_ps(aligned) name = f'{ssrc:08x}_{stat["ps_packets"]}pkt.ps' with open(os.path.join(out_dir, name), 'wb') as f: f.write(aligned) print(f'{name} system={stat["system_headers"]} ' f'video={stat["pes_video"]} packets={meta[ssrc]["count"]}')

参数说明:文件名用 SSRC 的十六进制加 PS 包数,这样能直接和 wireshark 的 RTP stream 对应上,排查时不用猜哪个文件是哪路流。ps_packets明显偏少时,说明这路流丢包严重,后续播放画面大概率花屏。输出目录建议单独建,一次抓包可能提取出多路流,混在一起很难看。

4.3 时间戳和丢包:哪些要处理,哪些不要碰

这里有个关键认知要建立起来:裸 PS 文件不要求 RTP 时间戳连续。播放器播放 PS 流时,依靠的是 PES 头里的 PTS/DTS(33 bit,90kHz),不是 RTP 时间戳。国标摄像头在推流时,PES 头里基本都带 PTS,所以拼接后的文件直接可播。RTP 时间戳的作用是会话层的关联和丢包检测,和 PS 内部的呈现时间不是一回事,不要混着看。

那丢包要不要处理?我的建议是不要手工补包。PS 流有起始码做同步,解码器遇到损坏的 PES 会丢弃数据直到下一个 BA 重新同步,表现是花屏一闪然后恢复,这正是播放器的正常容错行为。你手工补一个假包进去,反而可能让解码器把错位数据当真,渲染出更奇怪的东西。唯一值得做的处理,是记录 RTP 序列号跳变的位置,用来精确定位丢包时间点,这个后面会讲。

提示:输出文件扩展名写.ps而不是.mpg,ffprobe 和 ffmpeg 会按 MPEG-PS demuxer 自动识别。如果 VLC 打不开,试试ffplay -i output.ps,或者把文件改名.mpg再拖进 VLC,这是播放器识别策略的问题,不是文件坏了。

跑完这步,你已经能从任意 pcap 里提出可播放的国标流了。下一章讲这过程中最容易翻车的地方。

5. 提取国标流的四个必踩坑:现象、原因与排查路径

下面的坑都是真机抓包时踩过的,按概率排序。每一个都是「现象 -> 原因 -> 解决」三段式,值不值得照着做,看第一条就够说服你。

5.1 pcapng 当成 pcap 读,脚本直接崩

现象:脚本跑起来第一行就报ValueError: not classic pcap,而同一个文件在 wireshark 里打开完全正常。

原因:wireshark 从 1.12 版本开始默认保存 pcapng,这是一种 block 结构的格式,文件头是0A 0D 0D 0A,和 classic pcap 的d4 c3 b2 a1完全不同。很多教程里的解析脚本只认 classic pcap,没做 pcapng 适配。

解决:不改脚本,用 tshark 转格式,这是最省事的路:

tshark -r input.pcapng -F pcap -w output.pcap

如果你不想依赖 tshark,也可以给脚本加一个分支:读到0A 0D 0D 0A时按 pcapng 的 Section Header Block 和 Interface Description Block 解析。但 pcapng 的 option 字段是 TLV 结构,写起来至少多几十行,前面那句「先转格式」其实是最快路径。

5.2 RTP 扩展头没剥离,视频花成一片

现象:提取出的 PS 文件用 ffprobe 能识别出 H.264,但画面大面积花屏、时间轴乱跳,甚至丢帧严重。

原因:部分国标摄像头在 RTP 头里开了扩展(X 位置 1),扩展头里有 Profile 定义、长度字段和一些厂商私有数据。如果只按固定 12 字节剥离 RTP 头,扩展数据会被当成 PS 载荷拼进去,起始码错位,解码器从错位处开始解析直到碰上 BA 才重新同步,于是表现为周期性花屏。

解决:在剥离 RTP 头时检查 X 位,按第 3 章的hlen计算公式跳过扩展。排查时可以在 wireshark 里点开一个 RTP 包,看 Header 里 Extension 字段是否为 1,一眼就能确认。这个坑最阴险的地方在于 ffprobe 依然能识别出编码格式,让人误以为文件是好的。

5.3 RTCP 滤错:按「奇数 PT」判断是老黄历

现象:提取的文件里 PS 包数量正常,但文件里混着大量不可解析的垃圾数据,用 ffprobe 看时长忽长忽短。

原因:网上大量教程写「RTP 偶数端口、RTCP 奇数端口」,于是按源端口奇偶性滤 RTCP。但这条规律源于早期 RTP 端口分配的约定,不是协议层规则。国标平台在协商媒体端口时完全不遵守奇偶,而 RTCP 的 payload type 其实是 200 以上的数字,和端口奇偶没有对应关系。按端口过滤,要么放过 RTCP,要么误伤 RTP。

解决:按 payload type 范围过滤,RTCP 的 PT 分布在 192 到 223(SR 是 200,RR 是 201),代码里if 192 <= pt <= 223: continue一行就解决。排查时在 wireshark 里看rtcp过滤后的包数占总量比例,如果超过 1%,说明你的过滤逻辑有问题。

5.4 丢包之后拼接点损坏:别手工补包,交给解码器容错

现象:播放提取出的 PS 文件时,前半段正常,某时间点之后连续花屏几秒,然后恢复。看 PS 包数量,和正常值差别不大。

原因:RTP 丢包造成 PS 流断续。摄像头把一个大 PS 包拆成多个 RTP 发送,中间丢一个,拼接出来的 PS 包就少一块,PES 头里的长度字段和实际内容对不上。解码器按错误的 PES 长度解析,直到遇到下一个 BA 才恢复同步。

解决:不要在提取阶段补假数据。正确的做法是保持原样落盘,播放时用ffmpeg -err_detect ignore_err降低容错带来的连锁问题,分析时只看丢包点之前的帧。如果想精确定位丢包位置,在拼接阶段检查 RTP 序列号是否连续,把跳变点输出成 CSV。这个比修文件有用得多——它告诉你的是网络质量,而网络质量才是问题根源。

这四个坑有一个共性:都是「看似能跑、实则输出不达标」的隐性错误。养成提取后先看统计指标、再 ffprobe 验证的习惯,能省掉大量排查时间。

6. 验证与进阶:用 ffprobe 验收 PS 流,再解出 H.264 裸流

提取完不等于提取对,先花十秒验证:

ffprobe -v error -show_entries stream=codec_name,width,height,r_frame_rate \ -show_entries format=duration -of default output.ps

期望看到codec_name=h264或h265,width和height非零。如果duration为 0 或 N/A,说明 PES 时间戳有问题,多半是抓包起点不在 PS 包边界或丢包严重。更直观的验证是用ffplay -i output.ps直接播放,能看到画面就说明流可解。两个都不行的时候,回到第 5 章的统计指标逐项查。

验证通过后,进阶需求通常是解出裸 H.264。这一步不需要解码,ffmpeg 用 copy 模式把 PS 容器脱掉就行:

ffmpeg -i output.ps -map 0:v -c:v copy output.h264

-c:v copy是关键参数,只做解封装不解码,速度极快,一个百兆的 PS 文件几秒就能完成。得到的裸流可以直接喂给帧级分析工具,或者和其它模块做对接。

批处理大量 pcap 时,一个 for 循环就够了:

for f in *.pcap; do python pcap2ps.py "$f" "out_${f%.pcap}" done

我现在的习惯是:接到国标视频问题,先抓 20 秒包跑一遍 pcap2ps,确认 PS 流里有东西、起始码数量正常,再回头看信令和平台日志。这一步能过滤掉一大半「设备根本没推流」「推了但格式不对」的伪故障,少走很多弯路。希望帮到你。

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

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

STC32G12K128开发环境搭建:从Keil兼容到VSCode跨平台实战

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

作者头像 李华
网站建设 2026/10/4 5:34:29

文章标题怎么填才精准

打开汇写&#xff08;https://www.huixielunwen.com/tool/graduationThesis&#xff09;的毕业文章页面&#xff0c;最显眼的就是顶部那个大输入框&#xff1a;"请输入完整标题&#xff08;2-50 字&#xff09;"。别小看这一行字&#xff0c;你填的标题质量直接决定 …

作者头像 李华
网站建设 2026/10/4 5:34:23

WebSocket聊天室实战:Java后端与jQuery前端实时通信全解析

简介&#xff1a;WebSocket聊天室是一份基于JavaScript、jQuery与Java的实时通讯应用源码&#xff0c;适合学习Java Web与前端交互的开发者。项目覆盖多人聊天、私人对话及在线客服场景&#xff0c;通过WebSocket实现低延迟双向通信&#xff0c;并包含用户登录验证、在线用户列…

作者头像 李华
网站建设 2026/10/4 5:32:53

STM32 UART高可靠通信实战:DMA双缓冲与波特率误差补偿

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

作者头像 李华