看Linphone的代码,主程序很简单,只有三个函数: linphone-1.7.1\\console下的linphonec.c中的: main (int argc, char *argv[]) {
if (! linphonec_init(argc, argv) )
exit(EXIT_FAILURE);
}
可以看到,主要的事物处理在linphonec_main_loop这个函数里面,该函数是一个while循环,他的主要流程就是等待终端输入命令,然后根据命令来进行不同的响应或者发出不同的SIP消息,完毕然后进入下一轮循环重新等待命令的输入。既然linphonec_main_loop只是对输入命令的处理,那么对于从网络上接收到的数据的处理是在哪里实现的呢?既然linphonec_main_loop里面没有,那肯定是在linphonec_init(argc, argv)的时候启动了某个监听网络的线程。
通过对linphonec_init这个函数的代码分析,终于找到了关键点:在这里面他调用了eXosip_init()函数,而在eXosip_init()里面加载了四个有限状态机,正是这四个有限状态机线程能够对接收到的网消息进行自动的状态跳转并处理。下面对eXosip_init()进行详细的分析。
eXosip_init()是eXosip的初始化函数,我们来看看它的内部实现:
首行是定义的osip_t *osip,这在oSIP的官方手册里我们看到,所有使用oSIP
linphonec_main_loop (&linphonec, sipAddr); linphonec_finish(EXIT_SUCCESS);
exit(EXIT_SUCCESS); /* should never reach here */
1
的程序都要在最开始处声明一个osip_t的指针,并使用osip_init(&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是not stop的。
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是用的原生的winsock api函数,也就是我们可能以前学过的用VC和WINAPI写sock程序时(不是MFC),用到的那段SOCK初始代码,还有一段有意思的代码,就是jpipe()函数,它们返回的是一个管道,一个有2个整型数值的数组(一个进一个出),查看其代码发现,非WIN32平台是直接使用的
2
pipe系统函数,而WIN32下则是用一对TCP的本地SOCK连接来模拟的管道,一个SOCK写一个SOCK读,这段代码是比较有参考价值的:)
j = 50;
while (aport++ && j-- > 0) {
raddr.sin_port = htons ((short) aport);
if (bind (s, (struct sockaddr *) &raddr, sizeof (raddr)) < 0) {
OSIP_TRACE (osip_trace(__FILE__,__LINE__,OSIP_WARNING, NULL,
} else break; }
\"Failed to bind one local socket %i!\\n\
含义即,依次检测50个端口,从static int aport = 10500;即10500~10550端口找出一个可用的本地端口来绑定listen模拟pipe的一对sock。
eXosip_set_callbacks (osip)没有什么好看的,无非是和oSip官方文档介绍的一样,设臵一大堆的回调函数,关键是回调函数的实现,这也是许多初学者使用oSip被卡壳的主要原因,不知道oSip构建的程序是怎样跑起来的,随便选几个回调函数看一下eXosip是怎样实现的,有许多是形如下文的函数:
static void
cb_sndbye (int type, 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)); }
3
即,只是打印一下调试,并没有完整实现什么功能,我们学习时,完全可以用相同的方法,定义一大堆回调函数,并不忙想怎么完全实现,先都是只打印一下调试信息,看具体的应用逻辑根据抓包测试分析和看调试看程序走到了哪一步,调用了哪一个回调,来明白具体回调函数要实现什么用途,再来实现代码就方便多了,当然,如果看透了RFC文档,应该从字面就能知道各个回调函数的用途了,这是后话,不是谁都能快速完全看懂RFC的,所以我们要参考eXosip:) 我们对其中的重要的回调函数进行逐个的分析:
osip_set_cb_send_message (osip, &cb_snd_message) SIP消息发送回调函数 这个函数可能是最重要的回调函数之一,消息发送,包括请求消息和回应消息,一般情况下,状态机的状态就是由它控制的,发起一个消息初始化一个状态机,回应一个消息对状态机修改,终结消息发送结束状态机。
看cb_snd_message的函数实现,可以发现,其主要代码是对参数中的要发送的消息osip_message_t * sip进行分析,找出消息要发送的真实char *host,int port的值(这些参数可以省略,但要发送消息肯定需要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,并尽最大能力解析
4
出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事务的清理比较复杂一些,
5
要处理的内容也比较多,可以根据实际应用的情况进行有必要的清理工作:) 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前记录一下通话日志等等。
6
OSIP_ICT_STATUS_1XX_RECEIVED uac收到1xx消息,一般是表示对端正在处理中,这时,主要是设臵一下事务状态机的状态值,并对会话中的osip的一些参数根据返回值进行相应设臵,里面有许多条件判断,但我们常用的一般是100,180,183的判断而已,暂时可以忽略里面复杂的判断代码。
OSIP_ICT_STATUS_2XX_RECEIVED uac收到2xx消息,这里主要跟踪一下Register情况下的2xx,表示注册成功,这时会更新一下exosip的注册字段值,以便让eXosip能自动维护uac的注册,BYE的2xx回应是终结消息,Invite的2xx回应,则主要是初始化一下会话相关的数据,表示已成功建立连接。 其它4xx,5xx,6xx则分别是对应的处理,根据实现情况进行概要的查看即可。
report_event (je, sip)是代码中用来进行事件处理的一个函数,跟踪后发现,其最终是使用了我们上文提到的jpipe管道,以便在状态机外实时观测状态机内的处理信息。
OSIP_NIST_STATUS_XXX_SENT即对应于上面的uac的处理,这里是uas的对应的消息处理,相比较于uac简单一点。
7
linphone编译移植的建议
1.首先在x86上编译使用(最好用新的gcc或fc版本) libosip2-2.2.2(./configure,make,make install), ortp(./configure,make,make install),
ffmpeg-0.cvs20060823(./configure --prefix=/usr,make,make install), speex-1.1.12(./configure --prefix=/usr,make,make install), mediastreamer2(./configure --prefix=/usr,make,make install), linphone-1.7.1(./configure,make,make install) linphone的使用:
cd /usr/local/bin
linphonec(无视频),linphonec -V(有视频) 进入linphonec>,可以输入help看操作说明。
LINPHONE的编译(LINUX下的)
1、先装OSIP。建议下比较新的OSIP2。直接./configure make make install 2、装ORTP。在linphone下也有。直接./configure make make install 3、下载SPEEX。(新的linphone版本就不需要,我用的是1.1.0的,需要),
后安装
4、安装Linphone。直接./configure make make install
然后就可以用了。
TIPS:建议用新点的系统。REDHAT9的编译器比较旧了,编译1.1.0的话,通过不了。更新之 2. arm移植(前面的几个步骤)
1.tar jxvf arm-linux-gcc-3.3.2.tar.bz2
8
2.vi /root/.bash_profile PATH=/usr/local/arm/2.95.3/bin:$PATH
source .bash_profile
3.libosip2-2.2.2,
ncurses-5.5, readline-5.2, libogg-1.1.3, speex-1.1.12, ffmpeg-0. cvs20060823, sdl-1.2.11, alsa-lib-1.0.10, linphone-1.7.1 4.mkdir /work/usr
make clean make make instll
(1)libosip2-2.2.2
./configure --host=arm-linux --prefix=/work/usr --with-gnu-ld --disable-static (2) Ortp
./configure --host=arm-linux --prefix=/work/usr --with-gnu-ld --disable-static (3) ncurses-5.5
./configure --host=arm-linux --prefix=/work/usr --with-gnu-ld --with-shared --without-ada (4) readline-5.2
./configure --host=arm-linux --prefix=/work/usr --with-gnu-ld --disable-static (5) ffmpeg-0.cvs20060823
./configure cc=arm-linux-gcc --cross-compile --prefix=/work/usr --cpu=armv41 (6) libogg-1.1.3
./configure --host=arm-linux --prefix=/work/usr --with-gnu-ld --disable-static (7)speex-1.1.12
./configure --host=arm-linux --prefix=/work/usr --with-gnu-ld --disable-static
9
--with-ogg-libraries=/work/usr/lib --with-ogg-includes=/work/usr/include (8) alsa-lib-1.0.10
./configure --host=arm-linux --prefix=/work/usr --with-gnu-ld --disable-static (9) sdl-1.2.11
./configure--host=arm-linux --prefix=/work/usr --disable-video-dga --disable-arts --disable-esd --disable-video-x11 --disable-nasm --with-gnu-ld --with-share (10) linphone-1.7.1
./configure --host=arm-linux --prefix=/work/usr --with-gnu-ld --disable-static --disable-glib
--with-osip=/work/usr
--with-readline=/work/usr
--with-ffmpeg=/work/usr --with-sdl=/work/usr 需要交叉编译的软件包包括:
libosip2-2.2.2,ncurses-5.5,readline-5.2,libogg-1.1.3,speex-1.1.12,ffmpeg-0.cvs20060823,sdl-1.2.11,alsa-lib-1.0.10,linphone-1.7.1(包含Ortp, exosip, mediastreamer)
10
因篇幅问题不能全部显示,请点此查看更多更全内容