1、概述
1.1 SIP概念
会话初始协议SIP(Session Initiation Protocol)是一个应用层的控制协议,可以建立、修改和结束多媒体的会话。它是由IETF提出并主持研究的一个在IP网络上进行多媒体通信的应用层控制协议,它被用来创建、修改、和终结一个或多个参加者参加的会话进程。这些会话包括Internet多媒体会议、Internet电话、远程教育以及远程医疗等。即所有的因特网上交互式两方或多方多媒体通信活动,统称为多媒体会话。参加会话的成员可以通过组播方式、单播联网方式或者两者结合的方式进行通信。
总的来说,SIP能够支持下列五种多媒体通信的信令功能:
User location(用户定位):确定参加通信的终端用户的位置
User capabilities(用户能力):确定通信的媒体类型和参数
User availability(用户的可用性):决定被叫方是否愿意参加通信
Call setup(呼叫建立):振铃,在主叫和被叫直接建立呼叫的参数
Call handling(呼叫处理):包括呼叫转移和终止
2、协议消息
2.1 消息类型
SIP消息基于10646文本编码,且不区分大小写字符,分为请求消息和响应消息。
请求消息 |
消息含义 |
INVITE |
发起会话请求,邀请用户加入一个会话,会话描述含于消息体中。对于两方呼叫来说,主叫方在会话描述中指示其能够接受的媒体类型及其参数。被叫方必需在成功响应消息的消息体中指明其希望接受哪些媒体,还可以指示其行将发送的媒体。如果收到的是关于参加会议的邀请,被叫方可以根据Call-ID或者会话描述中的标识确定用户已经加入该会议,并返回成功响应消息。 |
ACK |
证实已收到对于INVITE请求的最终响应。该消息仅和INVITE消息配套使用。 |
BYE |
结束会话 |
CANCEL |
取消尚未完成的请求,对于已完成的请求(即已收到最终响应的请求)则没有影响 |
REGISTER |
注册 |
OPTIONS |
查询服务器的能力 |
序号 |
状态码 |
消息功能 |
1xx |
信息响应(呼叫进展响应) |
表示已经接收到请求消息,正在对其进行处理 |
100 |
试呼叫 |
|
180 |
振铃 |
|
181 |
呼叫正在前转 |
|
182 |
排队 |
|
2xx |
成功响应 |
表示请求已经被成功接受、处理 |
200 |
OK |
|
3xx |
重定向响应 |
表示需要采取进一步动作,以完成该请求 |
300 |
多重选择 |
|
301 |
永久迁移 |
|
302 |
临时迁移 |
|
303 |
见其它 |
|
305 |
使用代理 |
|
380 |
代换服务 |
|
4xx |
客户出错 |
表示请求消息中包含语法错误或者SIP服务器不能完成对该请求消息的处理 |
400 |
错误请求 |
|
401 |
无权 |
|
402 |
要求付款 |
|
403 |
禁止 |
|
404 |
没有发现 |
|
405 |
不允许的方法 |
|
406 |
不接受 |
|
407 |
要求代理权 |
|
408 |
请求超时 |
|
410 |
消失 |
|
413 |
请求实体太大 |
|
414 |
请求URI太大 |
|
415 |
不支持的媒体类型 |
|
416 |
不支持的URI方案 |
|
420 |
分机无人接听 |
|
421 |
要求转机 |
|
423 |
间隔太短 |
|
480 |
暂时无人接听 |
|
481 |
呼叫腿/事务不存在 |
|
482 |
相环探测 |
|
483 |
跳频太高 |
|
484 |
地址不完整 |
|
485 |
不清楚 |
|
486 |
线路忙 |
|
487 |
终止请求 |
|
488 |
此处不接受 |
|
491 |
代处理请求 |
|
493 |
难以辨认 |
|
5xx |
服务器出错 |
表示SIP服务器故障不能完成对正确消息的处理 |
500 |
内部服务器错误 |
|
501 |
没实现的 |
|
502 |
无效网关 |
|
503 |
不提供此服务 |
|
504 |
服务器超时 |
|
505 |
SIP版本不支持 |
|
513 |
消息太长 |
|
6xx |
全局故障 |
表示请求不能在任何SIP服务器上实现 |
600 |
全忙 |
|
603 |
拒绝 |
|
604 |
都不存在 |
|
606 |
不接受 |
2.2 消息结构
2.3 消息参数
请求消息和响应消息都包括SIP头字段和SIP消息字段。SIP请求命令由起始行、消息头和消息体组成,通过换行符区分消息头中的每一条参数行。对于不同的请求消息,有些参数可选。
Call-ID:本地标识@主机
示例:
Call-ID:[email protected]
其中, call-837575-3是全局唯一的本地标识,192.168.1.13是主机的IP地址。
该字段称为命令序号。客户在每个请求中应加入此字段,它由命令名称和一个十进制序号组成,该序号有请求客户端选定,在Call-ID范围内唯一确定。序号初值可为任意值,其后具有相同的Call-ID值,但不同命令名称、消息体的请求,其Cseq序号应加1。重发的消息序号保持不变。服务器将请求中的序号复制到响应消息中,将请求和其触发的响应关联。一般格式为:
Cseq:序号 命令名称
示例:
Cseq:2426 INVITE
所有的请求和响应必须包含此字段,以指示请求的发起者。服务器将此字段从请求消息复制到响应消息。一般格式为:
From:显示名
示例:
From:
其中,显示名为用户界面上显示的字符,如果系统不予显示,应置显示名为匿名。tag称为标记,为16进制数字串,中间可带连接符‘-’,且该置必须全局唯一。
该字段指明请求的接收者,格式与From相同,仅第一个关键词用To替代From。
该字段称为命令序号。客户在每个请求中应加入此字段,它由命令名称和一个十进制序号组成,该序号有请求客户端选定,在Call-ID范围内唯一确定。序号初值可为任意值,其后具有相同的Call-ID值,但不同命令名称、消息体的请求,其Cseq序号应加1。重发的消息序号保持不变。服务器将请求中的序号复制到响应消息中,将请求和其触发的响应关联。一般格式为:
Cseq:序号 命令名称
示例:
Cseq:2426 INVITE
该字段用于指示请求历经的路径。它可以防止请求消息传送产生环路,并确保响应和请求消息选择同样的路径,以保证通过防火墙或满足其它特定的选路要求。发起请求的客户必须将其自身的主机名或网络地址插入请求的Via字段,如果未采用缺省端口号,还需插入此端口号。在请求前传过程中,每个代理服务器必须将其自身地址作为一个新的Via字段加在已有的Via字段之前。如果代理服务器收到一个请求,发现其自身地址位于Via头部中,则必须回送响应“检测到环路”。
当请求消息通过NAT(如防火墙等)时,请求的源地址和端口号可能被改变,此时Via字段就不能成为响应消息选路的依据。为了防止这一点,代理服务器应校验顶端Via字段,如果发现其值和代理服务器检测到的前站地址不符,则应在该Via字段中加入“receive”参数,如此修改后的字段称为“接收方标记Via头部字段”。例如:
Via:SIP/2.0/UDP softx3000.bell-telephone.com:5060
Via:SIP/2.0/UDP 10.0.0.1:5060;received=191.169.12.30
代理服务器或UAC收到Via头部字段时的处理规则是:
1、第1个Via头部字段应该指示本代理服务器或UAC。如果不是,丢弃该消息,否则,删除该Via字段。
2、如果没有第2个Via头部字段,则该响应已经到达目的地。否则继续做如下处理。
3、如果第2个头部字段包含“maddr”参数,则按该参数指示的多播地址发送响应,端口号由“发送方”参数指明,如未指明,就使用端口号5060。响应的生存期应置为“生存期(ttl)”参数指定的值,如未指明,则置为1。
4、如果第2个Via字段不包含“maddr”参数,但有一个接收方标记字段,则应将该响应发往“received”参数指示的地址。
5、如果既无“maddr”参数又无标记,就按发送方参数指示的地址发送响应。
Via字段的一般格式为:
Via:发送协议 发送方;隐藏参数;生存期参数;多播地址参数;接收方标记,分支参数
其中,发送协议格式为:协议名/协议版本/传送层,协议名和协议版本的缺省值分别为SIP和UDP。发送方为通常的发送方主机和端口号。隐藏参数就是关键词hidden,如有此参数,表示该字段已由上游代理予以加密,以提供隐私服务。多播地址参数和接收方标记的意义如前所述。生存期参数与多播地址参数配用。分支参数用于代理服务器并行分发请求时标记各个分支,当响应到达时,代理可判定是哪一分支的响应。
简单来说,就是请求时一层一层在开头新增主机名或地址,响应时一层一层剥离地址。
该字段用于INVITE、ACK和REGISTER请求以及成功响应、呼叫进展响应和重定向响应消息,其作用是给出和用户直接通信的地址。INVITE和ACK请求中的Contact字段指示该请求发出的位置,这样被叫可以直接将请求发往该地址,而不必借助Via字段经由一系列代理服务器返回。对INVITE请求的成功响应消息可包含Contact字段,它使其后SIP请求(如ACK请求)可直接发往该字段给定的地址。该地址一般是被叫主机的地址,如果该主机位于防火墙之后,则为代理服务器地址。
对应于INVITE请求的呼叫进展响应消息中包含的Contact字段的含义和成功响应消息相同。但是,CANCEL请求不能直接发往该地址,必须沿原请求发送的路径前传。
REGISTER请求中的Contact字段指明用户可达位置。该请求还定义了通配Contact字段“*”,它只能和值为0的“失效”字段配用,表示去除某用户的所有登记。Contact字段也可设定“失效”参数(任选),给定登记的失效时间。如果没有设定该参数,则用“失效”字段值作为其缺省值。如果两者均无,则认为SIP URI的失效时间为1小时。
REGISTER请求的成功响应消息中的Contact字段返回该用户当前可达的所有位置。
重定向响应消息,如用户临时迁移、永久迁移、地址模糊等消息中的Contact字段给出供重试的其它可选地址,可用于对BYE、INVITE和OPTIONS请求的响应消息。
Contact字段的一般格式为:
Contact:地址;q参数;动作参数;失效参数;扩展属性
其中,地址的表示形式和To,From字段相同。q参数,其取值范围为[0,1],指示给定位置的相对优先级。数值越大,优先级越高。动作参数仅用于REGISTER请求。它表明希望服务器对其后至该客户的请求进行代理服务还是重定向服务。如果未含此参数,则执行动作取决于服务器的配置。失效参数指明URI的有效时间,可用秒表示,也可用SIP日期表示。扩展属性就是扩展名。
Contact字段的示例为:
Contact:
该字段用于定义一个请求到达其目的地址所允许经过的中转站的最大值。请求每经过一个中转站,该值减1。如果该值为0时该请求还没有到达其目的地址,服务器将回送“483”(Too Many Hops)响应并终止这个请求。
设置该字段的目的主要是为了出现环路时不会一直消耗代理服务器的资源。该字段的初始值为70。
Max-Forwards字段的一般格式为:
Max-Forwards:十进制整数
该字段给出代理服务器支持的所有请求消息类型列表。
Allow字段的示例:
Allow: INVITE, ACK, OPTIONS, CANCEL, BYE
该字段表示消息体的大小,为十进制值。应用程序使用该字段表示要发送的消息体的大小,而不考虑实体的媒体类型。如果使用基于流的协议(如TCP协议)作为传输协议,则必须使用此消息头字段。
消息体的长度不包括用于分离消息头部和消息体的空白行。 Content-Length值必须大于等于0。如果消息中没有消息体,则Content-Length头字段值必须设为0。
SDP用于构成请求消息和2xx响应消息的消息体。
Content-Length字段的一般格式为:
Content-Length:十进制值
Content-Length字段的示例:
Content-Length: 349
表示消息体的长度为349个字节。
Content-Type字段表示发送的消息体的媒体类型。如果消息体不为空,则必须存在Content-Type 头字段。如果消息体为空且Content-Type 头字段存在,则表示此类型的消息体长度为0 (如一个空的声音文件)。
Content-Type字段的示例:
Content-Type: application/sdp
SIP协议中定义的100类临时响应消息的传输是不可靠的,即UAS发送临时响应后并不能保证UAC端能够接受到该消息。
如果需要在该响应消息中携带媒体信息,那么就必须保证该消息能够可靠的传输到对端。100rel扩展为100类响应消息的可靠传输提供了相应的机制。100rel新增加对临时响应消息的确认请求方法:PRACK。
如果UAC支持该扩展,则在发送的消息中增加Supported:100rel头域和字段。如果UAS支持该扩展,则在发送100类响应时增加Require:100rel头域和字段。UAC收到该响应消息后需要向UAS发送PRACK请求通知UAS已收到该临时响应。UAS向UAC发送对PRACK的2XX响应消息结束对该临时响应的确认过程。
如果某一UA想要在发送的临时响应消息中携带SDP消息体,那么UAC和UAS都必须支持和使用100rel扩展以保证该消息的可靠传输。
举例:
Supported: 100rel
User-Agent头字段包含有发起请求的用户终端的信息。
显示用户代理的软件版本信息可能会令用户在使用有安全漏洞的软件易受到外界攻击,因此,应该使User-Agent头字段成为可选配置项。
举例:
User-Agent: Softphone Beta1.5
Expires头字段指定了消息(或消息内容)多长时间之后超时。
举例:
Expires: 5
Accept-Language头字段用在请求消息中,表示原因短语、会话描述或应答消息中携带的状态应答内容的首选语言类型。如果消息中没有Accept-Language头字段,则服务器端认为客户端支持所有语言。
举例:
Accept-Language: en
Authorization字段包含某个终端的鉴权证书。
3、消息流程
用户每次开机时都需要向服务器注册,当SIP Client的地址发生改变时也需要重新注册。注册信息必须定期刷新。具体流程参考SIP协议详解。
4、exosip用法分析
以exosip源码中的示例程序分析,流程如下:
//初始化
context_eXosip = eXosip_malloc ();
if (eXosip_init (context_eXosip)) {
syslog_wrapper (LOG_ERR, "eXosip_init failed");
exit (1);
}
if (eXosip_listen_addr (context_eXosip, IPPROTO_UDP, NULL, port, AF_INET, 0)) {
syslog_wrapper (LOG_ERR, "eXosip_listen_addr failed");
exit (1);
}
if (localip) {
syslog_wrapper (LOG_INFO, "local address: %s", localip);
eXosip_masquerade_contact (context_eXosip, localip, port);
}
if (firewallip) {
syslog_wrapper (LOG_INFO, "firewall address: %s:%i", firewallip, port);
eXosip_masquerade_contact (context_eXosip, firewallip, port);
}
eXosip_set_user_agent (context_eXosip, UA_STRING);
if (username && password) {
syslog_wrapper (LOG_INFO, "username: %s", username);
syslog_wrapper (LOG_INFO, "password: [removed]");
if (eXosip_add_authentication_info (context_eXosip, username, username, password, NULL, NULL)) {
syslog_wrapper (LOG_ERR, "eXosip_add_authentication_info failed");
exit (1);
}
}
{
osip_message_t *reg = NULL;
int i;
regparam.regid = eXosip_register_build_initial_register (context_eXosip, fromuser, proxy, contact, regparam.expiry * 2, ®);
if (regparam.regid < 1) {
syslog_wrapper (LOG_ERR, "eXosip_register_build_initial_register failed");
exit (1);
}
i = eXosip_register_send_register (context_eXosip, regparam.regid, reg);
if (i != 0) {
syslog_wrapper (LOG_ERR, "eXosip_register_send_register failed");
exit (1);
}
}
//处理
for (;;) {
eXosip_event_t *event;
if (!(event = eXosip_event_wait (context_eXosip, 0, 1))) {
osip_usleep (10000);
continue;
}
eXosip_automatic_action (context_eXosip);
switch (event->type) {
case EXOSIP_REGISTRATION_SUCCESS:
syslog_wrapper (LOG_INFO, "registrered successfully");
break;
case EXOSIP_REGISTRATION_FAILURE:
regparam.auth = 1;
break;
default:
syslog_wrapper (LOG_DEBUG, "recieved unknown eXosip event (type, did, cid) = (%d, %d, %d)", event->type, event->did, event->cid);
}
eXosip_event_free (event);
}
eXosip_malloc、eXosip_init等初始化SIP链接的一些内存和参数,这些都是直接调用源码,可以直接沿用。下面的处理流程,是解析服务器下发的报文,并按枚举宏的形式分别处理。
总体来说,exosip调用的逻辑比较清晰,源码钻研的意义对本人来说意义不大,所以能写的比较少,就这样贴出来做一个参考吧。