oSIP协议栈.docx
- 文档编号:23797915
- 上传时间:2023-05-20
- 格式:DOCX
- 页数:15
- 大小:27.82KB
oSIP协议栈.docx
《oSIP协议栈.docx》由会员分享,可在线阅读,更多相关《oSIP协议栈.docx(15页珍藏版)》请在冰豆网上搜索。
oSIP协议栈
第一阶段:
------------------------------------------------------
先创建新工程,网上许多文档都介绍创建一个Win32动态链接库工程,我们这里也一样,创建一个空白的工程保存。
同样,将oSIP2版本3.0.1src目录下的Osipparser2目录下的所有文件都拷到我们刚创建的工程的根目录下,在VC6上操作:
Project-Add To Project-Files
将所有的源程序和头文件都加入到工程内,保存工程。
这时,我们可以尝试编译一下工程,你会得到许多错误提示信息,其内容无非是找不到osipparser2/xxxxx.h头文件之类。
处理:
在Linux下,我们一般是将头文件,lib库都拷到/usr/inclue;/usr/lib之类的目录下,c源程序里直接写#include
\ProgramFiles\MicrosoftVisualStudio\VC98\Include。
这时,我们再次编译我们的工程,顺利编译,生成osipparser2.dll,这时,网上很多文档里可能直接就说,这一步也会生成libs目录,里面里osipparser2.lib文件,但我们这里没有生成:
)
最简单的方法,不用深究,直接再创建一个工程,同上述创建动态链接库方法,创建一个Win32静态链接库工程,直接编译,即可得到osipparser2.lib。
------------------------------------------------------
上面,我们得到了Osip的解析器开发库,下面再编译完整的Osip协议栈开发库,同样照上述方法,分别创建动态链接库工程和静态链接库工程,只是要拷的文件换成src下的osip目录下文件和include下的osip目录,得到osip2.dll和osip2.lib。
在编译osip2.dll这一步可能会再次得到错误,内容含义是找不到链接库,所以,我们要把前面编译得到的osipparser2.lib也拷到osip工程目录下,并在VC6中操作:
Project-Setting-Link中的Object/Library Modules:
kernel32.libuser32.lib...xxx.lib之类的内容最后增加:
osipparser2.lib
保存工程后再次编译,即可成功编译osip2.dll。
------------------------------------------------------
至此,我们得到了完整的oSIP开发库,使用时,只需在我们的程序里包含oSIP的头文件,工程的链接参数里增加osipparser2.lib和osip2.lib即可。
------------------------------------------------------
下面我们验证一下我们得到的开发库,并大概了解一下OSIP的语法规范。
在VC里创建win32控制台程序工程,将libosip源码包的SRC目录下的Test目录内的C源程序随便拷一个到工程时,直接编译(工程设置里照前文方法在link选项里增加osip2.lib,osipparser2.lib引用我们之前成功编译得到的静态库文件)就可以运行(带参数运行,参数一般为一个文本文件,同样从Test目录的res目录里拷一个与源文件同名的纯文本文件到工程目录下即可)。
该目录下的若干文件基本上是测试了Osip的一些基本功能函数,例如URI解析之类,可以大概了解一下oSIP的语法规范和调用方法,同时也能校验一下之前编译的OSIP开发库能否正常使用,成功完成本项工作后,可以进入下一步具体的oSIP的使用学习了。
------------------------------------------------------
由于oSIP是比较底层的SIP协议栈实现,新手较难上手,而官方的示例大都是一些伪代码,需要有实际的例子程序参考学习,而最好的例子就是同样官方发布的oSIP的扩展开发库exosip2,使用exoSIP可以很方便地快速创建一个完整的SIP程序(只针对性地适用于SIP终端开发用,所以我们这里只是用它快速开发一个SIP终端,用来更方便地学习oSIP,要想真正掌握SIP的开发,需要掌握oSIP并熟读RFC文档才行,exoSIP不是我们的最终学习目的),通过成功编译运行一个自己动手开发出的程序,再由浅入深应该是初学都最好的学习方法通过对使用exosip开发库的使用创建自己的SIP程序,熟悉后再一个函数一个函数地深入学习exosip提供的接口函数,就可以深入理解osip了,达到间接学习oSIP的目的,同时也能从eXoSIP中学习到正确使用oSIP的良好的编程风格和语法格式。
而要成功编译ExoSIP,似乎许多人被难住了,直接在XP-sp2上,用VC6,虽然你使用了eXoSIP推荐的winsock2.h,但是会得到一个sockaddr_storage结构不能识别的错误,因为vc6自带的开发库太古董了,需要升级系统的Platform SDK,下载地址如下:
...PSP2FULLInstall.htm(VC6的支持已经停止,这是VC6能使用的最新SDK)
成功安装后编译前需加OSIP_MT宏,以启用线程库,否则在程序中使用eXoSIP库时会出错,而编译时也会得到许多函数未定义的Warning提示,编译得到exosip2.lib供我们使用,当然,在此之前需要成功编译了osip2和osipparser2,而在之后的实际使用时,发现oSIP也需要增加OSIP_MT宏,否则OSIP_MT调用oSIP的线程库时会出错,所以我们需要重新编译oSIP了:
),因为eXosip是基于oSIP的(同上方式创建静态和动态链接库工程,并需在Link中手工添加oSIP和oSIPparser的lib库)。
------------------------------------------------------
创建新工程,可以是任意工程,我们从最简单的Win32控制台程序开始,为了成功使用oSIP,我们需要引用相关库,调用相关头文件,经过多次试验,发现需要引用如下的库:
exosip2.libosip2.libosipparser2.libWSock32.LibIPHlpApi.LibWS2_32.LibDnsapi.lib
其中,除了我们上面编译得到的三个oSIP库外,其它库都是系统库,其中有一些是新安装的Platform SDK所新提供的。
至此,我们有了一个简单的开发环境了,可以充分利用网上大量的以oSIP为基础的代码片段和官方说明文档开始具体函数功能的测试和使用了:
)
------------------------------------------------------
我们先进行一个简单的纯SIP信令(不带语音连接建立)的UAC的SIP终端的程序开发的试验(即一个只能作为主叫不能作为被叫的的SIP软电话模型),我们创建一个MFC应用程序,对话框模式,照上面的说明,设置工程包含我们上面得到的oSIP的相关开发库及SDK的一些开发库,并且由于默认LIBC的冲突,需要排除MSVCRT[D]开发库(其中D代表Debug模式下,没有D表示Release模式下),直接使用eXosip的几个主要函数就可以创建一个基本的SIP软电话模型。
其主要流程为:
初始化eXosip库-启动事件监听线程-向SIP Proxy注册-向某SIP终端(电话号码)发起呼叫-建立连接-结束连接
初始化代码:
intret=0;
ret=eXosip_init();
eXosip_set_user_agent("##YouToo0.1");
if(0!
=ret)
{
AfxMessageBox("Couldn'tinitializeeXosip!
\n");
returnfalse;
}
ret=eXosip_listen_addr(IPPROTO_UDP,NULL,0,AF_INET,0);
if(0!
=ret)
{
eXosip_quit();
AfxMessageBox("Couldn'tinitializetransportlayer!
\n");
returnfalse;
}
启动事件监听线程:
AfxBeginThread(sip_uac,(void*)this);
向SIP Proxy注册:
eXosip_clear_authentication_info();
eXosip_add_authentication_info(uname,uname,upwd,"md5",NULL);
real_send_register(30); /*自定义函数代码请见源码*/
发起呼叫(构建假的SDP描述,实际软电话使用它构建RTP媒体连接):
osip_message_t*invite=NULL;/*呼叫发起消息体*/
inti=eXosip_call_build_initial_invite(&invite,dest_call,source_call,NULL,"##YouTootestdemo!
");
if(i!
=0)
{
AfxMessageBox("IntialINVITEfailed!
\n");
}
charlocalip[128];
eXosip_guess_localip(AF_INET,localip,128);
snprintf(tmp,4096,
"v=0\r\n"
"o=josua00INIP4%s\r\n"
"s=conversation\r\n"
"c=INIP4%s\r\n"
"t=00\r\n"
"m=audio%sRTP/AVP08101\r\n"
"a=rtpmap:
0PCMU/8000\r\n"
"a=rtpmap:
8PCMA/8000\r\n"
"a=rtpmap:
101telephone-event/8000\r\n"
"a=fmtp:
1010-11\r\n",localip,localip,"9900");
osip_message_set_body(invite,tmp,strlen(tmp));
osip_message_set_content_type(invite,"application/sdp");
eXosip_lock();
i=eXosip_call_send_initial_invite(invite);
eXosip_unlock();
挂断或取消通话:
intret;
ret=eXosip_call_terminate(call_id,dialog_id);
if(0!
=ret)
{
AfxMessageBox("hangup/terminateFailed!
");
}
可以看到非常简单,再借助于oRTP和Mediastreamer开发库,来快速为我们的SIP软电话增加RTP和与系统语音API接口交互及语音编码功能,即可以快速开发出一个可用的SIP软电话,关于oRTP和Mediastreamer的相关介绍不是本文重点,将在有空的时候考虑增加相应使用教程,文章前提到的地方可以下载基本可用的完整SIP软电话的VC源码工程文件供参考使用,完全CopyLeft,欢迎转载,但请在转载时注明作者信息,谢谢!
第二阶段:
---------------------------------------------------
得到了一个SIP软电话模型后,我们可以根据软电话的实际运行表现(结合用Ethereal抓包分析)来进行代码的分析,以达到利用eXoSIP来辅助我们学习oSIP的最终目的(如要快速开发一个可用的SIP软电话,请至前面提到的论坛去下载使用oRTP和Mediastreamer快速搭建的一个基本完整可用的SIP软电话##YouToo0.1版本的VC源码工程文件作参考)。
现在从eXosip的初始化函数开始入手,来分析oSIP的使用,这是第二阶段,第三阶段就是深入学习oSIP的源码了,但大多数情况下应该没有必要了,因为在第二阶段就有部分涉及到第三阶段的工作了,而且oSIP的源码也就大多是一些SIP数据的语法解析和状态机的实现,能深入理解了SIP协议后,这些只是一种实现方式,没必要完全去接受,而是可以用自己的方式和风格来实现一套,比如,更轻量化更有适用目的性的方式,oSIP则只起参考作用了。
eXosip_init()是eXosip的初始化函数,我们来看看它的内部实现:
首行是定义的 osip_t*osip,这在oSIP的官方手册里我们看到,所有使用oSIP的程序都要在最开始处声明一个osip_t的指针,并使用osip_init(&osip)来初始化这个指针,销毁这个资源使用osip_release(osip)即可。
我们可以在代码中看到很多OSIP_TRACE,这是调试输出宏调用了函数osip_trace,可以用ENABLE_TRACE宏来打开调试以方便我们开发调试。
其它就是很多的eXosip_t的全局变量eXosip的一些初始化操作,包括最上面的memset(&eXosip,0,sizeof(eXosip))完全清空和下面的类似eXosip.user_agent=osip_strdup("eXosip/"EXOSIP_VERSION)的exosip变量的一些初始值设置,其中有一个eXosip.j_stop_ua=0应该是一个状态机开关,后面可以看到很多代码检测这个变量来决定是否继续流程处理,默认置成了0表示现在exosip的处理流程是就绪的,即ua是notstop的。
osip_set_application_context(osip,&eXosip)是比较有意思的,它让下面的eXosip_set_callbacks(osip)给osip设置大量的回调函数时,能让osip能访问到eXosip这个全局变量中设置的大量程序运行时交互的信息,相当于我们在VC下开启一个线程时,给线程传入的一个void指针指向我们的MFC应用程序的当前dialog对象实例,可以用void*osip_get_application_context(osip_t*osip)这个函数来取出指针来使用,不过好象exosip中并没有用到它,可能是留给个人自已扩展的吧:
)
还能看到初始化代码前面有一段WIN32平台下的SOCK的初始化代码,可以知道eXosip是用的原生的winsockapi函数,也就是我们可能以前学过的用VC和WINAPI写sock程序时(不是MFC),用到的那段SOCK初始代码,还有一段有意思的代码,就是jpipe()函数,它们返回的是一个管道,一个有2个整型数值的数组(一个进一个出),查看其代码发现,非WIN32平台是直接使用的pipe系统函数,而WIN32下则是用一对TCP的本地SOCK连接来模拟的管道,一个SOCK写一个SOCK读,这段代码是比较有参考价值的:
)
j=50;
while(aport++&&j-->0)
{
raddr.sin_port=htons((short)aport);
if(bind(s,(structsockaddr*)&raddr,sizeof(raddr))<0)
{
OSIP_TRACE(osip_trace(__FILE__,__LINE__,OSIP_WARNING,NULL,
"Failedtobindonelocalsocket%i!
\n",aport));
}else
break;
}
含义即,依次检测50个端口,从staticintaport=10500;即10500~10550端口找出一个可用的本地端口来绑定listen模拟pipe的一对sock。
eXosip_set_callbacks(osip)没有什么好看的,无非是和oSIP官方文档介绍的一样,设置一大堆的回调函数,关键是回调函数的实现,这也是许多初学者使用oSIP被卡壳的主要原因,不知道oSIP构建的程序是怎样跑起来的,随便选几个回调函数看一下eXosip是怎样实现的,有许多是形如下文的函数:
staticvoid
cb_sndbye(inttype,osip_transaction_t*tr,osip_message_t*sip)
{
OSIP_TRACE(osip_trace
(__FILE__,__LINE__,OSIP_INFO3,NULL,"cb_sndbye(id=%i)\r\n",
tr->transactionid));
}
即,只是打印一下调试,并没有完整实现什么功能,我们学习时,完全可以用相同的方法,定义一大堆回调函数,并不忙想怎么完全实现,先都是只打印一下调试信息,看具体的应用逻辑根据抓包测试分析和看调试看程序走到了哪一步,调用了哪一个回调,来明白具体回调函数要实现什么用途,再来实现代码就方便多了,当然,如果看透了RFC文档,应该从字面就能知道各个回调函数的用途了,这是后话,不是谁都能快速完全看懂RFC的,所以我们要参考eXosip:
)
我们对其中的重要的回调函数进行逐个的分析:
---------------------------
osip_set_cb_send_message(osip,&cb_snd_message) SIP消息发送回调函数
这个函数可能是最重要的回调函数之一,消息发送,包括请求消息和回应消息,一般情况下,状态机的状态就是由它控制的,发起一个消息初始化一个状态机,回应一个消息对状态机修改,终结消息发送结束状态机……
看cb_snd_message的函数实现,要以发现,其主要代码是对参数中的要发送的消息osip_message_t*sip进行分析,找出消息要发送的真实char*host,intport的值(这些参数可以省略,但要发送消息肯定需要host和port,所以要从sip中解析),最后根据sip中解析出的传输方式是TCP还是UDP选择最终进行消息发送处理的函数cb_udp_snd_message,cb_tcp_snd_message处理(它们的参数一致,即本函数只是补全一些省略的参数并对消息进行合法性检查)。
**毕竟eXosip是一个通用的开发库,它考虑了要支持TCP,UDP,TCPs,IPV4,IPV6,WIN32,*nix,WINCE等等多样化的复杂环境,所以,我们可以略过我们暂时不需要的部分,比如,IPV6相关的代码实现等。
由于我们大多数情况下SIP是用的UDP,所以先来看一下cb_udp_snd_message的实现,它从全局变量exosip中获取可用的sock,并尽最大能力解析出host和port(?
?
难道前面的函数还不够解析彻底?
?
如最终仍无port信息则默认设置为5060),使用osip_message_to_str(sip,&message,&length)函数将要发送的格式化的SIP消息转换成能用SOCK传输的简单数据并发送即完成消息发送,代码中有许多复杂的环境探测和错误控制等等等等,我们可以暂时不用过多关注,可以继续向下,结尾处有一个keeplive相关代码,从代码字面分析,可能是SIP的Register消息的自动重发相关代码,可以在后面再细化分析。
cb_tcp_snd_essage的函数实现要比上文的udp的实现简单很多,主要是环境探测错误控制方面,因为毕竟tcp是稳定连接的,对比一下代码,可以看到主要流程还是将SIP消息转换后,发送到从SIP消息中解析出的host和port对应的目标。
看完两个函数,可以知道,eXosip需要有两个sock,是一个数组,0是给UDP用的,1是给TCP用的,要用SOCK当然要初始化,就是下文要介绍的eXosip的网络相关的初始化了,上面的exosip_init可以看成是这个开发库的系统初始化吧:
)
至些,我们应该知道了oSIP开发的SIP应用程序的消息是从哪里发出的吧,对了,就是从这个回调函数里,所谓万事开头难,就象开发WIN32应用程序时,找到了WIN32程序的main函数入口下面的工作就好办了,下面就都是为一些事件消息开发对应的处理函数而已了:
)
osip_set_kill_transaction_callback 事务终结回调函数
对应ICT,IST,NICT,NIST客户/服务器注册/非注册事务状态机的终结,主要是使用osip_remove_transaction(eXosip.j_osip,tr)将当前tr事务删除,再加上一系列的清理工作,其中,NICT即客户端的非Invite事务的清理比较复杂一些,要处理的内容也比较多,可以根据实际应用的情况进行有必要的清理工作:
)
cb_transport_error 传输失败处理回调
对应于上面说到的四种事务状态机,如果它们在处理时失败,则在这时进行统一处理。
从代码可知,只是在NOTIFY,SUBSCRIBE,OPTION操作失败才进行处理,其它错误可直接忽略。
osip_set_message_callback 消息发送处理回调
根据type不同,表示不同的消息发送状态
OSIP_XXX_AGAIN重发相关消息
OSIP_ICT_INVITE_SENT发起呼叫
OSIP_ICT_ACK_SENT ACK回应
OSIP_NICT_REGISTER_SENT 发起注册
OSIP_NICT_BYE_SENT BYE发出
OSIP_NICT_CANCEL_SENT Cancel发出
OSIP_NICT_INFO_SENT,OSIP_NICT_OPTIONS_SENT,OSIP_NICT_SUBSCRIBE_SENT,OSIP_NICT_NOTIFY_SENT,OSIP_NICT_UNKNOWN_REQUEST_SENT
我们可以看到,eXosip没有对它们作任何处理,我们可以根据自己需要,比如,重发2xx消息前记录一下日志之类的,扩展一下retransmission的处理方式,发起Invite前记录一下通话日志等等。
OSIP_ICT_STATUS_1XX_RECEIVED uac收到1xx消息,一般是表示对端正在处理中,这时,主要是设
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- oSIP 协议