7 实际使用WebRTC的线程模型

 

在这一章节我将实际演示如何将WebRTC的/trunk/talk/base目录下的所有文件编译成静态库。并且在这基础上编译运行2个小程序,以观察整个线程模型的调用关系。这将能够很好地帮助读者理解各个模块是如何协同工作的。

 

7.1 编译静态库

 

正如在本文开头时提到的,这一节的内容可能是大家最感兴趣的,但也是我不得不和大家说声抱歉的,我是在Mac OSX平台上完成了对WebRTC的/trunk/talk/base目录下的源代码的编译。所以编译脚本只能在Mac OSX下使用,不过我相信向Linux和Windows移植的工作应该不会很困难。因为:

1 WebRTC本身就是跨平台编写的,它本身就支持所有的平台;

2 谷歌已经为WebRTC编写了完美的编译脚本,这种脚本以Python为基础可以在各个平台上使用命令行编译或者生成IDE(包括Visual Studio)工程脚本。特别要提一下的是谷歌使用的ninjia实在太强大了,编译速度超快无比,推荐使用;

3 我所使用的脚本并不复杂,所以通过简单修改应该可以在各个平台上使用;

基于以上的原因,可能大家会觉得我自己写编译脚本有些多此一举。从理论上来说确实有些多此一举,但是我也有我自己的理由:

1 最近为了找工作所以学习了一些关于make脚本的知识,这次正好用来练手。其实在以前的工作中使用过nmake,不过主体框架是美国同事写得,我们不过是在添加删除文件时维护一下。所以,这次学习WebRTC源代码可以有机会在实践中检验一下对make脚本的理解。

2 我希望将/trunk/talk/base目录独立编译出来,这样可以将它作为独立的模块进行试验,避免被其他模块干扰。

 

言归正题,让我们先来看一下编译脚本:


[plain]  view plain  copy
  1. .PHONY = all create_dir clean  
  2.    
  3. CC := g++  
  4. COCOACC := g++  
  5. AR := ar  
  6.    
  7. ut_srcs := $(wildcard *_unittest.cc)  
  8. exclusive_files := $(ut_srcs) \  
  9.                                    diskcache_win32.ccjson.cc linuxwindowpicker.cc \  
  10.                                    macsocketserver.ccnatserver_main.cc schanneladapter.cc \  
  11.                                    sslidentity.ccunittest_main.cc win32.cc win32filesystem.cc \  
  12.                                    win32regkey.ccwin32securityerrors.cc win32socketserver.cc \  
  13.                                    win32window.ccwin32windowpicker.cc winfirewall.cc winping.cc  
  14.    
  15. proj_dir := ../..  
  16. obj_dir := $(proj_dir)/bin/talk_base  
  17.    
  18. all_srcs := $(wildcard *.cc)  
  19. srcs := $(filter-out $(exclusive_files),$(all_srcs))  
  20.    
  21. objs := $(patsubst %.cc, %.o, $(srcs))  
  22. fp_objs := $(addprefix $(obj_dir)/,$(objs))  
  23.    
  24. cocoa_srcs := scoped_autorelease_pool.mm maccocoathreadhelper.mm  
  25. cocoa_objs := $(patsubst %.mm, %.o,$(cocoa_srcs))  
  26. fp_cocoa_objs := $(addprefix $(obj_dir)/,$(cocoa_objs))  
  27.    
  28. CCFLAGS := -DPOSIX -DOSX -I$(proj_dir) -g  
  29. COCOACCFLAGS := -DOSX -I$(proj_dir) -g  
  30. ARFlAGS := crv  
  31.    
  32. all: create_dir $(fp_objs) $(fp_cocoa_objs)  
  33.        $(AR)$(ARFlAGS) $(obj_dir)/libtalkbase.a $(fp_objs) $(fp_cocoa_objs)  
  34.    
  35. $(fp_objs): $(obj_dir)/%.o: %.cc  
  36.        $(CC)$(CCFLAGS) -o $@ -c $^  
  37.    
  38. $(fp_cocoa_objs): $(obj_dir)/%.o: %.mm  
  39.        $(COCOACC)$(COCOACCFLAGS) -o $@ -c $^  
  40.    
  41. create_dir:  
  42.        mkdir-p $(obj_dir)  
  43.    
  44. clean:  
  45.        rm-rf $(obj_dir)  

对于使用惯Visual Studio的Windows开发人员来说,make脚本确实有些晦涩,不过以上脚本并不是很难。由于初次编写make脚本,所以特意避开了一些复杂的技巧,并且尽可能不考虑编译环境的通用性。比如,通常make脚本需要生成依赖文件(*.d),由于当前只是为了编译确定版本的WebRTC代码(代码不会发生变化),所以我省略了这一步。

 

整个make脚本的大致就是先把所有的代码文件(*.cc)罗列出来,然后去除掉unit test文件,以及一些Windows平台下特有的文件。将余下的文件使用g++编译成目标文件(*.o)。由于我使用的是Mac OSX平台,所以需要特别编译几个Mac OSX的代码文件(*.mm)文件。最后使用ar工具将所有的目标文件(*.o)打包成静态库(libtalkbase.a)。整个过程并没有什么特别的。不过,由于初次编写make脚本,有些地方并不是很规范(比如变量名的使用),请各位读者见谅。

7.2 范例代码1

 

这里先让我们看一段网上摘录的代码:

 

[cpp]  view plain  copy
  1. #include <string>  
  2. #include <iostream>  
  3. #include "talk/base/thread.h"  
  4.    
  5. class HelpData : publictalk_base::MessageData  
  6. {  
  7. public:  
  8.  std::string info_;  
  9. };  
  10.    
  11. class Police : publictalk_base::MessageHandler  
  12. {  
  13. public:  
  14.  enum {  
  15.    MSG_HELP,  
  16.   };  
  17.    
  18.  void Help(const std::string& info) {  
  19.    HelpData* data = new HelpData;  
  20.    data->info_ = info;  
  21.    talk_base::Thread::Current()->Post(this, MSG_HELP, data);  
  22.   }  
  23.    
  24.  virtual void OnMessage(talk_base::Message* msg) {  
  25.    switch (msg->message_id) {  
  26.    case MSG_HELP:  
  27.      HelpData* data = (HelpData*)msg->pdata;  
  28.      std::cout << "MSG_HELP : " << data->info_<< std::endl;  
  29.      break;  
  30.     }  
  31.   }  
  32. };  
  33.    
  34. int main(int argc, char** argv)  
  35. {  
  36.  std::cout << "Test Thread is started"<< std::endl;  
  37.  Police p;  
  38.  p.Help("Please help me!");  
  39.  talk_base::Thread::Current()->Run();  
  40.  std::cout << "Test Thread is completed"<<std::endl;  
  41.  return 0;  
  42. }  

这段代码很简单,它演示了单线程环境下如何使用消息处理器。不过这个程序会永久等待在以下语句:

talk_base::Thread::Current()->Run();

不能,正常退出。如果读者感兴趣,可以尝试以下如何使用一条延迟消息,让程序正常退出。

 

接下来,让我们看一下整个WebRTC代码的调用栈展开会是一个生个状况。可能会出乎你的意料,整个调用栈还是比较复杂的:

 

 

从上图可以看到,在调用talk_base::Thread::Current函数之后,整个调用栈涉及到了talk_base::ThreadManager、talk_base::MessageQueue、talk_base::MessageQueueManager三个类,其中talk_base::ThreadManager和talk_base::MessageQueueManager是在其Instance函数被调用后触发构造的全局唯一的对象实例。从这个调用栈可以看出,一旦调用有些talk_base::Thread的成员函数(静态/非静态)就会自动触发构造talk_base::ThreadManager和talk_base::MessageQueueManager,并且把当前线程封装成一个talk_base::Thread实例。

7.3 范例代码2

 

范例代码1是一个单线程的例子,所以并不能够足以研究WebRTC在多线程环境下的工作情况。因此,在这个例子的基础上,我把它改良成了多线程的版本:


[cpp]  view plain  copy
  1. #include <string>  
  2. #include <iostream>  
  3. #include "talk/base/thread.h"  
  4.    
  5. class HelpData : publictalk_base::MessageData  
  6. {  
  7. public:  
  8.  std::string info_;  
  9. };  
  10.    
  11. class Police : publictalk_base::MessageHandler  
  12. {  
  13. public:  
  14.  enum {  
  15.    MSG_HELP,  
  16.   };  
  17.    
  18.  void Help(talk_base::Thread& targetThread, const std::string&info) {  
  19.    HelpData* data = new HelpData;  
  20.    data->info_ = info;  
  21.    targetThread.Post(this, MSG_HELP, data);  
  22.   }  
  23.    
  24.  virtual void OnMessage(talk_base::Message* msg) {  
  25.    switch (msg->message_id) {  
  26.    case MSG_HELP:  
  27.       HelpData* data = (HelpData*)msg->pdata;  
  28.      std::cout << "MSG_HELP :" << data->info_<< std::endl;  
  29.      break;  
  30.     }  
  31.   }  
  32. };  
  33.    
  34. int main(int argc, char** argv)  
  35. {  
  36.  std::cout << "Test Multi-thread is started"<<std::endl;  
  37.  Police p;  
  38.  talk_base::Thread thread;  
  39.  thread.Start();  
  40.    
  41.  p.Help(thread"Please help me!");  
  42.  talk_base::Thread::Current()->SleepMs(100);  
  43.  std::cout << "Test Multi-thread is completed" <<std::endl;  
  44.    
  45.  return 0;  
  46. }  

这个版本的代码仅仅就是稍微改装了一点点,但是整个调用栈的情况发生了很大的变化:


 


从上图可以看出,虽然在多线程环境下情况复杂得多,但是WebRTC依然可以保证talk_base::ThreadManager和talk_base::MessageQueueManager这两个全局设施可以在任何子线程启动之前被构造完毕。

 

结合以上2个例子,加上本文对涉及的各个类的解说,我想应该大多数读者能够掌握WebRTC的线程模型的所有细节。如果还有什么疑问的话,那就只能靠动手才能解决问题了。请将以上两个例子编译通过并在你有疑惑的函数中设置log输出,甚至于单步调试你有疑惑的代码,是掌握代码原理的终极手段。
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐