
1. 为什么抓SRT不是从推流端开始而是先看握手指令我第一次被SRT握手控制包“教育”是在一条远程直播链路半夜卡死的时候。当时推流端显示码率正常播放端却隔几秒就缓冲一次。我习惯性地去查带宽、查丢包、查CPU折腾了半小时一无所获。后来把UDP 9000端口的流量拖进Wireshark按时间线逐包看才发现连接根本没有真正建立起来——每一次播放器都在原地重发握手控制包压根没进入媒体数据阶段。从那一刻起我养成了一个习惯排查SRT流第一件事不是看视音频负载而是先看握手。SRTSecure Reliable Transport本质上是基于UDP的应用层可靠传输协议用来承载高质量、低延迟的直播视频。OS层面的UDP没有“连接”概念所以SRT把连接建立完全放在了应用层。谁来主动连接、连接双方是什么身份、用哪个库的版本、要加密还是明文、推流时携带的Stream ID是什么、缓冲延迟设多少——这些信息几乎全部由握手控制包承载。也就是说握手包不是一句“你好”就完事的开场白它是整个SRT会话的“身份证合同”。这也是为什么标题是“握手控制包”而不是“SRT数据包”。数据包只负责搬运TS流或MPEG-TS格式的码流出了问题往往只能看到宏观现象而握手控制包里面带着十几条协商信息任何一条不一致都可能让后面的数据流变成一团乱麻。对做直播运维、瘦服务端开发或者底层协议分析的人来说先把握手读透了等于拿到了一把打开SRT排障大门的钥匙。我在之后的很多次实测里都验证过这个逻辑如果一个连接能稳定跑完全程握手包的协商参数一定是干净一致的如果频繁重连、画面黑屏、CPU不高但音频断断续续八成能在握手阶段找到一条不匹配的字段。下面我就从包结构、模式差异、扩展字段到抓包排查把SRT握手控制包完整拆一遍。2. 握手控制包的包体结构一场高信息量的网络“相亲”先说整体形态。SRT一个UDP包可以分两类数据包和控制包。握手属于控制包而且通常是连接发生后的第一个控制包。拿Wireshark解析结果看一个典型的SRT握手控制包由“公共控制头 握手载荷 握手扩展字段”三块拼起来公共头负责标识这是谁发出的、发给谁的、什么时刻发的载荷负责双方基本身份和能力扩展字段负责附加的个性化需求。2.1 公共控制头时间戳与Socket ID公共头里最容易被忽略但也值得重视的是时间戳和目的Socket ID。时间戳是微秒级的由发送方生成。它不只是给Wireshark排序用的SRT在握手之后计算RTT、做丢包重传和缓冲时间同步都会参考握手阶段的时间基准。第一次握手包里的时间戳往往会成为后面TSBPD协商的锚点如果两边时间戳基准对不上后面就可能出现“包到了但被判定过期”这种诡异问题。目的Socket ID是一个32位标识用来把UDP包对应到某个SRT会话实例。第一次从Caller发出去的握手包里这个值常见为初始减一或由库自动生成Listener收到后再用自己的Socket ID回包。它的作用类似门牌号这么多UDP连接共用同一个端口时没有这个字段系统根本不知道该把包交给谁。排障时如果发现同一个UDP端口上同时出现多个不同的Socket ID说明上层可能起了多个SRT连接实例别被这种“串包”干扰判断。再往前一点公共控制头里还有“控制包类型”字段值0对应握手Wireshark里会显示为Handshake。抓包时不要只看注释层写着SRT就认为是握手得落到控制包类型这一层确认。CMO和我都吃过这个亏把ACER包当成握手包去分析浪费了十分钟。2.2 握手载荷版本、状态、连接类型与加密参数公共头后面紧跟着握手载荷这一块字段较多我把常见字段整理成一张表抓包时对照着看就不会晕。字段含义排障价值Peer Version对端SRT库版本号比如1.4.3确认双方库版本跨度Peer State握手进行到哪一步的状态标识判断卡在请求还是应答Connection TypeCaller、Listener或Rendezvous快速识别模式配错Socket ID本端会话标识区分同一端口上的多路连接Encryption Field加密算法标识比如AES-128/192/256或无加密排查密码不匹配Extension Block后续扩展字段的起点标志定位Stream ID等参数Peer Version就是SRT库的版本。为什么强调它因为SRT规范一直在演进老版本库和新版本库握手时对相同字段的解释可能有差异。比如某直播中台从1.2升级到1.4后原本能连的客户端突然反复重连抓包发现Client报1.2版本的握手包Server用1.4版本字段应答两边虽然都认识“握手”但对扩展字段的解析逻辑不同最终谁也没等来对方的数据。Peer State一般不是一个简单的“成功/失败”它会在握手阶段多次变化。以Caller/Listener模式来看大致是“初始请求→收到Cookie→带Cookie确认→完成”。Wireshark里能看到不同握手包的状态值在跳。如果状态反复回到“请求”而不进入“确认”基本可以判定在Cookie交换环节出了问题。Connection Type就是握手时双方约定的连接模式。这个字段往大说影响了整个数据流的方向和重传策略Caller主动发起连接Listener被动监听Rendezvous则两边都在发起、没主没次。连接类型不一致是握手失败的高频原因之一比如一边配的Caller一边配的Caller两边都在“主动打电话”电话占线没人接或者一边Listener一边Listener大家都不拨号永远无法建立。Encryption Field则记录加密需求。SRT的加密基于AES握手阶段要先告诉对端“我用哪种长度”。常见的有0表示不加密、AES-128对应16字节密钥、AES-192对应24字节、AES-256对应32字节。如果到后面KMREQ/KMREP密钥交换阶段对不上客户端往往表现为“握手过去了但立刻断流”。不要把加密参数当成和握手无关的存在。SRT的加密不是数据包层单独搞的握手载荷里已经把加密算法预声明了后续的密钥材料交换是握手扩展的延续。所以分析握手包时看不到加密字段的可以直接期待对端跑明文看到加密字段的密码不匹配大概率会在这里留下痕迹。3. 三种连接模式下的握手差异Caller、Listener与Rendezvous握手过程并不是所有模式都一个样。这一点对排查尤其关键因为很多人拿到一个握手包就习惯性套“请求—应答”去看遇到Rendezvous就懵了。我按抓包时的实际时间线把三种模式拆开讲。3.1 Caller/Listener的两次往返与Cookie机制Caller/Listener类似打电话Caller先拨号Listener接听。完整的握手大致会看到这么几步Caller向Listener的端口发送第一个握手控制包包内带着自己的版本、连接类型、Socket ID。Listener收到后回一个握手控制包同时附带一个一次性Cookie。这个Cookie不是用来登录的是为了防止有人伪造IP刷端口攻击。Listener通过Cookie验证“你确实能收到我回发的包”。Caller拿到Cookie后再次发送包含该Cookie的握手控制包。Listener校验Cookie通过后回最后一个握手确认包之后才开始真正的媒体数据传输。这里有个常见的误区很多人以为SRT和TCP一样三次握手结束就建立了看到前三个包出现就觉得“应该通了”。实际上Caller/Listener模式中最后一个确认包和之后的KMREQ等扩展交换同样重要。我遇到过一台编码器它发了三步握手后迟迟没有等来确认包又因为超时反复从头开始重发看起来网络里全是握手实际上一帧画面都没有。3.2 Rendezvous为什么只需要一次对称握手Rendezvous模式要放到另一个语境里理解。它不区分主动和被动适合点对点场景尤其是两边都在NAT后面、希望互相“找”到对方的情况。抓包时你看到的不是一条线上“请求—响应”而是两条线互相发握手包。“对称”这两个字怎么理解两边都认为自己是连接发起方所以会同时向外发送握手控制包也都可能在同一个端口上监听。收到对端包后双方各自回应再完成参数交换。理论上双方不需要像Caller/Listener那样由某一方存心挖坑、另一方跳过坑再回来所以看起来更简洁。但简洁不意味着好配。Rendezvous模式下两边的时间戳、Socket ID和版本必须能互相“自洽”任何一边解析不了对方的扩展字段握手就中断了。再加上如果两边启动时间差距很大先启动的那一方会一直重发直到另一台上线。很多人在P2P拉流时手一抖把其中一边配成Caller另一边配成Rendezvous结果连接怎么都建立不了。抓包看到只有单方向重复发握手第一反应就应该是检查模式字段两边是不是都一致。3.3 模式对照表抓包时第一眼该看什么模式握手形态常见场景抓包特征Caller主动发起经过Listener的Cookie挑战推流端、播放器从单端口向Socket ID发起持续出现握手确认Listener被动响应下发Cookie后等待确认接收服务器、网关等待连接收到握手后返回Cookie字串Rendezvous双向同时发起对称协商P2P、NAT穿透两个IP端口互发握手包不区分主从在实际直播系统中Caller/Listener最常见。OBS推流到SRT服务器播放器走SRT拉流基本都是Caller往Listener撞。Rendezvous多数用在两台编码器直接对传、或者两个不受管制的设备间建立临时链路。抓包时先把连接类型字段看了再开始分析后面的字段能省一半时间。我自己踩过好几次“拿着一份Listener的握手指南去排查Rendezvous故障”的坑所以特别想说一句协议分析的第一步永远是确认当前场景不是套模板。4. 握手扩展字段的实战价值Stream ID、TSBPD与密钥协商如果说握手载荷是双方身份和基本能力握手扩展字段就是个性化的“需求清单”。SRT允许在握手包里附带一系列扩展每个扩展用“命令号长度值”的方式排布。所谓TLVType-Length-Value就是这么个概念。Wireshark解析到位后你在包里看到的往往不是裸的数字而是已经翻译过的友好名称。4.1 扩展字段的TLV组织方式握手扩展区不是一个独立的包它是嵌在握手包后面的数据结构。每个扩展项有三个部分一个数字命令号标明这是什么扩展一个长度字段告诉解析器后面要读多少字节最后是具体值。解析时按顺序读读到长度越界就说明包截断了或者扩展区被误判。排障时如果Wireshark显示“malformed packet”首先要怀疑抓包大小设置不全pcap的snaplen太小然后是握手包在传输中被UDP切碎最后才考虑协议栈本身的问题。常见的握手扩展命令我用表格整理一下扩展名含义排障价值SRT_CMD_TSBPD时间戳基准与包延时协商延迟参数不对时会在这里出问题SRT_CMD_STREAM_ID流标识字符串鉴权失败时优先查这里SRT_CMD_KMREQ密钥材料请求加密连接建立时出现SRT_CMD_KMREP密钥材料响应密钥协商完成后出现SRT_CMD_MSS最大分段大小某些MTU协商异常时查找这些扩展不一定每次抓包都齐全。比如不加密场景下就没有KMREQ/KMREP只有明文握手后直接进入媒体传输。如果看到本应出现的密钥协商扩展缺失那就先查两边的加密参数是否一致别急着看网络路径。4.2 Stream ID如何实现接入鉴权与路由Stream ID是我在实战中用得最多的握手扩展之一它几乎相当于应用层的URL路径。推流端在握手包里带上一串自定义字符接收端Listener可以在握手上层直接做路由和鉴权不需要等媒体数据到位。比如我们可以约定一种格式streamid#!::rlive/camera1,mpublish,ualice,ppasswd这里的r通常代表资源路径m代表用途publish还是playu和p可以放业务侧的用户名密码。Listener端收到握手包后会在建立媒体会话之前做校验。校验不过就直接不回复最终握手确认客户端只会看到“连接超时”。抓包时如果发现对方发了包含Stream ID的握手包但后续无确认、无扩展、无数据基本就是鉴权被拒。很多人会把Stream ID的功能和TCP层的四元组、IP白名单搞混。IP白名单只能判断“从哪里来”Stream ID可以判断“来干什么、要连哪一路视频”。中大型SRT网关会按照Stream ID把不同直播间、不同摄像头的信号调度到不同的处理单元这已经远超普通握手检查的范畴了。如果你在排查接入右侧的问题优先看Stream ID字段是否和你配置的完全相同。有时候只是少了个#!::前缀握手就变得像陌生人互不交谈。4.3 加密参数协商KMREQ/KMREP与握手的衔接SRT的加密不是“握手确认后立即加密”而是有一套自己的密钥交换流程。握手载荷里的Encryption Field先声明加密算法和密钥长度随后在握手扩展区发起密钥材料请求KMREQ对端返回KMREP。这一步和握手主流程穿插在一起虽然中间还会涉及放在有没有共同持但抓包时你完全可以把KMREQ/KMREP当作握手的第二阶段。如果密码不匹配常见的表现是握手主流程已经完成双方都回确认了但KMREQ发出后KMREP迟迟不来或者直接回错误然后连接被迅速丢弃。从Wireshark时间轴上看就是“一堆握手成功后紧接着回复一个断开或超时”。这种场景下别去翻媒体数据包直接在扩展区检查密钥协商的状态字段再对照两边配置的passphrase与pbkeylen是否一致通常十分钟内能定位。另外提醒一句密钥长度要和加密算法匹配。AES-128需要16字节AES-192需要24字节AES-256需要32字节。在URL参数里写错pbkeylen或者漏写passphrase抓包时大概率会在KMREQ/KMREP这一层看到异常。与其依赖服务器日志不如先看握手扩展区有没有异常记录。5. 用Wireshark复现一次完整握手从抓包到定位失败纸上谈兵差不多够了下面进入实操。不管你是运维还是做协议分析用Wireshark把SRT握手过程完整看一遍比任何文档都直观。5.1 打造能看懂SRT的抓包环境首先你需要一个能触发SRT握手的流量源。本地起一个最小闭环就行不一定要真推直播。我刚入门时用的是ffmpeg加ffplay一条命令行就搞定。推流端并以Caller模式发出握手ffmpeg -re -f lavfi -i testsrcsize640x480:rate25 -c:v libx264 -preset ultrafast -tune zerolatency -f mpegts srt://127.0.0.1:9000?modecallerlatency200拉流端以Listener模式等待连接并回握手ffplay -probesize 32 -analyzeduration 0 srt://127.0.0.1:9000?modelistenerlatency200跑起来后在Wireshark里选择Loopback网卡本地流量或你真正的物理网卡过滤条件先写一个保守的udp.port 9000如果Wireshark能自动识别SRT协议你会在Protocol列看到SRT。如果只是显示UDP说明当前版本没有带SRT解析器或需要手动启用协议解析。可以在“Protocols - SRT”里确认一下或者把包导出到较新版本的Wireshark里再看。5.2 实测中握手成功和失败的报文差异抓下来后不要急着滚动先在过滤栏里把范围缩窄。不同Wireshark版本的字段名可能不同常见有两种写法我都说一下srt.packet_type 0 srt.proto.type 0如果一条不生效就换另一条。握手包通常是整个抓包里最先出现的那几包落在TCP/UDP连接之外。你会看到类似这样的显示SRT Control PacketControl Packet Type: Handshake (0)Timestamp: xxxDestination SRT Sock ID: xxHandshake:Version: 1.4.3Connection Type: CallerEncryption: AES-128Extension Field...一个成功握手的时序通常大概长这样第一个Caller握手包目的Socket ID为0或初值。Listener回握手携带Cookie、自己的Socket ID。Caller再次发握手携带Cookie和Stream ID。Listener回最后握手确认。加密场景下紧接着可能是KMREQ/KMREP。经过很短间隔开始出现SRT数据包。失败的时序则有很强的“重复”特征。最典型的就是同一个方向反复发握手请求包间隔几百毫秒或一两秒但一直没有对应的回应或者回应了几次后始终没有进入数据包阶段。这种周期性重发说明双方在“当前连接能不能继续”上没有达成一致卡在了中间某一步。5.3 一次“播放器反复重连”的现场排查链路去年帮朋友排查过一个后台不友好的播放器。画面黑屏播放器每隔两三秒就重连一次服务端日志只显示“new connection”然后“disconnect”没有任何错误码。我用Wireshark在播放器一侧抓包过滤udp.port 9000后看到了大量握手控制包。排查链路是这样走下来的发现存在周期性的“请求—响应—断开”模式前两个握手包能正常交换但第三个握手确认迟迟不来。在第二个握手包里看到了Listener返回的Cookie和Server端SRT版本在第一个握手包里看到了Caller声明的Stream ID。对比服务端配置发现服务端限制了允许的Stream ID白名单而播放器发送的Stream ID末尾多了一个空格字符。修正播放器URL参数后重新抓包第三、第四个握手包正常返回随后开始出媒体数据包。这个例子给我最大的教训是很多SRT“连不上”的问题协议层面并没有崩溃只是握手扩展区里一个看不到的字符串差异让服务端拒绝了会话。如果不抓握手控制包只看服务端日志或播放器表面现象很难定位到一个空格身上。所以现在遇到SRT连接问题我基本默认流程就是先抓包、过滤握手、比较成功和失败两组包的字段差异。6. 握手控制包相关的坑能避一个是一个最后聊几个我这些年反复踩过的坑它们不全是握手包结构本身的问题但都会在握手控制包上现出原形。6.1 版本号不一致看似通了实际卡死SRT库版本跨度大时老客户端会发一个旧版握手格式新版服务端理解不了就会选择不回确认或者回到一种“兼容但什么都不干”的状态。抓包时最迷惑的是你能看到前几个握手包有来有往但之后没有任何数据包。这时候去看Peer Version字段如果两端库版本隔了好几个大版本不要犹豫先统一版本范围再继续排。我在一次现场踩得很深客户端用的FFmpeg自带的libsrt是1.3版服务端编译了1.5版SDK前两个包正常回第三个握手包携带的扩展字段被服务端解析成未知指令导致后续直接断流。整个过程在信令层看起来完全正常但媒体数据就是起不来。6.2 密码与加密类型不匹配的表现加密连接中握手载荷里带着加密算法标志扩展区才是真正的密钥协商。密码不匹配时常见表现和版本不兼容很相似主握手好像完成了但立即收到对端的关闭或超时。不要看到手握手成功就松懈记得等KMREQ/KMREP阶段走完再下结论。我现在抓包时习惯把过滤条件写长一点udp.port 9000 and (srt.packet_type 0 or srt)这样主握手和后续密钥协商都会留在视野里方便一次看完整个建连生命周期。6.3 Rendezvous模式下的NAT与初始化顺序Rendezvous模式本身不是握手包格式复杂而是网络环境配合难。两边都在NAT后面时UDP打洞往往依赖“同时开始”和“同端口”。如果其中一边先启动个几秒钟它发出的握手包可能被NAT丢弃后面的重发又路径不对结果两边都感觉对方“没上线”。抓包时如果看到只有单方向在重复发握手别急着改代码先在防火墙里确认UDP监听端口放行并确保两边约定的端口号完全一致。Rendezvous模式下Socket ID的辨别也会和Caller/Listener不同。没有明显的“主动方”和“被动方”你可能看到两个包都标记为握手请求。这是正常的不用觉得奇怪关键看后续两个握手包是否互相确认并进入数据阶段。6.4 从握手包中快速提取目标参数的小技巧每次抓包都点一条条看太累我习惯用tshark把握手关键字段一次性导出来。比如tshark -r srt.pcapng -Y udp.port 9000 -T fields -e frame.number -e srt.type -e srt.version -e srt.connection_type 2/dev/null | head -50如果你的Wireshark版本字段名不同先跑tshark -G fields | grep srt找字段把名字替换上去。用这种命令批量扫一遍握手包模式是否两边一致、版本是否有跨度、有没有重复连接一目了然。我个人操作时的另一个习惯是用Wireshark的“Time Reference”把第一个握手包设为时间起点看后续包相对它的延迟。正常局域网内握手总耗时通常在个位数毫秒级公网场景几十毫秒甚至上百毫秒也可能接受。如果相对延迟忽大忽小说明UDP路径上可能存在严重抖动即使握手成功后面直播稳定性也很难保障。说到底SRT握手控制包就是一个“信息密度极高”的报头聚集体。它把双方能不能协作、按什么规则协作在正式推流之前就全部写清楚了。学会从握手包里抽丝剥茧比盲目调重传参数、加FEC有效太多。最后分享一个我自己的小习惯每次新接入一台SRT编码器或者一个新播放器不是直接就推流而是先空跑一次连接抓一份基准握手包存起来。后面出问题时拿异常包和基准包一对比一翻字段差异就知道是谁变了。这套方法帮我解决过好几次匪夷所思的兼容性问题也算是我在SRT协议分析上最值回票价的经验。