1. SIP协议到底是什么:从一次真实通话说起
上周帮一个客户排查语音网关对接问题,对方一直报“电话打不通”,我远程上去打开抓包软件,看到UDP 5060端口上密密麻麻的SIP消息,几分钟就定位到是Contact头域里的IP地址写错了。这类问题在VoIP和IP-PBX的日常维护里太常见了,而所有排查工作的基础,都绕不开SIP协议的基本呼叫流程。
SIP(Session Initiation Protocol,会话初始协议)是IETF在RFC 3261中定义的VoIP信令协议,核心职责是建立、修改和终止多媒体会话。这里说的“会话”,最常见的场景就是两方通话,但也包括视频会议、即时消息、在线状态订阅等。它是文本协议,格式简单,调试起来直观,而且和传输层无关——可以跑在UDP、TCP、TLS之上。对运维、开发、VoIP工程师和网络管理员来说,弄懂SIP的基本呼叫流程,就是掌握了一套通用的语音排障语言。
这篇内容不打算堆文档,我会从“一次完整通话是怎么跑通的”出发,把SIP的角色定位、关键消息、完整呼叫流程、抓包分析方法、常见故障排查一次讲清楚。
1.1 SIP协议的角色定位:信令与媒体分离
理解SIP之前,必须先建立“信令”和“媒体”分离的概念。信令负责“商量事情”,比如谁打给谁、对方是否在线、铃声是否在响、要不要接听、怎么挂断;媒体则负责“传输内容”,也就是双方真正听到的语音数据流。
SIP只管信令部分,真正的声音数据通常走RTP协议(Real-time Transport Protocol,实时传输协议),使用UDP端口范围一般在10000到20000之间。双方在“打电话”过程中,SIP消息里用SDP(Session Description Protocol,会话描述协议)协商好了用什么编码、用什么IP和端口收音频,之后音频就直接在两个终端之间RTP传输了。
为什么这么设计?因为信令和媒体是两类完全不同性质的数据。信令数据量小、但要求可靠有序,媒体数据量大、但允许丢包重传一小部分。分开传输、分流处理,既保证了呼叫控制的稳定性,也让媒体路径可以根据网络情况灵活调整。实际排障时,如果只盯着SIP抓包,看不到RTP是怎么走的,就很容易漏掉问题的真正根源。后面我会专门讲怎么把两者结合起来分析。
1.2 SIP的典型组网元素:UAC、UAS、代理服务器与注册服务器
SIP网络里有几个基础角色,理解它们对看懂信令流非常重要。
用户代理(User Agent,UA)是整个系统的终端实体,比如软电话、IP话机、语音网关。UA内部又分两部分:UAC(User Agent Client)负责发起请求,UAS(User Agent Server)负责接收请求并回响应。一部话机同时具备这两种能力,呼出时它是UAC,呼入时它是UAS。SIP服务器则分几类:代理服务器(Proxy Server)负责转发请求,相当于信令层面的“路由器”;重定向服务器(Redirect Server)告诉呼叫方“你要找的人现在在另一个地址”,然后由呼叫方自己继续;注册服务器(Register Server)接收终端的REGISTER请求,维护“用户和当前联系地址”的绑定关系,这个绑定关系通常保存在定位服务(Location Service)里。
实际商用环境中,这几类服务器经常合并在一个设备里实现,比如FreeSWITCH、Asterisk、opensips,以及各种运营商级IMS平台。理解这个概念最大的用处是:当你看到一条INVITE消息被转发了多次,Via头域里记录着每一跳的地址时,就能明白信令在哪个节点上做了转发判断,从而定位问题出现在哪一段。
另外,SIP的寻址方式类似电子邮件,使用SIP URI,格式如sip:1001@192.168.1.10:5060。前面的1001是用户标识,后面是域或IP,端口默认是5060。域名解析也是SIP排障中常遇到的问题,不过是后话了。
2. SIP消息的“交通规则”:方法、头域与状态码
SIP协议本质上是客户端-服务器模式下的请求/响应协议。UAC发请求,UAS回响应,双方靠一组定义好的“话术”来进行会话管理。要读懂一条SIP消息,最核心的是三件事:请求方法、关键头域、状态码。
2.1 六种基本方法:INVITE、ACK、BYE、CANCEL、REGISTER、OPTIONS
RFC 3261定义了六种基本方法。
INVITE用来发起一个会话请求,比如拨号时话机发出INVITE,里面携带了SDP,告诉对端“我想用这个编码、这个地址接收音频”。这是最重要的一条消息。ACK用来确认最终响应,尤其在INVITE收到最终响应(2xx或非2xx)之后,UAC需要回一条ACK确认。值得注意的是,如果INVITE收到的是非2xx错误响应,ACK往往由代理服务器代发,这个细节后面实操部分再展开。BYE用来终止已建立的会话,可以由任意一方发起。CANCEL用于在收到最终响应之前取消一个正在进行的请求,比如对方还没接听,主叫方就挂了,这时发的是CANCEL而不是BYE。REGISTER用来注册,把“我是这个号码,我现在在某个IP地址”告诉注册服务器。OPTIONS用于查询对端能力,比如服务器是否在线、支持哪些方法,常被用来做SIP探测。
除了这六种,实际应用中还经常见到INFO、NOTIFY、SUBSCRIBE、REFER、MESSAGE等扩展方法,分别用于传输DTMF、订阅状态、呼叫转接和即时消息。这些扩展让SIP的功能覆盖了传统电话之外的协作场景。
2.2 必须看懂的头域:Via、From、To、Call-ID、CSeq、Contact
SIP消息由起始行(请求行或状态行)、消息头、消息体三部分组成。刚开始看SIP消息时会觉得头域特别多,但其实最关键的只有几个,我把它们按作用划分成四类。
第一类是标识对话的:Call-ID是整个呼叫的唯一标识,同一会话的所有消息共享同一个Call-ID;From标识发起方,To标识接收方;CSeq是一个整数+方法名组成的序号,用于保证消息有序,比如“1 INVITE”、“2 BYE”。四个合起来可以在整个会话系统中唯一定位一条事务。
第二类是路由消息的:Via记录了请求经过的每一个设备地址,每经过一跳就会追加一个Via头域,响应消息严格按照Via顺序原路返回,这是SIP路由的基石。Route和Record-Route则用于在后续消息中强制走代理服务器,呼叫保持、监听等业务会用到。Contact是“我接下来要在哪个地址接收后续消息”,发起方和接收方都会在消息里带上各自想收到的地址,后续消息往往直接发到Contact指定的地址,不再经过代理。
第三类是确认消息能力的:Max-Forwards限制最大跳数,类似IP TTL,默认70,防止消息环路;User-Agent标识客户端软件类型。第四类是认证相关的:Authorization和Proxy-Authorization携带鉴权凭据,WWW-Authenticate和Proxy-Authenticate服务器用来挑战客户端。我用一张表来总结,排查时按图索骥就行。
| 头域 | 作用 | 排障关注点 |
|---|---|---|
| Via | 记录请求路径,响应用它原路返回 | 响应是否按路径返回;NAT场景下是不是私有地址 |
| From / To | 呼叫发起方、接收方的SIP URI | 号码格式是否带前缀,域名是否正确 |
| Call-ID | 标识本次会话 | 同一通话的所有信令必须一致 |
| CSeq | 请求序号,保证事务顺序 | 相同CSeq重复可能是重传 |
| Contact | 后续消息的目标地址 | 内外网IP混淆的常见点 |
| Max-Forwards | 最大转发跳数,默认70 | 数值递减到0说明存在环路 |
| Record-Route | 要求后续信令经过代理 | 是否需要经过B2BUA做媒体处理 |
2.3 状态码速查:1xx到6xx怎么读
SIP状态码和HTTP很像,共六大类。1xx是临时响应,表示请求已收到且正在处理中,关键的有100 Trying、180 Ringing(正在振铃)、183 Session Progress(会话进行中,可以传早期媒体,比如彩铃)。2xx是成功响应,200 OK表示请求成功,INVITE的200 OK是“对方已接听”。3xx是重定向类,302 Moved Temporarily表示用户暂时移到别处,呼叫方需要按Contact里的新地址重新发起。4xx是客户端错误,最常见的有401 Unauthorized和407 Proxy Authentication Required,服务器要求认证;404 Not Found是号码不存在;486 Busy Here是对方忙;480 Temporarily Unavailable是对方暂时不可用。5xx是服务器错误,500 Server Internal Error,502 Bad Gateway,503 Service Unavailable。6xx是全局失败,603 Decline表示被叫明确拒绝。
排障时先看状态码,心里就有了大概的方向。有一次看到一个呼叫稳定返回404,先怀疑号码不存在,后来仔细一看发现是域名解析把请求送到了别的服务器。状态码只能给方向,具体原因还要配合头域和服务器日志定位。
3. 基本呼叫流程拆解:一次“完美通话”的完整生命周期
到了本文的核心部分。我以最常见的场景为例:两部SIP话机A和B,都注册到同一台IP-PBX上,A呼叫B,B接听,通话一段时间后A挂断。把整个流程分成注册、呼叫建立、媒体协商、挂断四个阶段。
3.1 第一步:注册(REGISTER)与鉴权
话机接入SIP网络的第一件事是发送REGISTER请求。“你好,我是分机1001,我现在在192.168.1.100:5060这个地址,以后有人找我就往这儿发信令。”
首次REGISTER通常不带凭证,服务器会返回401 Unauthorized,同时在响应头里带上WWW-Authenticate,里面包含realm、nonce等参数,相当于给出一个“挑战码”。话机收到后用用户名、密码和nonce做MD5或SHA算法算出摘要,再次发送带Authorization头域的REGISTER。服务器验证通过后返回200 OK,注册完成。
这里有三个容易踩坑的细节。一是注册有效期,REGISTER消息里有Expires头(默认3600秒),到期后必须重新注册,否则服务器会清掉绑定。二是注册刷新,话机通常在过期时间的一半时自动发送刷新请求,测试时如果把Expires设置太短,会发现周期性注册风暴。三是Contact头域里的IP地址,话机如果NAT后面的私网地址,注册到公网服务器时必须启用NAT穿透,让信令里的地址变为公网映射地址,否则会出现“注册成功但打不通”的怪现象。
注册完成后,定位服务里就有了1001和1002各自对应的联系地址,后续INVITE就能被路由到正确的话机了。
3.2 第二步:呼叫建立(INVITE → 100 → 180 → 200 → ACK)
A呼叫B,A的话机向PBX发送INVITE请求,From是A的URI,To是B的URI,消息体里带着A的SDP,告诉B“我想用G.711编码,我打算在这个IP端口收语音”。
PBX收到INVITE后,立即回复100 Trying,表示“我已经收到了,正在处理”。这个100 Trying在呼叫流程中很关键,它能让A的话机知道请求没有丢,同时它也是UDP场景下防止反复重传的重要手段。
接着PBX作为代理,查询被叫B的位置,把INVITE转给B的话机。B的话机振铃后,向PBX返回180 Ringing,PBX再转发给A。A的话机听到回铃音。B接起电话,话机返回200 OK,里面也带了B的SDP,表示“我接听了,这是我的音频参数”。PBX把200 OK转发给A,同时自己向B回复ACK确认收到这个响应。A的话机收到200 OK后,需要直接向B或按Record-Route路径发送ACK,确认会话建立。
这里有个细节容易绕晕:INVITE的200 OK确认方式是ACK,而不是像普通消息那样直接回复200。如果主叫没收到ACK,或ACK丢失,被叫往往会重复发送200 OK,直到超时。所以抓包时看到大量重复的200 OK,第一反应就是ACK丢了。
另外,如果B正在通话中,B的话机会返回486 Busy Here,PBX转发给A,A回ACK确认后就结束这一次呼叫尝试。这类流程在排障中同样常见。
3.3 第三步:媒体协商(SDP Offer/Answer)——真正说话的通道
INVITE的200 OK不只是“接听了”这么简单,它同时完成了SDP的Offer/Answer协商。所谓Offer/Answer,就是发起方在INVITE里先给一个SDP Offer,列出自己支持的编码和收流地址端口;被叫方在200 OK里给一个Answer,从Offer里挑一个双方都支持的编码,写上自己的收流地址端口。
SDP消息体看起来可能是这样的:
v=0 o=1001 2890844526 2890844526 IN IP4 192.168.1.100 s=- c=IN IP4 192.168.1.100 t=0 0 m=audio 49170 RTP/AVP 0 8 101 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:101 telephone-event/8000最关键是m=行,它定义了媒体类型、收流端口、传输协议和负载类型。比如m=audio 49170 RTP/AVP 0 8 101表示:音频流,UDP端口49170,支持负载类型0(PCMU)、8(PCMA)和101(DTMF事件)。c=行定义连接地址,协商后A会把音频发到192.168.1.100:49170,同样B也会告诉A自己想从哪里收。
媒体协商出问题的常见表现是“能接通但没声音”。比如一方只支持PCMA(8),另一方只支持PCMU(0),两边列出的编码没有交集,协商就会失败。更常见的是双方都选了同一编码,但端口或地址不对,导致RTP发到了无法到达的地方。所以我在排障时,第一步永远都是看SDP里的c行和m行,确认收流地址是合理的。
3.4 第四步:挂断(BYE → 200 OK)
通话结束后,任何一方都可以挂断。假设A先挂断,A的话机发出BYE消息,消息头里带着和之前INVITE相同的Call-ID、From、To,但CSeq会是新序号。B回复200 OK,至此这次通话的会话结束。
需要注意,BYE不经过代理服务器也可以。如果呼叫建立时代理服务器通过Record-Route把自己写进了信令路径,那么BYE也应该经过代理,目的是让代理知道会话结束并释放资源。所以在抓包里看到BYE只有两个方向而没有经过PBX时,不一定是问题,要回头检查INVITE里有没有Record-Route。
把前面这些步骤串起来,一次完整通话的信令序列就是:REGISTER/401/REGISTER/200,然后是INVITE/100 Trying/INVITE/180 Ringing/200 OK/ACK,通话结束后BYE/200 OK。这也是面试中常被问到的SIP基本呼叫流程,更是日后分析一切SIP问题的骨架。
3.5 不同场景下的流程变体:呼叫保持、转接、取消
理解基础流程后,扩展场景就顺理成章了。呼叫保持(Hold)基于re-INVITE实现,一方在已有会话中再次发送INVITE,SDP里把媒体方向改为a=sendonly或a=inactive,对方回复200 OK,这个时候通话被“冻住”。呼叫转接(Transfer)一般用REFER方法,A发给B一个REFER请求,告诉B“接下来请呼叫C”,然后B会作为UAC向C发起新的INVITE。呼叫取消发生在180 Ringing之后、200 OK之前,主叫挂断时发送CANCEL,被叫确认CANCEL成功后再发送487 Request Terminated,主叫回复ACK,整个事务结束。
这些变体看着花哨,本质还是那几个方法加头域的组合。只要基础流程扎实,在看到异常的onHold、REFER消息时,就能很快判断是哪一步出了问题。
4. 抓包实战:用sngrep和Wireshark看懂SIP信令
理论讲再多,不如实际抓一次包。SIP是文本协议,技术人常用的两款开源工具都很好用——Wireshark功能全面,适合深入分析协议层;sngrep则是终端里运行的SIP专用抓包工具,轻量、直观,是Linux服务器上排障的利器。
4.1 准备一个SIP抓包环境
在Linux服务器上,一条命令就能安装sngrep:
# Debian/Ubuntu apt install sngrep # RHEL/CentOS yum install sngrep抓包命令很简单:
sngrep -d eth0 -d port 5060-d可以指定网卡,系统有公网网卡、内网网卡多个接口时各自抓一遍,或用port 5060过滤。如果还要看RTP包,直接在sngrep界面里选中一个通话记录按Enter,就能看到对应的RTP统计,包括丢包、抖动等信息。
系统里没有sngrep时,Wireshark肯定也可以。启动Wireshark选择注意使用的网卡,在过滤栏输入:
sip || rtpp过滤时会有一个很多人栽过跟头的细节:只过滤sip会漏掉很多SIP包,因为有些设备是把SIP放在TCP/TLS上的,有些甚至用非标准端口,建议抓包时先把全流量抓到pcap文件,再用Wireshark过滤分析,避免漏抓。
4.2 对照抓包记录理解一次完整呼叫
我举一个实际抓包范例来说明怎么看。
UAC (192.168.1.100) SIP Proxy (192.168.1.1) UAS (192.168.1.200) | REGISTER | | |-------------------------->| | | 401 Unauthorized | | |<--------------------------| | | REGISTER (with auth)| | |-------------------------->| | | 200 OK | | |<--------------------------| |看抓包时,先按时间顺序把Call-ID相同的消息串起来,然后检查每一跳的状态码是否符合预期。比如注册流程里出现两次REGISTER是正常的,第一趟没带认证,第二趟带了Authorization。如果只看到一次REGISTER就返回200,可能是服务器没有启用鉴权,也可能是IP白名单直接放行了。
呼叫流程的抓包对照更复杂一点。我用一个简单的文字版序列来说明:
Step 1: A -> PBX: INVITE (SDP Offer: PCMU/PCMA) Step 2: PBX -> A: 100 Trying Step 3: PBX -> B: INVITE (SDP Offer: PCMU/PCMA) Step 4: B -> PBX: 180 Ringing Step 5: PBX -> A: 180 Ringing Step 6: B -> PBX: 200 OK (SDP Answer: PCMU) Step 7: PBX -> A: 200 OK (SDP Answer: PCMU) Step 8: A -> PBX: ACK Step 9: PBX -> B: ACK看到这个序列,A和B都协商到PCMU(负载类型0),接下来应该能看到从双方各自发向对方c行地址的RTP包。有一次排查“单通”问题,抓包显示信令完全正常,200 OK和ACK都齐了,我马上把过滤条件切成RTP,发现只有一方在向另一方的5004端口发音频包,另一方完全没有回流。问题指向被叫侧NAT或防火墙没有放行UDP音频端口。
4.3 常见问题定位思路
利用抓包定位问题的通用思路,我总结成四步走。
第一步,确定问题边界,是“完全不能呼叫”“能呼叫但异常挂断”还是“能接通但媒体异常”,不同边界对应不同排查重点。第二步,过滤同一呼叫的完整信令,看每一跳的状态码和关键头域,定位失败在哪个事务。第三步,检查SDP的c行、m行与RTP实际流量是否一致。地址不一致时,重点找NAT或防火墙原因。第四步,结合设备端日志确认是协议层问题还是应用层问题。
这套方法很土但很稳,几乎能覆盖绝大多数SIP故障场景。用熟悉之后,你会发现自己不再被一堆看似杂乱的消息吓住,而是像读故事一样顺畅地看完整条信令链。
5. 常见故障与排查技巧实录
SIP排障的知识点,很大一部分就藏在各种报错状态码里。我在这个部分把自己踩过坑最多的场景都列出来,做成一个速查表,方便你直接对照使用。
5.1 注册失败、401、403、486等错误速查
| 现象 | 可能原因 | 排查建议 |
|---|---|---|
| REGISTER无响应 | 服务器端口未监听、防火墙丢包 | 先telnet或nc测试5060端口连通性 |
| 反复401/407 | 密码错误、算法不匹配 | 检查话机鉴权用户名;对比服务器日志中收到的密码摘要 |
| 403 Forbidden | ACL拒绝、域名不匹配 | 检查服务器ACL配置,看From域是否在允许列表内 |
| 404 Not Found | 号码不存在或路由表缺失 | 检查被叫注册状态,测试直接IP呼叫排除路由问题 |
| 486 Busy Here | 被叫忙 | 看是否需要启用呼叫等待或遇忙转移 |
| 480 Temporarily Unavailable | 被叫未注册或话机离线 | 检查被叫注册状态及话机网络 |
| 488 Not Acceptable Here | 媒体协商无交集 | 检查双方SDP编码列表,确认至少有一个公共编码 |
| 503 Service Unavailable | 服务器过载或维护 | 查看服务器端资源使用情况与维护配置 |
注册相关的问题,我最常遇到的就是“能注册但不能呼出”和“呼出时401循环”,后者最常见原因是话机使用IP地址注册,服务器上配置的域名与From头中的域不一致,导致代理服务器每次都对INVITE发407挑战,而话机携带的凭证又不被认可。
5.2 单通、无声音、回声、延迟的媒体故障
媒体故障相对难排查,因为信令全部正常,问题藏在RTP层。我总结了几种典型情况。
单通是一方听得到另一方声音,另一方听不到这边声音。一般是被叫侧的音频回传路径断了。比如通话双方跨运营商或者跨NAT,媒体流走了错误的地址。排查时用sngrep打开对应的RTP流,看是只有单方向有包,还是双向都有包但一方丢弃严重。前者指向路由问题,后者多半是防火墙/NAT或者编码不匹配。
无声音是双方都听不到。常见原因是SDP协商失败(488错误,但偶尔也有协商成功但编码实际不支持的怪事),也可能是早期媒体(Early Media)与正式媒体的切换问题。有一次遇到电话接通后3秒才听到声音,一查发现网关用了183 Session Progress带早期媒体,而对方不支持,导致音频路径没建立成功。解决办法是调整网关,让它在200 OK之后才建立媒体流。
回音通常出现在网络延迟大或回声抑制没开启的设备上,检查双方是否开启回声消除,以及网关的抖动缓冲设置。延迟问题要重点排查网络链路和媒体路径,比如RTP是否被路由到了很远的中转节点。
5.3 NAT穿透与拓扑层面的排查思路
NAT穿透是SIP永远绕不开的话题。信令里携带的是内网地址,但实际通信要通过公网地址,处理不当就会出现“注册成功但来电话时无反应”或者“能呼出但听不到声音”。
主流解决方案有三种。
一是启用话机和网关的NAT穿透功能,让设备自动检测公网映射地址并写入SDP和Contact,常见功能名“NAT Traversal”“STUN”。二是部署SIP代理/B2BUA服务器,由服务器改写SDP中的地址和端口,这种方式最稳,这也是很多企业IP-PBX部署在云上的原因。三是边缘会话边界控制器(SBC),专门处理防火墙穿透和拓扑隐藏,运营商和大型企业常用。
在配置话机时,我建议无论如何都开启NAT Keepalive,让话机周期性发送OPTIONS或注册刷新,保持NAT映射不老化,否则即使配置了STUN,长时间不打电话后仍可能遇到呼入失败。
5.4 排障工具箱:我常用的命令与习惯
最后分享几条个人觉得特别实用的排障命令和分析习惯。
# 抓取全部SIP信令并保存到文件 sngrep -d eth0 -d port 5060 -O /tmp/sip.pcap # 从pcap中统计各个状态码出现次数 tshark -r /tmp/sip.pcap -Y "sip.Status-Code" -T fields -e sip.Status-Code | sort | uniq -c # 查看某个通话的SDP信息(只显示媒体描述) tshark -r /tmp/sip.pcap -Y "sip && sip.Call-ID == \"xxxx\"" -T fields -e sip.msg2 -e sdp.media分析Call-ID字段时,我喜欢在Wireshark里右键“Apply as Filter -> Selected”,这样能快速隔离同一通电话的所有信令。进一步配合sngrep的呼叫列表视图,可以很直观地看到一段时间内所有呼叫的建立与释放过程,判断是否存在大量呼叫失败、超时或异常挂断。
还有一个习惯:做完一次排障后,把关键抓包文件名、问题现象、根因和解决方案记录在一个笔记里。几个月后再碰到类似问题时,直接查自己的排障笔记,比从头开始SIP文档效率高得多。
6. 写在最后:几个实用的排障小技巧
关于SIP协议的基本呼叫流程,前面已经讲得比较完整了。最后分享几个我实际工作中积累的小技巧。
第一个,抓包时不要只盯UDP 5060。很多平台默认使用TLS 5061或TCP 5060,有些甚至跑在非标准端口上。如果一开始过滤条件就设死了,可能会漏掉真正的信令流。没思路时先全量抓30秒,再慢慢过滤,反而更快。
第二个,看SIP消息头域时,优先看Via、Contact和SDP的c行。这三个地方最容易暴露NAT或地址配置问题。很多时候,呼叫流程明明在逻辑上是对的,就是这三个头域里的地址不对,导致信令或媒体找不到回家的路。
第三个,处理跨网段、跨NAT的语音时,别急着怀疑SIP协议本身。优先确认UDP端口是否放通、RTP端口范围是否在防火墙白名单内、设备是否开启了NAT Keepalive。语音这种实时性要求高的业务,网络层的一个丢包策略,影响往往比协议配置错误更隐蔽。
根据我个人经验,SIP学习最有效的路径,不是抱着一本RFC啃,而是搭一个两台的测试环境,自己用话机注册、拨打、挂断,再对照抓包工具把每一条消息读一遍。流程跑通了,原理也就自然通了。之后再遇到那些加了代理、加密、NAT的复杂环境,你也能轻松拆解出问题出在哪个环节。