live555
学习笔记
1
-引子
一直想研究 ,没有时间,终于因为项目的原因可以深入无间地研究一下了.所以在此著文以记之.
一 如何编译
利用 环境很容易:在 文件夹下,
即可.
可以用 生成 可用的 ,但是对比较新的 版本支持不好,需要自
己改很多东西.
用 编译有一种更好的办法:
手动为每个库都生成一个 项目,为 生成 项目,设置好各库之间的依赖关系,就可以
用 编译了.由于 代码中没有单独支持 的东西,所以编译是相当的容易.这样就可以用
编译和调试了.
我现在怕麻烦,只用 . 的调试也很好用了.
/**********************************************************************/
/**********************************************************************/
live555
学习笔记
2 -基础类
二 基础类
讲几个重要的基础类:
和 中的类都是用于整个系统的基础功能类.比如 代表了整个系统运行
的环境,它提供了错误记录和错误报告的功能,无论哪一个类要输出错误,就需要保存 的指针.而 !"#
则提供了任务调度功能.整个程序的运行发动机就是它,它调度任务,执行任务(任务就是一个函数).!"# 由于在全局中
只有一个,所以保存在了 中.而所有的类又都保存了 的指针,所以谁想把自己的任务加入调度
中,那是很容易的.在此还看到一个结论:整个 (服务端)只有一个线程.
类 $"!:不用多说,实现了哈稀表.
类 %&'##:译为"延迟队列",它是一个队列,每一项代表了一个要调度的任务(在它的 ! 变量中保存).同时保存了这个
任务离执行时间点的剩余时间.可以预见,它就是在 !"# 中用于管理调度任务的东西.注意,此队列中的任务只被执行一次!
执行完后这一项即被无情抛弃!
类 $ :$ 集合.$ 是什么呢?它是一种专门用于执行 操作的任务(函数),$ 被 !"#
用来管理所有的 任务(增删改查).所以 !"# 中现在已调度两种任务了: 任务(" )和延迟任务
(%&'##).其实 !"# 还调度第三种任务: ,介个后面再说.
类 *#:这个是放在单独的库 *# 中。它封装了 操作,增加了多播放支持和一对多单播的功能.但我只看到它对
%+ 的支持,好像不支持 !+。它管理着一个本地 和多个目的地址,因为是 %+,所以只需知道对方地址和端口即可发送数据。
*# 的构造函数有一个参数是 # , -#.,在构造函数中首先会调用父类构造函数创建 对象,然后
判断这个地址,若是多播地址,则加入多播组。*# 的两个成员变量 /0% 和 % 1 2
都表示目的地址集和,但我始终看不出 % 1 2 有什么用,且 % 1 2 是一个没有被继承的
虚类,看起来 没有什么用。仅 % 也够用了,在 % ()和 % ()函数中就是操作 %,添加或删
除目的地址。
解释一下 *#33"% + ()函数:
[cpp]view plain copy
1. //改变目的地址的参数
2. //newDestAddr 是新的目的地址
3. //newDestPort 是新的目的端口
4. //newDestTTL 是新的 TTL
5. voidGroupsock::changeDestinationParameters(
6. structin_addrconst&newDestAddr,
7. PortnewDestPort,
8. intnewDestTTL)
9. {
10. if(fDests==NULL)
11. return;
12.
13. //获取第一个目的地址(此处不是很明白:fDest 是一个单向链表,每次添加一个目的地址,
14. //都会把它插入到最前目,难道这个函数仅改变最后一个添加的目的地址?)
15. structin_addrdestAddr=fDests->fGroupEId.groupAddress();
16. if(newDestAddr.s_addr!=0){
17. if(newDestAddr.s_addr!=destAddr.s_addr
18. &&IsMulticastAddress(newDestAddr.s_addr))
19. {
20. //如果目的地址是一个多播地址,则离开老的多播组,加入新的多播组。
21. socketLeaveGroup(env(),socketNum(),destAddr.s_addr);
22. socketJoinGroup(env(),socketNum(),newDestAddr.s_addr);
23. }
24. destAddr.s_addr=newDestAddr.s_addr;
25. }
26.
27. portNumBitsdestPortNum=fDests->fGroupEId.portNum();
28. if(newDestPort.num()!=0){
29. if(newDestPort.num()!=destPortNum&&
30. IsMulticastAddress(destAddr.s_addr))
31. {
32. //如果端口也不一样,则先更改本身 socket 的端口
33. //(其实是关掉原先的 socket 的,再以新端口打开一个 socket)。
34. changePort(newDestPort);
35. //然后把新的 socket 加入到新的多播组。
36. //Andrejointhemulticastgroup:
37. socketJoinGroup(env(),socketNum(),destAddr.s_addr);
38. }
39. destPortNum=newDestPort.num();
40. fDests->fPort=newDestPort;
41. }
42.
43. u_int8_tdestTTL=ttl();
44. if(newDestTTL!=~0)
45. destTTL=(u_int8_t)newDestTTL;
46.
47. //目标地址的所有信息都在 fGroupEId 中,所以改变成员 fGroupEId。
48. fDests->fGroupEId=GroupEId(destAddr,destPortNum,destTTL);
49.
50. //(看起来这个函数好像只用于改变多播时的地址参数,
51. //以上分析是否合理,肯请高人指点)
52. }
/**********************************************************************/
/**********************************************************************/
live555
学习笔记
3 -消息循环
三消息循环
看服端的主体: 中的 ()函数,可见其创建一个 /!+ 类实例后,即进入一个函数 4
5 "#() 6()中,看名字很明显是一个消息循坏,执行到里面后不停地转圈,生名不息,转圈不止。那么在这个人
生的圈圈中如何实现 /!+ 服务和 /!+ 传输呢?别想那么远了,还是先看这个圈圈中实现了什么功能吧。
[cpp]view plain copy
1. voidBasicTaskScheduler0::doEventLoop(char*watchVariable){
2. //Repeatedlyloop,handlingreadblesocketsandtimedevents:
3. while(1){
4. if(watchVariable!=NULL&&*watchVariable!=0)
5. break;
6. SingleStep();
7. }
8. }
!"#7 从 !"# 派生,所以还是一个任务调度对象,所以依然说明任务调度对象是整个程序的发动机。
循环中每次走一步: ()。这走一步中都做些什么呢?
总结为以下四步:
1为所有需要操作的 执行 。
2找出第一个应执行的 任务(")并执行之。
3找到第一个应响应的事件,并执行之。
4找到第一个应执行的延迟任务并执行之。
可见,每一步中只执行三个任务队列中的一项。下面详细分析函数 ():
[cpp]view plain copy
1. //循坏中主要执行的函数
2. voidBasicTaskScheduler::SingleStep(unsignedmaxDelayTime){
3. fd_setreadSet=fReadSet;//makeacopyforthisselect()call
4. fd_setwriteSet=fWriteSet;//ditto
5. fd_setexceptionSet=fExceptionSet;//ditto
6.
7. //计算 selectsocket 们时的超时时间。
8. DelayIntervalconst&timeToDelay=fDelayQueue.timeToNextAlarm();
9. structtimevaltv_timeToDelay;
10. tv_timeToDelay.tv_sec=timeToDelay.seconds();
11. tv_timeToDelay.tv_usec=timeToDelay.useconds();
12. //Verylarge"tv_sec"valuescauseselect()tofail.
13. //Don'tmakeitanylargerthan1millionseconds(11.5days)
14. constlongMAX_TV_SEC=MILLION;
15. if(tv_timeToDelay.tv_sec>MAX_TV_SEC){
16. tv_timeToDelay.tv_sec=MAX_TV_SEC;
17. }
18. //Alsocheckour"maxDelayTime"parameter(ifit's>0):
19. if(maxDelayTime>0
20. &&(tv_timeToDelay.tv_sec>(long)maxDelayTime/MILLION
21. ||(tv_timeToDelay.tv_sec==(long)maxDelayTime/MILLION
22. &&tv_timeToDelay.tv_usec
23. >(long)maxDelayTime%MILLION))){
24. tv_timeToDelay.tv_sec=maxDelayTime/MILLION;
25. tv_timeToDelay.tv_usec=maxDelayTime%MILLION;
26. }
27.
28. //先执行 socket 的 select 操作,以确定哪些 socket 任务(handler)需要执行。
29. intselectResult=select(fMaxNumSockets,
30. &readSet,&writeSet,&exceptionSet,
31. &tv_timeToDelay);
32.
33. if(selectResult<0){
34. //#ifdefined(__WIN32__)||defined(_WIN32)
35. interr=WSAGetLastError();
36. //Forsomeunknownreason,select()inWindozesometimesfailswithWSAEINVALif
37. //itwascalledwithnoentriessetin"readSet".Ifthishappens,ignoreit:
38. if(err==WSAEINVAL&&readSet.fd_count==0){
39. err=EINTR;
40. //Tostopthisfromhappeningagain,createadummysocket:
41. intdummySocketNum=socket(AF_INET,SOCK_DGRAM,0);
42. FD_SET((unsigned)dummySocketNum,&fReadSet);
43. }
44. if(err!=EINTR){
45. //#else
46. //if(errno!=EINTR&&errno!=EAGAIN){
47. //#endif
48. //Unexpectederror-treatthisasfatal:
49. //#if!defined(_WIN32_WCE)
50. //perror("BasicTaskScheduler::SingleStep():select()fails");
51. //#endif
52. internalError();
53. }
54. }