SIP简单,可读性好;但SIP也复杂,很多头域有丰富的语义,路由机制也足够灵活,这难免会让很多初学者迷惑。下面我就一个简单的SIP会话流程来分析一下SIP中的几个重要头域以及参数。该场景来源于SIP根本大典——RFC 3261。另外经典书籍《Understanding the Session Initiation Protocol, Second Edition》中也有丰富的示例与解析。
本来3261中已经有了流程图,但是文本,拿到这里来会变形,而且注册流程跟会话流程是分开画的。刚好今天在网上又看到了一个非常漂亮的这个例子的流程图,于是就用这个了。这里要感谢一个网友介绍的网站(
http://www.tech-invite.com/)。
【说明】本例是在SIP标准文档RFC 3261中给出的,主要是为了解释SIP的使用,因此也就忽略了SIP的消息体以及相关的头域:Content-Length 和 Content-Type。
注册与会话流程图(点击查看完整图片):
[图1] RFC 3261中的注册已经会话流程图
注册部分:
实际应用中,被叫是必须要事先注册的,因为如果不注册就无法实现记录地址到设备地址的映射。本例给出的注册流程比较简单,没有对用户的认证(鉴权)。Bob首先向其所在域biloxi.com的注册服务器进行注册,这在上图的右上角可以看到。下面是注册过程中的消息:
F1 REGISTER Bob -> Registrar
REGISTER sip:registrar.biloxi.com SIP/2.0
Via: SIP/2.0/UDP bobspc.biloxi.com:5060;branch=z9hG4bKnashds7
Max-Forwards: 70
To: Bob
From: Bob ;tag=456248
Call-ID:
843817637684230@998sdasdh09
CSeq: 1826 REGISTER
Contact:
Expires: 7200
Content-Length: 0
start-line:
起始行中有请求方法,因此是个请求消息,而非响应消息。并且请求方法是REGISTER,表明是一个注册请求。request-URI是该消息的处理者,此处是registrar.biloxi.com,也就是registrar的URI。目前SIP统一都是2.0版的。
【注意】SIP请求的处理者(在request-URI中)与接收者(在To中)不完全是一回事,虽然他们往往是一样的。
Via:
SIP协议栈自动维护该头域:当一个请求消息从协议栈发出前,协议栈会先在现有Via头域(如果有的话)的最前面增加一个Via头域,以记录当前协议栈的URI等信息,因此Via头域无需应用程序过问。
Via头域主要有两个用处:
1、用于记录请求消息所经过的路径,以便响应消息沿该路径原路返回,这也是响应消息无需一个目的地址的原因,因为有Via记录了他的完整传输路径。
2、用于环路检测。SIP中有两种主要机制来保证SIP消息不会因为路径问题而过多浪费网络资源:一个是Max-Forwards,一个就是环路检测。如果同一个消息两次经过同一个节点,我们就认为存在环路。如果一个Proxy发现他接收到的一个请求Via头域中已经有自己先前加入的一个Via头域,就表明存在环路。但是有一种情况比较特殊(我们称之为“螺旋”):如果主被叫位于同一个域内,那么主叫发出的INVITE请求就会又被路由回同一个Proxy(如果该域只有一个Proxy的话)。为了能够检测出环路,而又不误判“螺旋”的情况,Proxy必须保证加入的Via能够体现出当时的场景,以便当同一个消息又被接收到时能够判断出究竟是环路还是“螺旋”。这是通过参数branch来保证的,branch是通过计算SIP多个头域的散列值得到的。
我们可以看出,Bob协议栈的URI是bobspc.biloxi.com。
【注意】Via中URI中的端口是用于接收该请求的响应消息的端口,而不是该请求消息发出的源端口,因此往往就是5060。
另外,我们还可以看出,该请求是通过UDP传输的。
Max-Forwards:
一个SIP消息每经过一个Proxy,该头域都会被减1。而如果减1后变成了0,则表明没必要再传下去了,当前的Proxy会给消息发送者返回一个错误响应。因此从发送者到接收者之间允许的最大跳数刚好就是Max-Forwards的值(比如Max-Forwards是70时,消息传输过程中的变化序列为:70~69~...~1~1)。
【注意】只有Proxy才会将该头域的值减1,UA不会。
上面讲了,该头域主要用于防止一个消息因为出现环路或者路径过长而长时间占用网络资源。70是经验值,因为一般的消息在70跳以内都可以到达目的地。
【注意】这个值一般不要改变,除非你很清楚自己的网络构架。因为如果改大了,可能会浪费了网络资源,改小了又可能导致一些消息不能到达目的地。
To:
To头域中的URI指出了消息的接收者。而在REGISTER消息中,它的含义有所不同。在此处是指本次注册要处理的记录地址(这里是sip形式的
[email protected]),而相应的设备地址在Contact中(Contact头域在REGISTER中可能没有,可能有一个,也可能有多个,因为SIP通过一个消息就可以完成注册/注销/注册信息查询等操作,不同的REGISTER消息格式意味着不同的操作)。
一个URI中还可以包含一个display-Name(这里是Bob),用以在终端上显示给用户。
To中的tag参数是To中的用户在本次会话中的标识。由于这是第一个REGISTER请求,因此To中没有tag参数。
From:
From头域中的URI指出了消息的发送者,在REGISTER请求中From与To中的URI通常都是相同的,因为通常都是自己为自己注册。但是如果是第三方注册的情况(由一个可信的第三方来代替用户进行注册),From中的URI就是该第三方的URI,就跟To中的不同了。registrar根据From中的URI来决定该用户是否有权进行相关操作(注册/注销/查询)。
From中的tag参数是From中的用户在本次会话中的标识。此处
[email protected]的tag是456248
【注意】tag是跟用户对应的,而不是From或To。这在下面关于会话的分析中会提到。
Call-ID:
对于Call-ID的唯一要求就是它要保证其全局唯一性,这是因为它是一个SIP会话的标识。
因此,该值往往包含两部分:前面的部分是一个跟时间相关的随机值,用于保证在本地主机的唯一性,后面的部分就是本机在网络中的标识(比如一个IP地址或者一个域名),这种结构有点像E-mail地址。
CSeq:
其生存域是一个会话。用于将一个会话中的请求消息序列化,以便用于重复消息、“迟到”消息的检测,响应消息与相应请求消息的匹配等。包含两部分:一个32位的序列号,一个请求方法。
通常在会话开始时确定一个初始值,其后再发送消息时将该值加1。主叫方与被叫叫各自维护自己的CSeq序列,互不干扰,这有点像TCP/IP中IP包的序列号。
一个响应消息有与其对应的请求消息相同的CSeq值。
【注意】SIP中CANCEL消息与ACK消息总是比较特殊。CANCEL消息的CSeq中的序列号总是跟其要cancel的消息的相同,而对于ACK消息:如果它所要确认的是INVITE请求的non-2xx响应,则ACK消息的CSeq中的序列号与对应INVITE请求的相同;如果是2xx响应,则不同,此时ACK被当作一个新的事务。
Contact:
Contact头域的语义比较丰富,在不同的场景有不同的意思,但总体来说是用来携带具体联系地址的。
在该请求中,有一个Contact头域,并且Expires的值不是0,因此这是一个注册消息,而不是一个注销或查询消息。要注册的地址对就是To与Contact中的地址对 <
[email protected],[email protected]>
Expires:
该头域用于指出本次注册信息保持有效的时间。
【注意】实际有效时间还需要由registrar综合考虑实际情况(比如配置数据)来确定,并再REGISTER请求的响应消息中的Expires头域返回。但最终的值不会大于REGISTER中Expires的值。
Content-Length:
该头域跟消息体相关,指出了消息体的长度。此处长度为0,表明消息体是空,因为注册时无需其他额外信息。
F2 200 OK Registrar -> Bob
SIP/2.0 200 OK
Via: SIP/2.0/UDP bobspc.biloxi.com:5060;branch=z9hG4bKnashds7
;received=192.0.2.4
To: Bob <sip:
[email protected]>;tag=2493k59kd
From: Bob <sip:
[email protected]>;tag=456248
Call-ID:
843817637684230@998sdasdh09
CSeq: 1826 REGISTER
Contact: <sip:
[email protected]>
Expires: 7200
Content-Length: 0
registrar在收到REGISTER请求并做相应处理后,返回一个200(OK)消息,并以表示请求处理成功,并且返回相应结果。
start-line:
没有请求方法,而是状态码,因此是一个响应消息。而状态码是200,表明是一个成功的响应消息。
Via:
Registrar接收到REGISTER请求后,在第一个Via头域中增加一个received参数,值为该数据包的源地址。
received参数主要有两个作用:
1、用于NAT检测:如果一个请求消息发出后经过的第一个Proxy在NAT外,它将UA经过NAT后的地址作为received的值添加到UA所对应的Via中。当响应消息返回到UA后,UA将Via中的URI与received参数的值比较就会发现是否有NAT存在(不同就有NAT)。
2、用于正确的路由:有时Via中的值并非消息真正经过的Proxy的地址,此时响应消息返回时就需要使用received中的地址,而不是Via中的。
To:
SIP中的To头域并非当前消息的接收者地址,而是当前事务的接收者地址。因此,响应消息中To头域与对应请求消息的To头域是相同的(唯一不同的是响应消息中To头域被消息处理者增加了tag参数)。
From:
Call-ID:
跟上面REGISTER请求的Call-ID相同。
CSeq:
跟上面REGISTER请求的CSeq相同。
Contact:
因为所对应的是一个注册请求,因此响应消息中的Contact跟REGISTER中的相同。
而如果是一个查询请求,Contact中列出的就是跟To中URI绑定的所有设备地址。
Expires:
本次注册实际有效时间是7200秒(跟注册者的期望一样)。
Content-Length:
响应消息的消息体仍然是空。