首页 > 代码库 > webrtc教程

webrtc教程

cdsn博客不支持word文件,所以这里显示不完全。可到本人资源中下载word文档:

v0.3:http://download.csdn.net/detail/kl222/6961491

v0.1:http://download.csdn.net/detail/kl222/6677635

 下载完后评论,可以返还你的积分。此文档还在完善中,欢迎大家交流,共同完善。

 

 

  

Webrtc  教程

 

 

版本0.3(2014年2月)

康林 (16614119@qq.com)

 

本文博客地址:http://blog.csdn.net/kl222/article/details/17198873

 

版本

内容

时间

作者

V0.1

初建文档

2013年11月

康林

V0.2

增加协议文档

2014年2月8日

康林

V0.3

细化了windows下编译过程。

增加IDE工具用法。

重新排版整个文档。

2014年2月20日

康林

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1     工具:...4

1.1       depot_tools:.4

1.1.1  目标:...4

1.1.2  chromium·使用它来...4

1.1.3  使用说明在这儿...4

1.1.4  下载:...4

1.1.4.1    linux下:...4

1.1.4.2    window下:...5

1.1.5  使用:...5

1.1.5.1    用gclient 获取代码...5

1.1.5.2    gclient 命令:...5

1.1.6  具体使用例子:.5

1.1.6.1        安装工具...5

1.1.6.2        .配置...6

1.2       Gyp工具...6

1.3       Python工具...6

1.4       本地集成开发环境(IDE)...6

1.4.1    Visual studio.6

1.4.2    Kdevelop.12

1.4.3    Eclipse.13

2     Webrtc.13

2.1       下载、编译:...13

2.1.1  Windows下:...13

2.1.2  ubuntu下编译:...17

2.1.3  编译Android(只能在linux下):.20

3     webrtc开发:...20

3.1       开发P2P视频软件需要处理的问题...20

3.1.1  用户列的获取、交换、信令的交换...20

3.1.2  P2P通信...20

3.1.3  多媒体处理...21

3.1.3.1    音视频捕获、播放...21

3.1.3.2    音视频编解码...21

3.1.3.3    音视频效果优化...21

3.2       webrtc架构:...21

3.2.1  WebRTC架构组件介绍...21

(1)     你的web应用程序...21

(2)     Web API.21

(3)     WebRTCNative C++ API.21

(4)     Transport /Session.21

(5)     VoiceEngine.22

(6)     VideoEngine.23

3.2.2  WebRTC核心模块API介绍...23

(1)        网络传输模块:libjingle.23

(2)        WebRTC Native C++ API.23

(3)        音频、视频图像处理的主要数据结构...23

(4)        音频引擎(VoiceEngine)模块 APIs.24

(5)        视频引擎(VideoEngine)模块 APIs.25

3.2.3  webRTC核心API详解...26

3.2.3.1    libjingle_peerconnection.26

3.2.3.2    libjingle_media库:...30

3.2.3.2.1 视频采集,处理、渲染类:...30

4     Libjingle详细介绍...34

4.1          重要组件:...34

4.1.1    信号:...34

4.1.2    线程和消息...34

4.1.3    名称转换...34

4.1.4    Ssl支持...34

4.1.5    连接...34

4.1.6    传输,通道,连接...35

4.1.7    候选项...35

4.1.8    数据包...35

4.2          如何工作:...35

4.2.1    Application模块...36

4.2.2    XMPP Messaging Component.模块...36

4.2.3    Session Logic and management commponent模块。...37

4.2.4    Peer to peer Component模块。...37

4.2.5    其他...38

4.3        建立libjingle应用程序...38

5     代码分析:...38

5.1       音频通道建立过程:...38

5.2       音频接收播放过程:...39

5.3       视频接收播放过程:...42

6     协议:.44

6.1       XMPP协议:.44

6.1.1  原理介绍...44

6.1.2    XMPP协议网络架构...45

6.1.3    XMPP协议的组成...47

6.1.4  Xmpp介绍...48

6.1.5  协议内容...48

6.2       Stun协议:.51

6.2.1  P2P实现的原理...52

6.2.2  P2P的常用实现...54

6.2.3  Stun uri.57

6.2.4  内容...57

6.2.5    中文内容...58

6.2.6    开源服务器...60

6.2.7    公开的免费STUN服务器...60

6.3       Turn协议...61

6.3.1    概念...61

6.3.2    Turn uri62

6.3.3    开源服务器工程...62

6.3.4    开源库...62

6.4        交互式连接建立(InteractiveConnectivity Establishment),一种综合性的NAT穿越的技术。   62

6.4.1    IETF 规格...67

6.4.2    开源工程:...68

6.5       XEP-0166 Jingle.69

6.5.1    绪论...70

6.5.2    需求...71

6.6       Sctp协议...95

6.7       Rtp协议...97

7     附件:...99

7.1       Gyp工具...99

7.2       Google test程序...101

7.3       Webrtc库介绍...102

7.4       webrtc代码相关基础知识...103

7.5       STUN和TURN技术浅析...105

7.6       基于ICE的VoIP穿越NAT改进方案...106

7.7       ubuntu安装使用stuntman.111

7.8       一个开源的ICE库——libnice介绍...111

7.9       4种利用TURN穿越对称型NAT方案的设计与实现...113

7.10    基于ICE方式SIP信令穿透Symmetric_NAT技术研究...114

 

 

 

1  工具:

1.1  depot_tools:

chromium自己整了一套开发工具系统,原来叫gclient(名字好像让位给google桌面客户端了) ,现在改名depot_tools。

1.1.1目标:

Wrapper script for checking out and updating source code frommultiple SCM repository locations.

chromium使用了(目前 @159834)107个代码仓库的代码,这些分散在多个代码仓库,chromiun不需要某些仓库的东西,google就封装个工具,这个工具既支持svn,也支持git,不光能down代码,也支持了

·       patch

·       cpplint,pylint

·      apply_issue

·      junction

·      codereview

1.1.2 chromium使用它来

·      更新chromium代码

·      生成工程文件,windows上生产sln,mac生产xcode工程,linux生成scons或者makefile

·      其他的patch,codereview,管理分散开发人员的修改

1.1.3 使用说明在这儿

http://www.chromium.org/developers/how-tos/depottools

1.1.4下载:

1.1.4.1 linux下:

sudo apt-getinstall git

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

 

1.1.4.2 window下:

l  已装cygwin:

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

l  无cygwin:

https://src.chromium.org/svn/trunk/tools/depot_tools.zip

1.1.5使用:

1.1.5.1 用gclient获取代码

·      首先会更新 depot_tools,有两种 bat和sh,目的都一样
更新depot_tools,然后运行python版gclient.py,参数都传给gclient.py
 这里解决了鸡生蛋还是蛋生鸡的问题,更新了gclient.py

·      生成.gclient文件,gclient指定了某个版本的chromium·代码

·      执行gclient sync,更新代码,生成工程文件,这里使用了另一个工具 GYP

1.1.5.2 gclient命令:

Commands are:

  cleanup    Cleans up allworking copies.

  config     Create a.gclient file in the current directory.

  diff       Displayslocal diff for every dependencies.

  fetch      Fetchesupstream commits for all modules.

  help       Prints listof commands or help for a specific command.

  hookinfo   Output the hooksthat would be run by `gclient runhooks`

  pack       Generate apatch which can be applied at the root of the tree.

  recurse    Operates on allthe entries.

  revert     Revert allmodifications in every dependencies.

  revinfo    Output revisioninfo mapping for the client and its dependencies.

  runhooks   Runs hooks for filesthat have been modified in the local working copy.

  status     Showmodification status for every dependencies.

  sync      Checkout/update all modules.

  update     Alias for thesync command. Deprecated.

 

 

Prints list of commands or help for aspecific command.

 

 

Options:

  --version            show program‘s version number and exit

  -h, --help           show this help message and exit

  -j JOBS, --jobs=JOBS  Specifyhow many SCM commands can run in parallel;

                       default=8

  -v, --verbose        Produces additional output for diagnostics. Can be

                       used up to three times for morelogging info.

  --gclientfile=CONFIG_FILENAME

                       Specify an alternate .gclient file

  --spec=SPEC          create a gclient file containing the provided string.

                       Due to Cygwin/Python brokenness, itprobably can‘t

                       contain any newlines.

 

1.1.6 具体使用例子:

1.1.6.1   安装工具

http://www.chromium.org/developers/how-tos/install-depot-tools

1.1.6.2   .配置

主要是写

.gclient和DEPS python语法(精确点就是json语法+“#”型注释,list最末元素可以有,执行时使用python的eval来解释的)

.gclient

 

1.2  Gyp工具

Gyp工具简介见附件。Google自己搞的玩意。Webrtc不是直接用的gyp,而是又封装了一下。

Webrtc中的gyp工具是build/ gyp_chromium

1.3  Python工具

整个webrtc工程是由python程序进行维护。而整个webrtc工程现在处理一个完全不稳定的阶段。所以需要了解一些基本的python语法知识。

1.4  本地集成开发环境(IDE)

工欲善其事,必先利其器。所以下面介绍下本地集成开发环境。

1.4.1    Visual studio

一般的在windows本地开发环境(IDE)是visualstudio。然而webrtc默认的是ninja。虽然可以手工生成vs工程,但是经常出现兼容性问题。所以下面介绍下vs加ninja开发环境。

Webrtc默认生成的工程位于源码根目录下的out目录中。

打开vs2012:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1.4.2    Kdevelop

官网地址:http://kdevelop.org/

它是一个跨平台开源的集成开发环境。用c++开发。所以效率比较高。本人比较喜欢用它在linux下开发,或用它开发跨平台程序。它默认支持ninja。

 

1.4.3    Eclipse

       官网地址:http://eclipse.org/

Eclipse是一个用java写的跨平台集成开发环境。支持java、c、c++等。它由于是用java开发的,所以效率比用c、c++开发的低。如果你的机器比较好,用这个也挻文便的。用于android开发。

 

2  Webrtc

2.1  下载、编译:

注意:由于天朝的限制,所以下载是一个艰难的过程(最好FQ)。由于google采用ninja做为默认编译工程。所以当你用其它本地建立方式(VS、make等)时,你要自己解决兼容问题。关于建立工具、编译工具的版本,最好采用最新的版本,否则也会出现兼容性问题。

相关工具安装,最好采用默认值。否则也可能出现兼容问题。

 

2.1.1Windows下:

1)  安装vs2012旗舰版

http://www.microsoft.com/zh-cn/download/details.aspx?id=30678

注册号:

Visual Studio 2012 Ultimate旗舰版序列号:

 

YKCW6-BPFPF-BT8C9-7DCTH-QXGWC

RBCXF-CVBGR-382MK-DFHJ4-C69G8

YQ7PR-QTHDM-HCBCV-9GKGG-TB2TM

 

建立用最新版本vs,vs2008以前版本不能保证百分之百兼容。因为开发者用的是最新版本。

 

2)  安装依赖库:

a)        Windows SDK(主要是需要DirectX sdk,主要是捕获、Render时用的是DirectX)

windows sdk介绍:

官网:

http://msdn.microsoft.com/zh-cn/windows/bb980924.aspx

从 Windows 7开始,DirectX SDK 就作为 Windows SDK 的一部分包含在内。

 

 

注意:然而webrtc现在用的是directx9.0 sdk。所以只要在下面地址下载:

http://www.microsoft.com/en-us/download/details.aspx?id=6812

 

b)       Windows DDK(好象可以不用安装)

从windows vista开始,ddk改成wdk。

官网:http://msdn.microsoft.com/zh-cn/windows/hardware/gg454513

Vista以后的版本,可以用最新的windowswdk 8.0

如果是windows xp,则需要wdk7.1.0以前的版本。

 

3)  下载depot_tools工具

a)  先装cygwin:

git clonehttps://chromium.googlesource.com/chromium/tools/depot_tools.git

b)  无cygwin:

https://src.chromium.org/svn/trunk/tools/depot_tools.zip

4)  把路径设置到环境变量PATH中。

5)  下载webrtc代码,最好选择稳定代码下载,trunk是当前开发代码库。

6)  配置下载代码库:

E:\source\muli\google>gclientconfighttp://webrtc.googlecode.com/svn/trunk/

这一步主要下载git、svn、python和配置文件.gconfig。

默认配置下载与平台相应的代码,如果要下其它平台代码。修改.gconfig文件,加入target_os = [‘windows‘,‘android‘,‘unix‘]

7)  设置产生者和版本号。此步可选

E:\source\muli\google\trunk>setGYP_GENERATORS=msvs  #设置产生者(可选)

指定工程文件类型,如果没有这一步,默认使用ninja

make forMakefiles

msvs forVisual Studio

msvs-ninja forVisual Studio project building with ninja

xcode for Xcode

(Windows: ninja/Visual Studio, OSX: ninja/XCode, Linux: ninja/make, Android: ninja)

 

E:\source\muli\google\trunk>setGYP_MSVS_VERSION=2012 #设置vs产生者版本(可选)

 

8)  同步源代码。

E:\source\muli\google>gclient sync --force       #同步源码

更新depot_tools工具、git、svn、python工具、下载webrtc代码及相关工具,有1G多大小。注意:如果下载中卡住了,需要FQ。

这步完成时,会自动调用gyp产生工程。如果没有设置前面两步,则默认的为ninja工程。

(Windows: ninja/Visual Studio, OSX: ninja/XCode, Linux: ninja/make, Android: ninja)

如果用默认工程,就可以直接到第10)操作。

 

9)  重新产生工程

E:\source\muli\google>gclient runhooks --force    #运行hooks,重新产生工程

 

10)     手工生成工程

E:\source\muli\google\trunk>python build\gyp_chromium.py--depth . -G msvs_version=2012  all.gyp            #手工产生指定版本的工程

在trunk目录下生all.sln

-Gmsvs_version :vs的版本号。产生者由环境变量GYP_GENERATORS设置

--generator-output:工程产生目录,但是生成出来的工程有问题,所以不选。

 

Msvs版本:建议用最新的版本号,因为开发者用的是最近的版本,所以老版本的兼容性可能会有问题。

 

11)     编译:

用vs2012打开工程文件all.sln 。

 

用ninja编译

ninja -C out/Debug All

 

12)  编译时可能出现的问题:

问题一:

________ running‘C:\openmeetings\google\depot_tools\python_bin\python.exe trunk

/webrtc/build/gyp_webrtc-Dextra_gyp_flag=0‘ in ‘C:\openmeetings\google‘

ERROR at//build/config/win/visual_studio_version.gni:33:24: Script returned non

-zero exit code.

  visual_studio_path =exec_script("get_visual_studio_path.py",

                       ^----------

Current dir:C:\openmeetings\google\trunk\out\gn_build.Debug64\

Command:"C:\openmeetings\google\depot_tools\python_bin\python.exe""C:\openmeet

ings\google\trunk\build\config\win\get_visual_studio_path.py"auto

Returned 1 andprinted out:

原因是由于没有安装visual studio引起的。解决方法是安装vs2012。

 

问题三:

E:\source\muli\google\trunk>pythonbuild\gyp_chromium.py --depth . -G msvs_versi

on=2012 all.gyp

Traceback (most recent call last):

 File "build\gyp_chromium.py", line 18, in <module>

   execfile(os.path.join(path, ‘gyp_chromium‘))

 File "E:\source\muli\google\trunk\build\gyp_chromium", line42, in <module>

   import find_depot_tools

ImportError: No module namedfind_depot_tools

原因是开发人员把这个模块给删掉了。但不知道为什么这么明显的BUG却没有测试到并修复?也许是开发人员从不用第10步生成工程。Webrtc代码处理开发阶段,很不稳定。在项目管理方面比其它开源项目(例如:ffmpeg、vlc、libnice)差很多。当然相应不久将来,这个BUG也许会被修复。

解决方法:

在gyp_chromium.py42行前加入下面行。

sys.path.insert(1,os.path.join(chrome_src, ‘webrtc‘, ‘build‘))

从下面地址下载find_depot_tools工具到trunk\webrtc\build 下:https://github.com/adobe/chromium/blob/master/tools/find_depot_tools.py

 

问题二:

用vs2008编译,出现下面问题:

3>.\video_coding\main\source\receiver.cc(159): error C2668: “abs”:对重载函数的调用不明确

3>        C:\Program Files\Microsoft VisualStudio 9.0\VC\include\math.h(539):可能是“long double abs(long double)”

3>        C:\Program Files\Microsoft VisualStudio 9.0\VC\include\math.h(491):或      “float abs(float)”

3>        C:\Program Files\Microsoft VisualStudio 9.0\VC\include\math.h(487):或      “double abs(double)”

3>        C:\Program Files\Microsoft VisualStudio 9.0\VC\include\math.h(485):或      “long abs(long)”

3>        C:\Program Files\Microsoft VisualStudio 9.0\VC\include\stdlib.h(380):或      “int abs(int)”

3>        试图匹配参数列表“(const int64_t)”时

3>.\video_coding\main\source\receiver.cc(164): error C2668: “abs”:对重载函数的调用不明确

3>        C:\Program Files\Microsoft VisualStudio 9.0\VC\include\math.h(539):可能是“long double abs(long double)”

3>        C:\Program Files\Microsoft VisualStudio 9.0\VC\include\math.h(491):或      “float abs(float)”

3>        C:\Program Files\Microsoft VisualStudio 9.0\VC\include\math.h(487):或      “double abs(double)”

3>        C:\Program Files\Microsoft VisualStudio 9.0\VC\include\math.h(485):或      “long abs(long)”

3>        C:\Program Files\Microsoft VisualStudio 9.0\VC\include\stdlib.h(380):或      “int abs(int)”

 

以上问题是由于vs2008对C99支持不够,用最新版本vs2012就可以解决。也可以修改代码进行类型强制转换。

 

2.1.2ubuntu下编译:

1)  安装depot_tools:

svnco http://src.chromium.org/svn/trunk/tools/depot_tools

或者:

git clonehttps://chromium.googlesource.com/chromium/tools/depot_tools.git

 

我的depot_tools下到了 /data/google/depot_tools 中。

 

2)  设置环境变量,把这个目录加入到PATH中:

exportPATH=$PATH:/data/google/depot_tools

3)  下载webrtc代码,最好选择稳定代码下载,trunk是当前开发代码库。

gclient config http://webrtc.googlecode.com/svn/trunk/(生成.gconfig文件)

默认配置下载与平台相应的代码,如果要下其它平台代码。修改.gconfig文件,加入target_os = [‘windows‘,‘android‘,‘unix‘]

gclientsync --force(同步项目文件,要下载1个多G的文件,网速不好的,可以去玩一会再回来),注意:如果下载中卡住了,需要FQ。

gclientrunhooks --force (重新生成相应的工程文件,Linux的MakeFile文件)

 

4)  安装依赖开发库:

apt-get installlibasound2-dev

apt-get install libpulse-dev

apt-get install libx11-dev

apt-get install libxext-dev

apt-get install libnss3-dev

 

5)  生成工程文件

a)  生成make工程

k@k-C410:/data/google/webrtc/trunk$export GYP_GENERATORS=make

指定工程文件类型,如果没有这一步,默认使用ninja

make forMakefiles

msvs forVisual Studio

msvs-ninja forVisual Studio project building with ninja

xcode for Xcode

 

k@k-C410:/data/google/webrtc/trunk$build/gyp_chromium --depth=. all.gyp

如果你没有安装依赖库,可能会出现下面错误:

Updating projects from gypfiles...

Package nss was not found inthe pkg-config search path.

Perhaps you should add thedirectory containing `nss.pc‘

to the PKG_CONFIG_PATHenvironment variable

No package ‘nss‘ found

gyp: Call to ‘pkg-config--libs-only-L --libs-only-other nss‘ returned exit status 1. while loadingdependencies of all.gyp while trying to load all.gyp

 

安装libnss:

k@k-C410:/data/google/webrtc/trunk$sudo apt-get install libnss3-dev

 

________ running‘/usr/bin/python trunk/webrtc/build/gyp_webrtc -Dextra_gyp_flag=0‘ in‘/home/google‘

Generating gypfiles from GN...

Updating projectsfrom gyp files...

Package gconf-2.0was not found in the pkg-config search path.

Perhaps you shouldadd the directory containing `gconf-2.0.pc‘

to thePKG_CONFIG_PATH environment variable

No package‘gconf-2.0‘ found

gyp: Call to‘pkg-config --cflags gconf-2.0‘ returned exit status 1.

Error: Command/usr/bin/python trunk/webrtc/build/gyp_webrtc -Dextra_gyp_flag=0 returnednon-zero exit status 1 in /home/google

安装gconf-2.0库:

k@k-C410:/home/google$ sudoapt-get install gconf-2.0

 

然后再生成编译工程:

k@k-C410:/data/google/webrtc/trunk$build/gyp_chromium --depth=. all.gyp

 

在当前目录下产生Makefile 文件。

 

编译:

k@k-C410:/data/google/webrtc/trunk$make peerconnection_server

k@k-C410:/data/google/webrtc/trunk$make peerconnection_client

生成的文件放在out目录下。

k@k-C410:/data/google/webrtc/trunk/out/Debug$ls

genmacro    libvpx_obj_int_extract  obj.target             re2c

genmodule   libyuv.a                peerconnection_client  yasm

genperf     linker.lock             peerconnection_server

genstring   obj                     protoc

genversion  obj.host                pyproto

 

你可以看到:

 

peerconnection_client、 peerconnection_server两个应用程序。

运行:

启动服务器:

k@k-C410:/data/google/webrtc/trunk/out/Debug$./peerconnection_server

Server listening on port 8888

 

启动客户端:

k@k-C410:/data/google/webrtc/trunk/out/Debug$./peerconnection_client

 

 

 

也可以用trunk/talk/examples/peerconnection/server/server_test.html目录下的页面进行测试。

 

 

a)  生成默认工程,默认为ninja

k@k-C410:/data/google/webrtc/trunk$build/gyp_chromium --depth=. all.gyp

如果你没有安装依赖库,可能会出现下面错误:

Updating projectsfrom gyp files...

Package nss wasnot found in the pkg-config search path.

Perhaps you shouldadd the directory containing `nss.pc‘

to thePKG_CONFIG_PATH environment variable

No package ‘nss‘found

gyp: Call to‘pkg-config --libs-only-L --libs-only-other nss‘ returned exit status 1. whileloading dependencies of all.gyp while trying to load all.gyp

 

安装libnss库:

k@k-C410:/data/google/webrtc/trunk$sudo apt-get install libnss3-dev

 

然后再生成编译工程:

k@k-C410:/data/google/webrtc/trunk$build/gyp_chromium --depth=. all.gyp

 

在当前目录下产生out目录,在out目录中有Debug、Release两个子目录,在子目录中有ninja工程文件build.ninja

 

编译指定的目标:

k@k-C410:/data/google/webrtc/trunk$ninja -C out peerconnection_server

k@k-C410:/data/google/webrtc/trunk$ ninja -C outpeerconnection_client

编译所有工程目标:

k@k-C410:/data/google/webrtc/trunk$ ninja -C out All

在Debug下可以看到许多的测试程序。

 

2.1.3编译Android(只能在linux下):

设置环境变量:ANDROID_NDK_ROOT、ANDROID_SDK_ROOT、JAVA_HOME

k@k-C410:/data/google /webrtc/trunk$export JAVA_HOME=/data/jdk1.7.0_45

k@k-C410:/data/google/webrtc/trunk$exportANDROID_SDK_ROOT=/data/adt-bundle-linux-x86_64-20130917/sdk

k@k-C410:/data/google/webrtc/trunk$export ANDROID_NDK_ROOT=/data/android-ndk-r9

k@k-C410:/data/google/webrtc/trunk$source build/android/envsetup.sh

k@k-C410:/data/google/webrtc/trunk$ build/gyp_chromium --depth=. all.gyp#此步可省

k@k-C410:/data/google/webrtc/trunk$ ninja -C out/Debug All  #此步可省

在编译过程中会有些类实例调用静态方法的警告错误。需要提示修改代码。

生成所有程序。

k@k-C410:/data/google/webrtc/trunk/out/Debug$ls *.apk

AppRTCDemo-debug.apk  OpenSlDemo-debug.apk  WebRTCDemo-debug.apk

 

 

在Debug下可以看到许多的测试程序。

 

 

3  webrtc开发:

    在编译环境搭建完成后,可以开始干活了。我们用webrtc的主要目的是应用它的音/视频捕获、视频显示、音频播放、音视频压缩、网络通信。如果你只是想在HTML5中用音/视频解决方案,可以跳过本教程,因为webrtc的最终目标是实现HTML5的音视频解决方案。本教程主要讲解在应用程序中如何使用webrtc库。

 

3.1  开发P2P视频软件需要处理的问题

3.1.1用户列的获取、交换、信令的交换

XMPP协议主要用于解决获取用户列表、交换用户数、信令交换。

 

3.1.2P2P通信

现实网络环境有三种情况:

1.  公共网络:这类网络IP之间可以不受限制的进行直接访问。

2.  NAT网络:这类网络主机在私有内网中,没有单独的公网IP。但可以通过打洞来找到它在公网中固定的网络地址。STUN协议就是解决些网络问题。

3.  严格的受限NAT网络:这类网络中的主机在私网内,只能单向访问外网。外网不能直接访问它。所以这类网络需要通过在公共网络上的TURN服务器来进行数据中转。TRUN协议就是解决此网络问题的。

为了解决地址转换、防火墙限制访问等问题。所以提供了ICE协议,ICE协议是一个框架,它依赖STUN协议解决完全锥形NAT、以及受限锥形NAT。TURN协议用于解决严格受限NAT网络的问题。

 

3.1.3多媒体处理

3.1.3.1 音视频捕获、播放

3.1.3.2 音视频编解码

3.1.3.3 音视频效果优化

 

3.2  webrtc架构:

 

 

(1)紫色部分是Web开发者API层;

(2)蓝色实线部分是面向浏览器厂商的API层(本教程主要讲解的部分)

(3)蓝色虚线部分浏览器厂商可以自定义实现

 

3.2.1WebRTC架构组件介绍

(1)你的web应用程序

Web开发者开发的程序,Web开发者可以基于集成WebRTC的浏览器提供的web API开发基于视频、音频的实时通信应用。

 

(2)WebAPI

面向第三方开发者的WebRTC标准API(Javascript),使开发者能够容易地开发出类似于网络视频聊天的web应用,最新的标准化进程可以查看这里。

 

(3)WebRTCNative C++ API

本地C++ API层,使浏览器厂商容易实现WebRTC标准的Web API,抽象地对数字信号过程进行处理。主要是 PeerConnection 对象。

 

(4)Transport/ Session

传输/会话层

会话层组件采用了libjingle库的部分组件实现,无须使用xmpp/jingle协议。

参见:https://developers.google.com/talk/talk_developers_home

现在libjingle已经移到webrtc项目中维护了。位于源码根目录的 talk 目录下。

 

a.  RTPStack协议栈

Real Time Protocol

 

b.  STUN/ICE

可以通过STUN和ICE组件来建立不同类型网络间的呼叫连接。现实网络环境有三种情况:

1.  公共网络:这类网络IP之间可以不受限制的进行直接访问。

2.  NAT网络:这类网络主机在私有内网中,没有单独的公网IP。但可以通过打洞来找到它在公网中固定的网络地址。STUN协议就是解决些网络问题

3.  严格的受限NAT网络:这类网络中的主机在私网内,只能单向访问外网。外网不能直接访问它。所以这类网络需要通过在公共网络上的服务器来进行数据中转。TRUN协议就是解决此网络问题的。

ICE是一个框架,在这个框架中,它会判断主机是上面三种类型之一,并用相应的办法来建立主机之间的连接。

 

c.  Session Management
一个抽象的会话层,提供会话建立和管理功能。该层协议留给应用开发者自定义实现。

 

(5)VoiceEngine

音频引擎是包含一系列音频多媒体处理的框架,包括从视频采集卡到网络传输端等整个解决方案。

PS:VoiceEngine是WebRTC极具价值的技术之一,是Google收购GIPS公司后开源的。在VoIP上,技术业界领先,后面的文章会详细了解

 

a.  iSAC

InternetSpeech Audio Codec

针对VoIP和音频流的宽带和超宽带音频编解码器,是WebRTC音频引擎的默认的编解码器
采样频率:16khz,24khz,32khz;(默认为16khz)
自适应速率为10kbit/s ~ 52kbit/;
自适应包大小:30~60ms;
算法延时:frame + 3ms

 

b.  iLBC
Internet Low Bitrate Codec
VoIP音频流的窄带语音编解码器
采样频率:8khz;
20ms帧比特率为15.2kbps
30ms帧比特率为13.33kbps
标准由IETF RFC3951和RFC3952定义


c.  NetEQ for Voice

针对音频软件实现的语音信号处理元件

NetEQ算法:自适应抖动控制算法以及语音包丢失隐藏算法。使其能够快速且高解析度地适应不断变化的网络环境,确保音质优美且缓冲延迟最小。

是GIPS公司独步天下的技术,能够有效的处理由于网络抖动和语音包丢失时候对语音质量产生的影响。

PS:NetEQ 也是WebRTC中一个极具价值的技术,对于提高VoIP质量有明显效果,加以AEC\NR\AGC等模块集成使用,效果更好。

 

d. Acoustic Echo Canceler (AEC)
回声消除器是一个基于软件的信号处理元件,能实时的去除mic采集到的回声。

 

e.  Noise Reduction (NR)
噪声抑制也是一个基于软件的信号处理元件,用于消除与相关VoIP的某些类型的背景噪声(嘶嘶声,风扇噪音等等… …)

 

(6)VideoEngine

WebRTC视频处理引擎

VideoEngine是包含一系列视频处理的整体框架,从摄像头采集视频到视频信息网络传输再到视频显示整个完整过程的解决方案。

 

a.  VP8
视频图像编解码器,是WebRTC视频引擎的默认的编解码器
VP8适合实时通信应用场景,因为它主要是针对低延时而设计的编解码器。
PS:VPx编解码器是Google收购ON2公司后开源的,VPx现在是WebM项目的一部分,而WebM项目是Google致力于推动的HTML5标准之一

 

b.  Video Jitter Buffer
视频抖动缓冲器,可以降低由于视频抖动和视频信息包丢失带来的不良影响。

 

c.  Image enhancements
图像质量增强模块
对网络摄像头采集到的图像进行处理,包括明暗度检测、颜色增强、降噪处理等功能,用来提升视频质量。

 

3.2.2WebRTC核心模块API介绍

(1)      网络传输模块:libjingle

WebRTC重用了libjingle的一些组件,主要是network和transport组件,关于libjingle的文档资料可以查看这里。参见:https://developers.google.com/talk/talk_developers_home

现在这个模块已经放到webrtc中进行维护了。位于源码根目录的talk目录下。

 

(2)      WebRTC Native C++ API

代码位于源码根目录的talk\app\webrtc 目录下。webrtc封装了peerconnection 对象,这个对象把流媒体传输也封装进去了。

PeerConnection类厂接口:

//PeerConnectionFactoryInterface is the factory interface use for creating

//PeerConnection, MediaStream and media tracks.

//PeerConnectionFactoryInterface will create required libjingle threads,

//socket and network manager factory classes for networking.

//If an application decides to provide its own threads and network

//implementation of these classes it should use the alternate

//CreatePeerConnectionFactory method which accepts threads as input and use the

//CreatePeerConnection version that takes a PortAllocatorFactoryInterface as

//argument.

PeerConnectionFactoryInterface

 

 

PeerConnectionInterface接口:

peerconnection API:talk/app/webrtc/peerconnectioninterface.h

 

webrtc本地API:talk/app/webrtc/mediastreaminterface.h

 

 

(3)      音频、视频图像处理的主要数据结构

常量\VideoEngine\VoiceEngine

 

注意:以下所有的方法、类、结构体、枚举常量等都在webrtc命名空间里  

类、结构体、枚举常量

头文件

说明

Structures

common_types.h

Lists the structures common to the VoiceEngine & VideoEngine

Enumerators

common_types.h

List the enumerators common to the  VoiceEngine & VideoEngine

Classes

common_types.h

List the classes common to VoiceEngine & VideoEngine

class VoiceEngine

voe_base.h

How to allocate and release resources for the VoiceEngine using factory methods in the VoiceEngine class. It also lists the APIs which are required to enable file tracing and/or traces as callback messages

class VideoEngine

vie_base.h

How to allocate and release resources for the VideoEngine using factory methods in the VideoEngine class. It also lists the APIs which are required to enable file tracing and/or traces as callback messages

 

(4)      音频引擎(VoiceEngine)模块 APIs

 

下表列的是目前在 VoiceEngine中可用的sub APIs

sub-API

头文件

说明

VoEAudioProcessing

voe_audio_processing.h

Adds support for Noise Suppression (NS), Automatic Gain Control (AGC) and Echo Control (EC). Receiving side VAD is also included.

VoEBase

voe_base.h

Enables full duplex VoIP using G.711.
NOTE: This API must always be created.

VoECallReport

voe_call_report.h

Adds support for call reports which contains number of dead-or-alive detections, RTT measurements, and Echo metrics.

VoECodec

voe_codec.h

Adds non-default codecs (e.g. iLBC, iSAC, G.722 etc.), Voice Activity Detection (VAD) support.

VoEDTMF

voe_dtmf.h

Adds telephone event transmission, DTMF tone generation and telephone event detection. (Telephone events include DTMF.)

VoEEncryption

voe_encryption.h

Adds external encryption/decryption support.

VoEErrors

voe_errors.h

Error Codes for the VoiceEngine

VoEExternalMedia

voe_external_media.h

Adds support for external media processing and enables utilization of an external audio resource.

VoEFile

voe_file.h

Adds file playback, file recording and file conversion functions.

VoEHardware

voe_hardware.h

Adds sound device handling, CPU load monitoring and device information functions.

VoENetEqStats

voe_neteq_stats.h

Adds buffer statistics functions.

VoENetwork

voe_network.h

Adds external transport, port and address filtering, Windows QoS support and packet timeout notifications.

VoERTP_RTCP

voe_rtp_rtcp.h

Adds support for RTCP sender reports, SSRC handling, RTP/RTCP statistics, Forward Error Correction (FEC), RTCP APP, RTP capturing and RTP keepalive.

VoEVideoSync

voe_video_sync.h

Adds RTP header modification support, playout-delay tuning and monitoring.

VoEVolumeControl

voe_volume_control.h

Adds speaker volume controls, microphone volume controls, mute support, and additional stereo scaling methods.

 

(5)      视频引擎(VideoEngine)模块 APIs

下表列的是目前在VideoEngine中可用的sub APIs

sub-API

头文件

说明

ViEBase

vie_base.h

Basic functionality for creating a VideoEngine instance, channels and VoiceEngine interaction.

NOTE: This API must always be created.

ViECapture

vie_capture.h

Adds support for capture device allocation as well as capture device capabilities.

ViECodec

vie_codec.h

Adds non-default codecs, codec settings and packet loss functionality.

ViEEncryption

vie_encryption.h

Adds external encryption/decryption support.

ViEErrors

vie_errors.h

Error codes for the VideoEngine

ViEExternalCodec

vie_external_codec.h

Adds support for using external codecs.

ViEFile

vie_file.h

Adds support for file recording, file playout, background images and snapshot.

ViEImageProcess

vie_image_process.h

Adds effect filters, deflickering, denoising and color enhancement.

ViENetwork

vie_network.h

Adds send and receive functionality, external transport, port and address filtering, Windows QoS support, packet timeout notification and changes to network settings.

ViERender

vie_render.h

Adds rendering functionality.

ViERTP_RTCP

vie_rtp_rtcp.h

Adds support for RTCP reports, SSRS handling RTP/RTCP statistics, NACK/FEC, keep-alive functionality and key frame request methods.

 

 

3.2.3webRTC核心API详解

webRTC本地API:http://www.webrtc.org/reference/native-apis。

翻译:http://www.cnblogs.com/longrenle/archive/2012/03/04/2378433.html 。

webRTC本地API是基于WebRTC spec的实现。webRTC的实现代码(包括流和PeerConnection API)是在libjingle中实现。

 

线程模型

WebRTCnative APIs 拥有两个全局线程:信令线程(signaling thread)和工作者线程(worker thread)。取决于PeerConnection factory被创建的方式,应用程序可以提供这两个线程或者直接使用内部创建好的线程。

Stream APIs和PeerConnectionAPIs的调用会被代理到信令线程,这就意味着应用程序可以在任何线程调用这些APIs。

所有的回调函数都在信令线程调用。应用程序应当尽快地跳出回调函数以避免阻塞信令线程。严重消耗资源的过程都应当其他的线程执行。

工作者线程被用来处理资源消耗量大的过程,比如说数据流传输。

 

3.2.3.1 libjingle_peerconnection

这个模块封装了对等网络连接通信过程。

 

块图:

 

 

调用顺序:

安装调用:

 

 

// The Following steps areneeded to setup a typical call usingJsep.

// 1. Create aPeerConnectionFactoryInterface. Check constructors for more

// information about inputparameters.

// 2. Create a PeerConnectionobject. Provide a configuration string which

// points either to stun orturn server to generate ICE candidates and provide

// an object that implementsthe PeerConnectionObserver interface.

// 3. Create localMediaStream and MediaTracks using the PeerConnectionFactory

// and add it toPeerConnection by calling AddStream.

// 4. Create an offer andserialize it and send it to the remote peer.

// 5. Once an ice candidatehave been found PeerConnection will call the

// observer functionOnIceCandidate. The candidates must also be serialized and

// sent to the remote peer.

// 6. Once an answer isreceived from the remote peer, call

// SetLocalSessionDescriptionwith the offer and SetRemoteSessionDescription

// with the remote answer.

// 7. Once a remote candidateis received from the remote peer, provide it to

// the peerconnectionby calling AddIceCandidate.

 

 

 

 

接收调用:

 

 

// The Receiver of a call candecide to accept or reject the call.

// This decision will betaken by the application notpeerconnection.

// If application decides toaccept the call

// 1. CreatePeerConnectionFactoryInterface if it doesn‘t exist.

// 2. Create a newPeerConnection.

// 3. Provide the remoteoffer to the new PeerConnection object by calling

//SetRemoteSessionDescription.

// 4. Generate an answer tothe remote offer by calling CreateAnswer and send it

// back to the remote peer.

// 5. Provide the localanswer to the new PeerConnection by calling

// SetLocalSessionDescriptionwith the answer.

// 6. Provide the remote icecandidates by calling AddIceCandidate.

// 7. Once a candidate havebeen found PeerConnection will call the observer

// function OnIceCandidate.Send these candidates to the remote peer.

 

 

关闭调用:

 

 

 

3.2.3.2 libjingle_media库:

3.2.3.2.1   视频采集,处理、渲染类:

视频捕获类:

 

 

l  得到VideoCapture过程:

cricket::VideoCapturer*Conductor::OpenVideoCaptureDevice() {

  talk_base::scoped_ptr<cricket::DeviceManagerInterface>dev_manager(

      cricket::DeviceManagerFactory::Create());

  if (!dev_manager->Init()) {

    LOG(LS_ERROR) <<"Can‘t create devicemanager";

    returnNULL;

  }

  std::vector<cricket::Device>devs;

  if (!dev_manager->GetVideoCaptureDevices(&devs)) {

    LOG(LS_ERROR) <<"Can‘t enumerate videodevices";

    returnNULL;

  }

  std::vector<cricket::Device>::iteratordev_it = devs.begin();

  cricket::VideoCapturer*capturer =NULL;

  for (;dev_it !=devs.end(); ++dev_it) {

    capturer =dev_manager->CreateVideoCapturer(*dev_it);

    if (capturer !=NULL)

      break;

  }

  returncapturer;

}

 

说明:

Ø  捕获设备的建立:

建立DeviceManagerFactory实例。Windows下在实例初始化时并实例化DefaultVideoCapturerFactory。

调用dev_manager->GetVideoCaptureDevices得到设备。

调用dev_manager->CreateVideoCapturer建立一个视频捕获对象。

在dev_manager->CreateVideoCapturer中,

先检查是否是文件捕获对象。如果,则返回文件捕获对象指针。

如果不是,调用device_video_capturer_factory_->Create(device);建立视频捕获对象。这里device_video_capturer_factory_=DefaultVideoCapturerFactory

 

classDefaultVideoCapturerFactory :publicVideoCapturerFactory {

 public:

  DefaultVideoCapturerFactory() {}

  virtual ~DefaultVideoCapturerFactory() {}

 

  VideoCapturer*Create(constDevice&device) {

#ifdefined(VIDEO_CAPTURER_NAME)

    VIDEO_CAPTURER_NAME*return_value =newVIDEO_CAPTURER_NAME;

    if (!return_value->Init(device)) {

      deletereturn_value;

      returnNULL;

    }

    returnreturn_value;

#else

    return NULL;

#endif

  }

};

 

这个DefaultVideoCapturerFactory类厂很简单,在Create函数中建立WebRtcVideoCapturer对象。并调用Init初始化此对象。在这个初始化中,会建立调用module_ = factory_->Create(0,vcm_id);其中factory_建立过程如下:

WebRtcVcmFactory->VideoCaptureFactory-> videocapturemodule::VideoCaptureImpl-> VideoCaptureDS

类关系详见捕获设备类:

 

VideoCaptureModuleV4L2:linux下v4l2捕获设备。

VideoCaptureDS:windows下DirectxShow捕获设备。

VideoCaptureAndroid:android下捕获设备。

 

Ø  捕获数据流:

在WebRtcVideoCapturer中的Start函数中,会把回调处理对象VideoCaptureDataCallback与视频捕获设备对象VideoCaptureModule进行关联。这样,当视频捕获设备对象有视频帧被捕获时。会调用VideoCaptureDataCallback中的OnIncomingCapturedFrame。WebRtcVideoCapturer继承VideoCaptureDataCallback,所以会调用WebRtcVideoCapturer::OnIncomingCapturedFrame。在其中调用SignalFrameCaptured(this, &frame);发送捕获信息。会调用基类VideoCapturer::OnFrameCaptured。在这里会对视频格式由BITMAP转换为I420。并且会调用已注册的VideoProcessors对象。还会SignalVideoFrame(this, &i420_frame);发送视频帧信号。

 

l  渲染类:

 

GdiVideoRenderer:windows gdi渲染

GtkVideoRenderer: GTK库渲染,这个用在linux平台。

 

 

 

4  Libjingle详细介绍

Libjingle现在已转到webrtc项目中维护了。位于源码talk目录中。

4.1         重要组件:

4.1.1    信号:

4.1.2    线程和消息

4.1.3    名称转换

4.1.4    Ssl支持

4.1.5    连接

一对libjingle的对等连接实际上是由两个通道组成:

会话协商通道(也称为信令信道)是用于协商数据连接的通信链路。此通道用于请求连接,交换人选,并协商会话的详细信息(如套接字地址,需要编解码器,以交换文件,连接变更请求,并终止请求)。这是计算机之间进行的第一次连接,并且仅在由该连接可以将数据信道来建立。对libjingle采用先导,以jingle指定要建立数据连接所需的节和响应(见jingle和对libjingle),该通道通过中介XMPP服务器发送节;示例代码中使用了谷歌Talk服务器作为中介。

       数据通道是在点对点会话之间交换实际数据(音频、视频、文件等)。数据通道的数据依赖于传输协商,被捆绑在TCP或UDP包中。并且不经过XMPP服务器。

       首先会话协商信道被建立,作为计算机协商数据信道的细节;数据连接后,大部分活动发生在数据信道,除非一个编解码器的改变,一个新的文件请求,偶尔请求重定向请求或终止请求。

下图显示了这两种途径。在该图中,两个备用数据路径被示出,虽然只有一个数据通路将被激活,在一个连接。这是因为数据通路可以是直接连接(连接尝试的92%,可以直接发生),或者通过中继服务器(连接尝试的8%所需要的中介中继服务器)。第三个数据通路,没有显示,是从计算机到计算机的直接连接时,有没有中介防火墙。

 

 

 

注意事项:

对libjingle发出偶尔STUN数据包,以保持可写性,保持防火墙和NAT地址绑定活跃,并检查连接延迟。

    对libjingle分配一个用户名和密码来连接端口。这确保了连接上的数据信道的计算机是同一个,协商通过信令信道的连接。由于这些用户名和密码值发送XMPP协议可能会或可能不会使用TLS进行加密,在STUN包这些值仅作识别,未加密的身份验证使用。

 

4.1.6    传输,通道,连接

4.1.7    候选项

4.1.8    数据包

 

4.2         如何工作:

https://developers.google.com/talk/libjingle/libjingle_applications?hl=zh-CN

 

 

4.2.1    Application模块

Libjingle的应用程序首先调用XMPP Messaging Component的XmppClient对象进行登录,然后做一些message,iq,presence等request/respond操作。

其次,每个application可能包含一个或多个sessionclient用来做P2P操作,比如远程协助,视频会议,音频连接,文件共享等等。

4.2.2    XMPP MessagingComponent.模块

此模块主要由三个部分组成:XmppClient,LoginHandler和Xmpp Helper Task.此模块主要做相当于一个peer的防火墙的功能,连接服务器和客户端,负责发送所有本地的stanza请求(即XMPP协议内容),并负责接收服务器的stanza请求,并分发到各个Helper Task里。

  • XmppClient主要是代理登录,发送stanza,接收stanza。之所以说是代理,是因为真正发送,接收的消息都是通过XmppEngine来实现的。
  • XmppEngine能注册多个XmppStanzaHandler回调,然后所有从服务器接收的Stanza都转发到已绑定的XmppStanzaHandler进行过滤,而实际上是回调XmppTask对象。
  • XmppStanzaHandler类定义如下:
  1. //! Callback to deliver stanzas to an Xmpp application module.  
  2. //! Register via XmppEngine.SetDefaultSessionHandler or via  
  3. //! XmppEngine.AddSessionHAndler.    
  4. class XmppStanzaHandler {  
  5. public:  
  6.   virtual ~XmppStanzaHandler() {}  
  7.   //! Process the given stanza.  
  8.   //! The handler must return true if it has handled the stanza.  
  9.   //! A false return value causes the stanza to be passed on to  
  10.   //! the next registered handler.  
  11.   virtual bool HandleStanza(const XmlElement * stanza) = 0;  
  12. };  
  • XmppTask是所有XmppHelperTask的基类,并继承自XmppStanzaHandler,主要有监听,过滤 XmppEngine对象转发过来的Stanza消息。XmppTask有多种类型,当取类型为HL_PEEK时,只有监听功能,无法做到过滤;而其他类 型可以做到过滤。过滤是有HandlerStanza函数来完成,当返回为true时,过滤,否则XmppEngine枚举下一个绑定的XmppTask 继续尝试分发、过滤。
  • 所有XmppHelperTask都要继承XmppTask并要重载HandlerStanza函数和ProcessStart函数。
    • HandlerStanza是用来过滤,相当于windows消息处理的GetMessage()
    • 而ProcessStart是用来处理HandlerStanza过滤的消息。
    • 比如在源代码example/call/presencepushtask.h里:

[cpp] view plaincopy

  1. class PresencePushTask : public XmppTask {  
  2.  public:  
  3.   PresencePushTask(XmppTaskParentInterface* parent, CallClient* client)  
  4.     : XmppTask(parent, XmppEngine::HL_TYPE),  
  5.       client_(client) {}  
  6.   virtual int ProcessStart();  
  7.   
  8.   sigslot::signal1<const Status&> SignalStatusUpdate;  
  9.   sigslot::signal1<const Jid&> SignalMucJoined;  
  10.   sigslot::signal2<const Jid&, int> SignalMucLeft;  
  11.   sigslot::signal2<const Jid&, const MucStatus&> SignalMucStatusUpdate;  
  12.   
  13.  protected:  
  14.   virtual bool HandleStanza(const XmlElement * stanza);  
  15.   void HandlePresence(const Jid& from, const XmlElement * stanza);  
  16.   void HandleMucPresence(buzz::Muc* muc,  
  17.                          const Jid& from, const XmlElement * stanza);  
  18.   static void FillStatus(const Jid& from, const XmlElement * stanza,  
  19.                          Status* status);  
  20.   static void FillMucStatus(const Jid& from, const XmlElement * stanza,  
  21.                             MucStatus* status);  
  22.   
  23.  private:  
  24.   CallClient* client_;  
  25. };  
    • 这里PresencePushTask类,通过HandleStanza过滤所有presence相关的stanza并在ProcessStart里处理所有来自服务器的用户状态更新消息。
  • LoginHandler部分是由XmppPump来负责的。主要调用XmppClient的connect和disconnect方法建立、断开连接,监听SignalStateChange事件来获取连接信息,类型为STATE_OPENED的事件表示连接成功。

4.2.3    Session Logicand management commponent模块。

所有p2p session逻辑相关的部分都放在了这个模块。可以session可能是处理文件传输的连接,或者可能是视频会话,或者音频会话等等。

  • 我们需要继承SessionClient来处理每个Session相关具体任务,比如文件传输Session:当接收对端客户端建立一个文件传输 session的时候,如果此Session是新创建的,SessionManager对象会回调所有注册的SessionClient的 OnSessionCreate的接口,并以SessionManger创建的Session对象为参数穿进去;如果是已有的Session则会调用 Session的OnIncomingMessage方法。
  • Session对象则抽象了两个peer之间的数据传输接口。当收到OnSessionCreate回调时,SessionClient可以通过Session的方法Accept来接受创建,Reject来拒绝。
  • 那怎么读写p2p数据呢?
    • 首先需要调用session的CreateChannel方法获取TransportChannel对象指针,然后监听TransportChannel的事件SignalReadPacket来接收数据,通过SendPacket方法来发送数据。
  1. class TransportChannel: public sigslot::has_slots<> {  
  2.  public:  
  3. //......  
  4.   
  5.   // Attempts to send the given packet.  The return value is < 0 on failure.  
  6.   virtual int SendPacket(const char *data, size_t len) = 0;  
  7.   // Signalled each time a packet is received on this channel.  
  8.   sigslot::signal3<TransportChannel*, const char*, size_t> SignalReadPacket;  
  9. //......  
  10. };  

 

4.2.4    Peer to peerComponent模块。

此模块才是libjingle核心,libjingle项目的初衷也是能够把模块设计得完美,使得所有需要通过P2P传输数据的应用层调用libjingle时,不用担心数据传输的稳定性,可靠性,高效性。

  • 刚才上面提到,当服务器发送stanza时XmppEngine把Stanza发送到XmppTask过滤,在这个模 块,SessionManagerTask代理SessionManager过滤session相关的stanza,并转发到 SessionManager对象,如下:
  1. class SessionManagerTask : public buzz::XmppTask {  
  2.  public:  
  3.   ......  
  4.   
  5.   virtual int ProcessStart() {  
  6.     const buzz::XmlElement *stanza = NextStanza();  
  7.     if (stanza == NULL)  
  8.       return STATE_BLOCKED;  
  9.     session_manager_->OnIncomingMessage(stanza);  
  10.     return STATE_START;  
  11.   }  
  12.   
  13.  protected:  
  14.   virtual bool HandleStanza(const buzz::XmlElement *stanza) {  
  15.     if (!session_manager_->IsSessionMessage(stanza))  
  16.       return false;  
  17.     QueueStanza(stanza);  
  18.     return true;  
  19.   }  
  20.   
  21.   
  22. }  // namespace cricket  

 

  • SessionManager类在这里起到连接上述3个模块的桥梁作用。
  • 当上层调用SessionManager创建的Session对象的CreateChannel时,实际上是调用P2PTransport的CreateChannel方法。
  • 上层通过P2PTransport创建P2pTransportChannel的类的。
  • P2pTransportChannel 继承自TransportChannel,并创建多个不同的Connection,每个Connection代表一个TCP或者UDP或者SSL连接。上 层传输数据最终是调到P2pTransportChannel的相关方法,当发送,接收数据时,P2pTransportChannel选择表现最好的 Connection进行传输。

4.2.5    其他

LigJingle提供了很多接口供我们继承,用于特定的个性化Session,同时也提供了不少实例(如pcp,login,call)让调用者更容易的理解框架思路。当需要着手研究libjingle时,如果能够充分的利用已成的实例,对于缩短熟悉时间,很有帮助。

 

4.3   建立libjingle应用程序

 

5  代码分析:

5.1  音频通道建立过程:

       peerconnection_client.exe!webrtc::RtpReceiverImpl::RtpReceiverImpl(intid=65536, webrtc::Clock * clock=0x017d4e68, webrtc::RtpAudioFeedback *incoming_audio_messages_callback=0x0417a7d4, webrtc::RtpFeedback *incoming_messages_callback=0x0417a7c4, webrtc::RTPPayloadRegistry * rtp_payload_registry=0x0417d060,webrtc::RTPReceiverStrategy * rtp_media_receiver=0x0417d338)  行91 C++

       peerconnection_client.exe!webrtc::RtpReceiver::CreateAudioReceiver(intid=65536, webrtc::Clock * clock=0x017d4e68, webrtc::RtpAudioFeedback *incoming_audio_feedback=0x0417a7d4, webrtc::RtpData *incoming_payload_callback=0x0417a7c0, webrtc::RtpFeedback *incoming_messages_callback=0x0417a7c4, webrtc::RTPPayloadRegistry *rtp_payload_registry=0x0417d060)  行61 + 0x47 字节   C++

      peerconnection_client.exe!webrtc::voe::Channel::Channel(intchannelId=0, unsigned int instanceId=1, const webrtc::Config &config={...})  行931 + 0x1e7 字节   C++

     peerconnection_client.exe!webrtc::voe::Channel::CreateChannel(webrtc::voe::Channel* & channel=0xcccccccc, int channelId=0, unsigned int instanceId=1, constwebrtc::Config & config={...})  行753 + 0x2a 字节  C++

      peerconnection_client.exe!webrtc::voe::ChannelManager::CreateChannelInternal(constwebrtc::Config & config={...})  行64 + 0x1f 字节      C++

      peerconnection_client.exe!webrtc::voe::ChannelManager::CreateChannel(constwebrtc::Config & external_config={...}) 行59 + 0x10 字节  C++

      peerconnection_client.exe!webrtc::VoEBaseImpl::CreateChannel(constwebrtc::Config & config={...})  行552    C++

      peerconnection_client.exe!cricket::WebRtcVoiceEngine::CreateVoiceChannel(cricket::VoEWrapper* voice_engine_wrapper=0x01a572f8)  行1652 + 0x23 字节     C++

      peerconnection_client.exe!cricket::WebRtcVoiceEngine::CreateMediaVoiceChannel()  行1657 C++

      peerconnection_client.exe!cricket::WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel(cricket::WebRtcVoiceEngine* engine=0x01a56e1c)  行1765 + 0x3d 字节      C++

      peerconnection_client.exe!cricket::WebRtcVoiceEngine::CreateChannel()  行635 + 0x22 字节      C++

  peerconnection_client.exe!cricket::CompositeMediaEngine<cricket::WebRtcVoiceEngine,cricket::WebRtcVideoEngine>::CreateChannel()  行191     C++

      peerconnection_client.exe!cricket::ChannelManager::CreateVoiceChannel_w(cricket::BaseSession* session=0x03fade50, const std::basic_string<char,std::char_traits<char>,std::allocator<char>> & content_name="audio", bool rtcp=true)  行327 + 0x1d 字节       C++

      peerconnection_client.exe!talk_base::MethodFunctor3<cricket::ChannelManager,cricket::VoiceChannel* (__thiscall cricket::ChannelManager::*)(cricket::BaseSession*,std::basic_string<char,std::char_traits<char>,std::allocator<char>> const &,bool),cricket::VoiceChannel *,cricket::BaseSession*,std::basic_string<char,std::char_traits<char>,std::allocator<char>> const &,bool>::operator()()  行294 + 0x2b 字节       C++

      peerconnection_client.exe!talk_base::FunctorMessageHandler<cricket::VoiceChannel*,talk_base::MethodFunctor3<cricket::ChannelManager,cricket::VoiceChannel *(__thiscall cricket::ChannelManager::*)(cricket::BaseSession*,std::basic_string<char,std::char_traits<char>,std::allocator<char>> const &,bool),cricket::VoiceChannel *,cricket::BaseSession*,std::basic_string<char,std::char_traits<char>,std::allocator<char>> const &,bool> >::OnMessage(talk_base::Message *msg=0x03a0fdc8)  行58 + 0xb 字节      C++

      peerconnection_client.exe!talk_base::Thread::ReceiveSends()  行456 + 0x13 字节 C++

      peerconnection_client.exe!talk_base::MessageQueue::Get(talk_base::Message* pmsg=0x03a0ff38, int cmsWait=-1, bool process_io=true)  行205 + 0xf 字节  C++

      peerconnection_client.exe!talk_base::Thread::ProcessMessages(intcmsLoop=-1)  行508 + 0x19 字节       C++

      peerconnection_client.exe!talk_base::Thread::Run()  行371      C++

      peerconnection_client.exe!talk_base::Thread::PreRun(void* pv=0x01a551f0)  行358 + 0x13 字节      C++

      kernel32.dll!76033c45()     

      [下面的框架可能不正确和/或缺失,没有为 kernel32.dll 加载符号]    

      ntdll.dll!771a37f5()     

      ntdll.dll!771a37c8()     

 

5.2  音频接收播放过程:

       peerconnection_client.exe!webrtc::acm1::AudioCodingModuleImpl::IncomingPacket(constunsigned char * incoming_payload=0x0026f5bc, const int payload_length=3, constwebrtc::WebRtcRTPHeader & rtp_info={...}) 行2032 C++

      peerconnection_client.exe!webrtc::voe::Channel::OnReceivedPayloadData(constunsigned char * payloadData=http://www.mamicode.com/0x0026f5bc, unsigned short payloadSize=3, constwebrtc::WebRtcRTPHeader * rtpHeader=0x03a5e0fc) 行545 + 0x2a 字节 C++

      peerconnection_client.exe!webrtc::RTPReceiverAudio::ParseAudioCodecSpecific(webrtc::WebRtcRTPHeader* rtp_header=0x03a5e0fc, const unsigned char * payload_data=http://www.mamicode.com/0x0026f5bc,unsigned short payload_length=3, const webrtc::AudioPayload &audio_specific={...}, bool is_red=false) 行395 + 0x22 字节 C++

      peerconnection_client.exe!webrtc::RTPReceiverAudio::ParseRtpPacket(webrtc::WebRtcRTPHeader* rtp_header=0x03a5e0fc, const webrtc::PayloadUnion &specific_payload={...}, bool is_red=false, const unsigned char *payload=0x0026f5bc, unsigned short payload_length=3, __int64 timestamp_ms=16210844,bool is_first_packet=true)  行206 + 0x1e 字节 C++

      peerconnection_client.exe!webrtc::RtpReceiverImpl::IncomingRtpPacket(const webrtc::RTPHeader & rtp_header={...}, const unsignedchar * payload=0x0026f5bc, int payload_length=3, webrtc::PayloadUnionpayload_specific={...}, bool in_order=true) 行244 + 0x65 字节   C++

      peerconnection_client.exe!webrtc::voe::Channel::ReceivePacket(constunsigned char * packet=0x0026f5b0, int packet_length=15, constwebrtc::RTPHeader & header={...}, bool in_order=true)  行2095 + 0x44 字节    C++

      peerconnection_client.exe!webrtc::voe::Channel::ReceivedRTPPacket(constchar * data=http://www.mamicode.com/0x0026f5b0, int length=15) 行2076 + 0x19 字节 C++

      peerconnection_client.exe!webrtc::VoENetworkImpl::ReceivedRTPPacket(intchannel=0, const void * data=http://www.mamicode.com/0x0026f5b0, unsigned int length=15) 行128 + 0x10 字节 C++

      peerconnection_client.exe!cricket::WebRtcVoiceMediaChannel::OnPacketReceived(talk_base::Buffer* packet=0x03a5e784, const talk_base::PacketTime & packet_time={...})  行2964 + 0x47 字节      C++

      peerconnection_client.exe!cricket::BaseChannel::HandlePacket(boolrtcp=false, talk_base::Buffer * packet=0x03a5e784, const talk_base::PacketTime& packet_time={...})  行643 + 0x23 字节   C++

      peerconnection_client.exe!cricket::BaseChannel::OnChannelRead(cricket::TransportChannel* channel=0x040bf4a0, const char * data=http://www.mamicode.com/0x04244008, unsigned int len=25, consttalk_base::PacketTime & packet_time={...}, int flags=0) 行392 C++

      peerconnection_client.exe!cricket::VoiceChannel::OnChannelRead(cricket::TransportChannel* channel=0x040bf4a0, const char * data=http://www.mamicode.com/0x04244008, unsigned int len=25, consttalk_base::PacketTime & packet_time={...}, int flags=0) 行1425 C++

      peerconnection_client.exe!sigslot::_connection5<cricket::BaseChannel,cricket::TransportChannel*,char const *,unsigned int,talk_base::PacketTime const&,int,sigslot::single_threaded>::emit(cricket::TransportChannel *a1=0x040bf4a0, const char * a2=0x04244008, unsigned int a3=25, consttalk_base::PacketTime & a4={...}, int a5=0) 行2047 + 0x2a 字节  C++

      peerconnection_client.exe!sigslot::signal5<cricket::TransportChannel*,char const *,unsigned int,talk_base::PacketTime const&,int,sigslot::single_threaded>::operator()(cricket::TransportChannel *a1=0x040bf4a0, const char * a2=0x04244008, unsigned int a3=25, consttalk_base::PacketTime & a4={...}, int a5=0) 行2616 + 0x30 字节   C++

      peerconnection_client.exe!cricket::TransportChannelProxy::OnReadPacket(cricket::TransportChannel* channel=0x0423b968, const char * data=http://www.mamicode.com/0x04244008, unsigned int size=25, consttalk_base::PacketTime & packet_time={...}, int flags=0) 行243 C++

      peerconnection_client.exe!sigslot::_connection5<cricket::TransportChannelProxy,cricket::TransportChannel*,char const *,unsigned int,talk_base::PacketTime const&,int,sigslot::single_threaded>::emit(cricket::TransportChannel *a1=0x0423b968, const char * a2=0x04244008, unsigned int a3=25, consttalk_base::PacketTime & a4={...}, int a5=0) 行2047 + 0x2a 字节      C++

      peerconnection_client.exe!sigslot::signal5<cricket::TransportChannel*,char const *,unsigned int,talk_base::PacketTime const&,int,sigslot::single_threaded>::operator()(cricket::TransportChannel *a1=0x0423b968, const char * a2=0x04244008, unsigned int a3=25, consttalk_base::PacketTime & a4={...}, int a5=0) 行2616 + 0x30 字节   C++

      peerconnection_client.exe!cricket::DtlsTransportChannelWrapper::OnReadPacket(cricket::TransportChannel* channel=0x0423bb10, const char * data=http://www.mamicode.com/0x04244008, unsigned int size=25, consttalk_base::PacketTime & packet_time={...}, int flags=0) 行463 C++

      peerconnection_client.exe!sigslot::_connection5<cricket::DtlsTransportChannelWrapper,cricket::TransportChannel*,char const *,unsigned int,talk_base::PacketTime const&,int,sigslot::single_threaded>::emit(cricket::TransportChannel *a1=0x0423bb10, const char * a2=0x04244008, unsigned int a3=25, consttalk_base::PacketTime & a4={...}, int a5=0) 行2047 + 0x2a 字节      C++

      peerconnection_client.exe!sigslot::signal5<cricket::TransportChannel*,char const *,unsigned int,talk_base::PacketTime const&,int,sigslot::single_threaded>::operator()(cricket::TransportChannel *a1=0x0423bb10, const char * a2=0x04244008, unsigned int a3=25, consttalk_base::PacketTime & a4={...}, int a5=0) 行2616 + 0x30 字节   C++

      peerconnection_client.exe!cricket::P2PTransportChannel::OnReadPacket(cricket::Connection* connection=0x05c2e260, const char * data=http://www.mamicode.com/0x04244008, unsigned int len=25,const talk_base::PacketTime & packet_time={...}) 行1260 C++

      peerconnection_client.exe!sigslot::_connection4<cricket::P2PTransportChannel,cricket::Connection*,char const *,unsigned int,talk_base::PacketTime const&,sigslot::single_threaded>::emit(cricket::Connection * a1=0x05c2e260,const char * a2=0x04244008, unsigned int a3=25, const talk_base::PacketTime& a4={...})  行1993 + 0x26 字节 C++

      peerconnection_client.exe!sigslot::signal4<cricket::Connection*,char const *,unsigned int,talk_base::PacketTime const&,sigslot::single_threaded>::operator()(cricket::Connection *a1=0x05c2e260, const char * a2=0x04244008, unsigned int a3=25, consttalk_base::PacketTime & a4={...})  行2544 + 0x2c 字节      C++

      peerconnection_client.exe!cricket::Connection::OnReadPacket(constchar * data=http://www.mamicode.com/0x04244008, unsigned int size=25, const talk_base::PacketTime &packet_time={...}) 行962 C++

      peerconnection_client.exe!cricket::UDPPort::OnReadPacket(talk_base::AsyncPacketSocket* socket=0x05c30528, const char * data=http://www.mamicode.com/0x04244008, unsigned int size=25, consttalk_base::SocketAddress & remote_addr={...}, const talk_base::PacketTime& packet_time={...}) 行268 C++

      peerconnection_client.exe!cricket::UDPPort::HandleIncomingPacket(talk_base::AsyncPacketSocket* socket=0x05c30528, const char * data=http://www.mamicode.com/0x04244008, unsigned int size=25, consttalk_base::SocketAddress & remote_addr={...}, const talk_base::PacketTime& packet_time={...}) 行104 C++

      peerconnection_client.exe!cricket::AllocationSequence::OnReadPacket(talk_base::AsyncPacketSocket* socket=0x05c30528, const char * data=http://www.mamicode.com/0x04244008, unsigned int size=25, consttalk_base::SocketAddress & remote_addr={...}, const talk_base::PacketTime& packet_time={...}) 行1021 + 0x30 字节 C++

      peerconnection_client.exe!sigslot::_connection5<cricket::AllocationSequence,talk_base::AsyncPacketSocket*,char const *,unsigned int,talk_base::SocketAddress const&,talk_base::PacketTime const&,sigslot::single_threaded>::emit(talk_base::AsyncPacketSocket *a1=0x05c30528, const char * a2=0x04244008, unsigned int a3=25, consttalk_base::SocketAddress & a4={...}, const talk_base::PacketTime &a5={...})  行2047 + 0x2a 字节      C++

      peerconnection_client.exe!sigslot::signal5<talk_base::AsyncPacketSocket*,char const *,unsigned int,talk_base::SocketAddress const&,talk_base::PacketTime const&,sigslot::single_threaded>::operator()(talk_base::AsyncPacketSocket *a1=0x05c30528, const char * a2=0x04244008, unsigned int a3=25, consttalk_base::SocketAddress & a4={...}, const talk_base::PacketTime &a5={...})  行2616 + 0x30 字节    C++

      peerconnection_client.exe!talk_base::AsyncUDPSocket::OnReadEvent(talk_base::AsyncSocket* socket=0x05c30044)  行133       C++

      peerconnection_client.exe!sigslot::_connection1<talk_base::AsyncUDPSocket,talk_base::AsyncSocket*,sigslot::multi_threaded_local>::emit(talk_base::AsyncSocket *a1=0x05c30044)  行1852 + 0x1a 字节      C++

      peerconnection_client.exe!sigslot::signal1<talk_base::AsyncSocket*,sigslot::multi_threaded_local>::operator()(talk_base::AsyncSocket *a1=0x05c30044)  行2346 + 0x20 字节    C++

      peerconnection_client.exe!talk_base::SocketDispatcher::OnEvent(unsignedint ff=1, int err=0)  行1150 C++

      peerconnection_client.exe!talk_base::PhysicalSocketServer::Wait(intcmsWait=32, bool process_io=true)  行1671 + 0x23 字节    C++

      peerconnection_client.exe!talk_base::MessageQueue::Get(talk_base::Message* pmsg=0x03a5ff38, int cmsWait=-1, bool process_io=true)  行271 + 0x21 字节      C++

      peerconnection_client.exe!talk_base::Thread::ProcessMessages(intcmsLoop=-1)  行508 + 0x19 字节       C++

      peerconnection_client.exe!talk_base::Thread::Run()  行371      C++

      peerconnection_client.exe!talk_base::Thread::PreRun(void* pv=0x00275080)  行358 + 0x13 字节     C++

      kernel32.dll!76033c45()     

      [下面的框架可能不正确和/或缺失,没有为 kernel32.dll 加载符号]    

      ntdll.dll!771a37f5()     

      ntdll.dll!771a37c8()     

 

5.3  视频接收播放过程:

       peerconnection_client.exe!webrtc::ModuleRTPUtility::RTPPayloadParser::ParseVP8(webrtc::ModuleRTPUtility::RTPPayload& parsedPacket={...})  行658 C++

      peerconnection_client.exe!webrtc::ModuleRTPUtility::RTPPayloadParser::Parse(webrtc::ModuleRTPUtility::RTPPayload& parsedPacket={...})  行581 + 0xc 字节    C++

     peerconnection_client.exe!webrtc::RTPReceiverVideo::ReceiveVp8Codec(webrtc::WebRtcRTPHeader* rtp_header=0x039ee0b8, const unsigned char * payload_data=http://www.mamicode.com/0x03f7f206,unsigned short payload_data_length=129) 行181 + 0xc 字节 C++

      peerconnection_client.exe!webrtc::RTPReceiverVideo::ParseVideoCodecSpecific(webrtc::WebRtcRTPHeader* rtp_header=0x039ee0b8, const unsigned char * payload_data=http://www.mamicode.com/0x03f7f206,unsigned short payload_data_length=129, webrtc::RtpVideoCodecTypesvideo_type=kRtpVideoVp8, __int64 now_ms=2915007, boolis_first_packet=true) 行126 + 0x15 字节 C++

      peerconnection_client.exe!webrtc::RTPReceiverVideo::ParseRtpPacket(webrtc::WebRtcRTPHeader* rtp_header=0x039ee0b8, const webrtc::PayloadUnion & specific_payload={...},bool is_red=false, const unsigned char * payload=0x03f7f206, unsigned shortpayload_length=129, __int64 timestamp_ms=2915007, boolis_first_packet=true)  行75 + 0x28 字节 C++

      peerconnection_client.exe!webrtc::RtpReceiverImpl::IncomingRtpPacket(constwebrtc::RTPHeader & rtp_header={...}, const unsigned char *payload=0x03f7f206, int payload_length=129, webrtc::PayloadUnionpayload_specific={...}, bool in_order=false) 行244 + 0x65 字节      C++

      peerconnection_client.exe!webrtc::ViEReceiver::ReceivePacket(constunsigned char * packet=0x03f7f1ee, int packet_length=153, constwebrtc::RTPHeader & header={...}, bool in_order=false)  行243 + 0x44 字节   C++

      peerconnection_client.exe!webrtc::ViEReceiver::OnRecoveredPacket(constunsigned char * rtp_packet=0x03f7f1ee, int rtp_packet_length=153)  行184  C++

      peerconnection_client.exe!webrtc::FecReceiverImpl::ProcessReceivedFec()  行220 + 0x24 字节 C++

      peerconnection_client.exe!webrtc::ViEReceiver::ParseAndHandleEncapsulatingHeader(constunsigned char * packet=0x03f81768, int packet_length=154, constwebrtc::RTPHeader & header={...})  行259 + 0x1d 字节     C++

      peerconnection_client.exe!webrtc::ViEReceiver::ReceivePacket(constunsigned char * packet=0x03f81768, int packet_length=154, constwebrtc::RTPHeader & header={...}, bool in_order=true)  行232 + 0x14 字节    C++

      peerconnection_client.exe!webrtc::ViEReceiver::InsertRTPPacket(constunsigned char * rtp_packet=0x03f81768, int rtp_packet_length=154, constwebrtc::PacketTime & packet_time={...}) 行224 + 0x1c 字节    C++

      peerconnection_client.exe!webrtc::ViEReceiver::ReceivedRTPPacket(constvoid * rtp_packet=0x03f81768, int rtp_packet_length=154, constwebrtc::PacketTime & packet_time={...}) 行156   C++

      peerconnection_client.exe!webrtc::ViEChannel::ReceivedRTPPacket(constvoid * rtp_packet=0x03f81768, const int rtp_packet_length=154, constwebrtc::PacketTime & packet_time={...}) 行1670 C++

      peerconnection_client.exe!webrtc::ViENetworkImpl::ReceivedRTPPacket(constint video_channel=0, const void * data=http://www.mamicode.com/0x03f81768, const int length=154, constwebrtc::PacketTime & packet_time={...}) 行160 + 0x14 字节 C++

      peerconnection_client.exe!cricket::WebRtcVideoMediaChannel::OnPacketReceived(talk_base::Buffer* packet=0x039ee7a4, const talk_base::PacketTime & packet_time={...})  行2570 + 0x5f 字节       C++

      peerconnection_client.exe!cricket::BaseChannel::HandlePacket(boolrtcp=false, talk_base::Buffer * packet=0x039ee7a4, const talk_base::PacketTime& packet_time={...})  行643 + 0x23 字节   C++

      peerconnection_client.exe!cricket::BaseChannel::OnChannelRead(cricket::TransportChannel* channel=0x041d0500, const char * data=http://www.mamicode.com/0x065e2008, unsigned int len=164, consttalk_base::PacketTime & packet_time={...}, int flags=0) 行392 C++

      peerconnection_client.exe!sigslot::_connection5<cricket::BaseChannel,cricket::TransportChannel*,char const *,unsigned int,talk_base::PacketTime const&,int,sigslot::single_threaded>::emit(cricket::TransportChannel *a1=0x041d0500, const char * a2=0x065e2008, unsigned int a3=164, consttalk_base::PacketTime & a4={...}, int a5=0) 行2047 + 0x2a 字节 C++

      peerconnection_client.exe!sigslot::signal5<cricket::TransportChannel*,char const *,unsigned int,talk_base::PacketTime const&,int,sigslot::single_threaded>::operator()(cricket::TransportChannel *a1=0x041d0500, const char * a2=0x065e2008, unsigned int a3=164, consttalk_base::PacketTime & a4={...}, int a5=0) 行2616 + 0x30 字节 C++

      peerconnection_client.exe!cricket::TransportChannelProxy::OnReadPacket(cricket::TransportChannel* channel=0x041d2108, const char * data=http://www.mamicode.com/0x065e2008, unsigned int size=164,const talk_base::PacketTime & packet_time={...}, int flags=0) 行243 C++

      peerconnection_client.exe!sigslot::_connection5<cricket::TransportChannelProxy,cricket::TransportChannel*,char const *,unsigned int,talk_base::PacketTime const &,int,sigslot::single_threaded>::emit(cricket::TransportChannel* a1=0x041d2108, const char * a2=0x065e2008, unsigned int a3=164, consttalk_base::PacketTime & a4={...}, int a5=0) 行2047 + 0x2a 字节     C++

      peerconnection_client.exe!sigslot::signal5<cricket::TransportChannel*,char const *,unsigned int,talk_base::PacketTime const&,int,sigslot::single_threaded>::operator()(cricket::TransportChannel *a1=0x041d2108, const char * a2=0x065e2008, unsigned int a3=164, consttalk_base::PacketTime & a4={...}, int a5=0) 行2616 + 0x30 字节 C++

      peerconnection_client.exe!cricket::DtlsTransportChannelWrapper::OnReadPacket(cricket::TransportChannel* channel=0x041d22b0, const char * data=http://www.mamicode.com/0x065e2008, unsigned int size=164,const talk_base::PacketTime & packet_time={...}, int flags=0) 行463 C++

      peerconnection_client.exe!sigslot::_connection5<cricket::DtlsTransportChannelWrapper,cricket::TransportChannel*,char const *,unsigned int,talk_base::PacketTime const&,int,sigslot::single_threaded>::emit(cricket::TransportChannel *a1=0x041d22b0, const char * a2=0x065e2008, unsigned int a3=164, consttalk_base::PacketTime & a4={...}, int a5=0) 行2047 + 0x2a 字节     C++

      peerconnection_client.exe!sigslot::signal5<cricket::TransportChannel*,char const *,unsigned int,talk_base::PacketTime const &,int,sigslot::single_threaded>::operator()(cricket::TransportChannel* a1=0x041d22b0, const char * a2=0x065e2008, unsigned int a3=164, consttalk_base::PacketTime & a4={...}, int a5=0) 行2616 + 0x30 字节 C++

      peerconnection_client.exe!cricket::P2PTransportChannel::OnReadPacket(cricket::Connection* connection=0x06675900, const char * data=http://www.mamicode.com/0x065e2008, unsigned int len=164,const talk_base::PacketTime & packet_time={...}) 行1260 C++

      peerconnection_client.exe!sigslot::_connection4<cricket::P2PTransportChannel,cricket::Connection*,char const *,unsigned int,talk_base::PacketTime const&,sigslot::single_threaded>::emit(cricket::Connection * a1=0x06675900,const char * a2=0x065e2008, unsigned int a3=164, const talk_base::PacketTime& a4={...})  行1993 + 0x26 字节       C++

      peerconnection_client.exe!sigslot::signal4<cricket::Connection*,char const *,unsigned int,talk_base::PacketTime const&,sigslot::single_threaded>::operator()(cricket::Connection *a1=0x06675900, const char * a2=0x065e2008, unsigned int a3=164, consttalk_base::PacketTime & a4={...})  行2544 + 0x2c 字节      C++

      peerconnection_client.exe!cricket::Connection::OnReadPacket(constchar * data=http://www.mamicode.com/0x065e2008, unsigned int size=164, const talk_base::PacketTime& packet_time={...}) 行962 C++

      peerconnection_client.exe!cricket::UDPPort::OnReadPacket(talk_base::AsyncPacketSocket* socket=0x041e4dd8, const char * data=http://www.mamicode.com/0x065e2008, unsigned int size=164, consttalk_base::SocketAddress & remote_addr={...}, const talk_base::PacketTime& packet_time={...}) 行268 C++

      peerconnection_client.exe!cricket::UDPPort::HandleIncomingPacket(talk_base::AsyncPacketSocket* socket=0x041e4dd8, const char * data=http://www.mamicode.com/0x065e2008, unsigned int size=164, consttalk_base::SocketAddress & remote_addr={...}, const talk_base::PacketTime &packet_time={...}) 行104 C++

      peerconnection_client.exe!cricket::AllocationSequence::OnReadPacket(talk_base::AsyncPacketSocket* socket=0x041e4dd8, const char * data=http://www.mamicode.com/0x065e2008, unsigned int size=164, consttalk_base::SocketAddress & remote_addr={...}, const talk_base::PacketTime& packet_time={...}) 行1021 + 0x30 字节 C++

      peerconnection_client.exe!sigslot::_connection5<cricket::AllocationSequence,talk_base::AsyncPacketSocket*,char const *,unsigned int,talk_base::SocketAddress const&,talk_base::PacketTime const&,sigslot::single_threaded>::emit(talk_base::AsyncPacketSocket *a1=0x041e4dd8, const char * a2=0x065e2008, unsigned int a3=164, consttalk_base::SocketAddress & a4={...}, const talk_base::PacketTime &a5={...})  行2047 + 0x2a 字节 C++

      peerconnection_client.exe!sigslot::signal5<talk_base::AsyncPacketSocket*,char const *,unsigned int,talk_base::SocketAddress const&,talk_base::PacketTime const&,sigslot::single_threaded>::operator()(talk_base::AsyncPacketSocket *a1=0x041e4dd8, const char * a2=0x065e2008, unsigned int a3=164, consttalk_base::SocketAddress & a4={...}, const talk_base::PacketTime &a5={...})  行2616 + 0x30 字节    C++

      peerconnection_client.exe!talk_base::AsyncUDPSocket::OnReadEvent(talk_base::AsyncSocket* socket=0x041e46bc)  行133       C++

      peerconnection_client.exe!sigslot::_connection1<talk_base::AsyncUDPSocket,talk_base::AsyncSocket*,sigslot::multi_threaded_local>::emit(talk_base::AsyncSocket *a1=0x041e46bc)  行1852 + 0x1a 字节      C++

      peerconnection_client.exe!sigslot::signal1<talk_base::AsyncSocket*,sigslot::multi_threaded_local>::operator()(talk_base::AsyncSocket *a1=0x041e46bc)  行2346 + 0x20 字节     C++

      peerconnection_client.exe!talk_base::SocketDispatcher::OnEvent(unsignedint ff=1, int err=0)  行1150 C++

      peerconnection_client.exe!talk_base::PhysicalSocketServer::Wait(intcmsWait=477, bool process_io=true)  行1671 + 0x23 字节    C++

      peerconnection_client.exe!talk_base::MessageQueue::Get(talk_base::Message* pmsg=0x039eff38, int cmsWait=-1, bool process_io=true)  行271 + 0x21 字节      C++

      peerconnection_client.exe!talk_base::Thread::ProcessMessages(intcmsLoop=-1)  行508 + 0x19 字节       C++

      peerconnection_client.exe!talk_base::Thread::Run()  行371      C++

      peerconnection_client.exe!talk_base::Thread::PreRun(void* pv=0x00262510)  行358 + 0x13 字节     C++

      kernel32.dll!76993c45()     

      [下面的框架可能不正确和/或缺失,没有为 kernel32.dll 加载符号]    

      ntdll.dll!77d437f5()     

      ntdll.dll!77d437c8()    

 

 

 

6  协议:

6.1  XMPP协议:

6.1.1原理介绍

XMPP(可扩展消息处理现场协议)是基于可扩展标记语言(XML)的协议,它用于即时消息(IM)以及在线现场探测。它在促进服务器之间的准即时操作。这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同。

  XMPP的前身是Jabber,一个开源形式组织产生的网络即时通信协议。XMPP目前被IETF国际标准组织完成了标准化工作。标准化的核心结果分为两部分;

   在IETF 中,把IM协议划分为四种协议,即即时信息和出席协议(Instant Messaging and Presence Protocol, IMPP)、出席和即时信息协议(Presence and Instant Messaging Protocol, PRIM)、针对即时信息和出席扩展的会话发起协议(Session Initiation Protocol for Instant Messaging and PresenceLeveraging Extensions, SIMPLE),以及可扩展的消息出席协议(XMPP)。最初研发IMPP 也是为了创建一种标准化的协议,但是今天,IMPP 已经发展成为基本协议单元,定义所有即时通信协议应该支持的核心功能集。

XMPP 和SIMPLE 两种协议是架构,有助于实现IMPP协议所描述的规范。PRIM 最初是基于即时通信的协议,与XMPP 和SIMPLE 类似,但是己经不再使用

1. XMPP 协议是公开的,由JSF开源社区组织开发的。XMPP 协议并不属于任何的机构和个人,而是属于整个社区,这一点从根本上保证了其开放性。

2. XMPP 协议具有良好的扩展性。在XMPP 中,即时消息和到场信息都是基于XML 的结构化信息,这些信息以XML 节(XML Stanza)的形式在通信实体间交换。XMPP 发挥了XML 结构化数据的通用传输层的作用,它将出席和上下文敏感信息嵌入到XML 结构化数据中,从而使数据以极高的效率传送给最合适的资源。基于XML 建立起来的应用具有良好的语义完整性和扩展性。

3. 分布式的网络架构。XMPP 协议都是基于Client/Server 架构,但是XMPP协议本身并没有这样的限制。网络的架构和电子邮件十分相似,但没有结合任何特定的网络架构,适用范围非常广泛。

4.XMPP 具有很好的弹性。XMPP 除了可用在即时通信的应用程序,还能用在网络管理、内容供稿、协同工具、档案共享、游戏、远端系统监控等。

5.安全性。XMPP在Client-to-Server通信,和Server-to-Server通信中都使用TLS (Transport LayerSecurity)协议作为通信通道的加密方法,保证通信的安全。任何XMPP服务器可以独立于公众XMPP网络(例如在企业内部网络中),而使用 SASL及TLS等技术更加增强了通信的安全性。如下图所示:

 

 

6.1.2    XMPP协议网络架构

XMPP 是一个典型的C/S架构,而不是像大多数即时通讯软件一样,使用P2P客户端到客户端的架构,也就是说在大多数情况下,当两个客户端进行通讯时,他们的消息都是通过服务器传递的(也有例外,例如在两个客户端传输文件时).采用这种架构,主要是为了简化客户端,将大多数工作放在服务器端进行,这样,客户端的 工作就比较简单,而且,当增加功能时,多数是在服务器端进行.XMPP服务的框架结构如下图所示.XMPP中定义了三个角色,XMPP客户端,XMPP服 务器、网关.通信能够在这三者的任意两个之间双向发生.服务器同时承担了客户端信息记录、连接管理和信息的路由功能.网关承担着与异构即时通信系统的互联 互通,异构系统可以包括SMS(短信)、MSN、ICQ等.基本的网络形式是单客户端通过TCP/IP连接到单服务器,然后在之上传输XML,工作原理 是:

1)   节点连接到服务器;

2)   服务器利用本地目录系统中的证书对其认证;

3)   节点指定目标地址,让服务器告知目标状态;

4)   服务器查找、连接并进行相互认证;

5)   节点之间进行交互.

 

·        XMPP客户端

XMPP 系统的一个设计标准是必须支持简单的客户端。事实上,XMPP 系统架构对客户端只有很少的几个限制。一个XMPP 客户端必须支持的功能有:

1)   通过 TCP 套接字与XMPP服务器进行通信;

2)   解析组织好的 XML 信息包;

3)   理解消息数据类型。

XMPP 将复杂性从客户端转移到服务器端。这使得客户端编写变得非常容易,更新系统功能也同样变得容易。XMPP 客户端与服务端通过XML 在TCP 套接字的5222 端口进行通信,而不需要客户端之间直接进行通信。

基本的XMPP 客户端必须实现以下标准协议(XEP-0211):

1)   RFC3920 核心协议Core

2)   RFC3921 即时消息和出席协议Instant Messaging andPresence

3)   XEP-0030 服务发现Service Discovery

4)   XEP-0115 实体能力Entity Capabilities

 

·        XMPP服务器

XMPP 服务器遵循两个主要法则:

5)   监听客户端连接,并直接与客户端应用程序通信;

6)   与其他 XMPP 服务器通信;

XMPP开源服务器一般被设计成模块化,由各个不同的代码包构成,这些代码包分别处理Session管理、用户和服务器之间的通信、服务器之间的通信、DNS(Domain Name System)转换、存储用户的个人信息和朋友名单、保留用户在下线时收到的信息、用户注册、用户的身份和权限认证、根据用户的要求过滤信息和系统记录等。另外,服务器可以通过附加服务来进行扩展,如完整的安全策略,允许服务器组件的连接或客户端选择,通向其他消息系统的网关。

基本的XMPP 服务器必须实现以下标准协议

1)   RFC3920 核心协议Core

2)   RFC3921 即时消息和出席协议Instant Messaging andPresence

3)   XEP-0030 服务发现Service Discovery

 

·          XMPP网关 

XMPP 突出的特点是可以和其他即时通信系统交换信息和用户在线状况。由于协议不同,XMPP 和其他系统交换信息必须通过协议的转换来实现,目前几种主流即时通信协议都没有公开,所以XMPP 服务器本身并没有实现和其他协议的转换,但它的架构允许转换的实现。实现这个特殊功能的服务端在XMPP 架构里叫做网关(gateway)。目前,XMPP 实现了和AIM、ICQ、IRC、MSN Massager、RSS0.9和Yahoo Massager 的协议转换。由于网关的存在,XMPP架构事实上兼容所有其他即时通信网络,这无疑大大提高了XMPP 的灵活性和可扩展性。

 

6.1.3    XMPP协议的组成

主要的XMPP 协议范本及当今应用很广的XMPP 扩展:

RFC 3920 :XMPP核心。全称:The Extensible Messaging andPresence Protocol,即可 扩展通讯和表示协议。说白了,就是规定基于XML流传输指定节点数据的协议。这么做的好处就是统一(注:大家都按照这个定义,做的东西就可以相互通讯、交流,这个应该很有发展前景!)。它是一个开放并且可扩展的协议,包括Jingle协议都是XMPP协议的扩展。(注:使用Wireshark抓包时,早期的版本可能找不到这个协议,这时候可以选择Jabber,它是XMPP协议的前身)。现在很多的IM都是基于XMPP协议开发的,包括gtalk等。定义了XMPP 协议框架下应用的网络架构,引入了XML Stream(XML 流)与XML Stanza(XML 节),并规定XMPP 协议在通信过程中使用的XML 标签。使用XML 标签从根本上说是协议开放性与扩展性的需要。此外,在通信的安全方面,把TLS 安全传输机制与SASL 认证机制引入到内核,与XMPP 进行无缝的连接,为协议的安全性、可靠性奠定了基础。核心 文档还规定了错误的定义及处理、XML 的使用规范、JID(Jabber Identifier,Jabber 标识符)的定义、命名规范等等。所以这是所有基于XMPP 协议的应用都必需支持的文档。

RFC 3921:用户成功登陆到服务器之后,发布更新自己的在线好友管理、发送即时聊天消息等业务。所有的这些业务都是通过三种基本的XML 节来完成的:IQ Stanza(IQ 节), Presence Stanza(Presence 节), Message Stanza(Message 节)。RFC3921 还对阻塞策略进行了定义,定义是多种阻塞方式。可以说,RFC3921 是RFC3920 的充分补充。两个文档结合起来,就形成了一个基本的即时通信协议平台,在这个平台上可以开发出各种各样的应用。

XEP-0030 服务搜索。一个强大的用来测定XMPP 网络中的其它实体所支持特性的协议。

XEP-0115 实体性能。XEP-0030 的一个通过即时出席的定制,可以实时改变交变广告功能。

XEP-0045 多人聊天。一组定义参与和管理多用户聊天室的协议,类似于Internet 的Relay Chat,具有很高的安全性。

XEP-0096 文件传输。定义了从一个XMPP 实体到另一个的文件传输。

XEP-0124 HTTP 绑定。将XMPP 绑定到HTTP 而不是TCP,主要用于不能够持久的维持与服务器TCP 连接的设备。

XEP-0166 Jingle。规定了多媒体通信协商的整体架构。Jingle协议是XMPP协议上的扩展协议,它着手解决在XMPP协议框架下的点对点的连接问题,也即P2P连接。在Jingle框架下,即使用户在防火墙或是NAT网络保护之下,也能够建立连接,从而提供文件传送、视频、音频服务等。

XEP-0167 Jingle Audio Content DescriptionFormat。定义了从一个XMPP 实体到另一个的语音传输过程。

TURN协议:全称:Traversal Using Relays around NAT,顾名思义,就是通过中继服务器来传输数据的协议。

STUN协议:全称:Simple Traversal of UDP over NATs,即NAT 的UDP简单穿越,它允许位于NAT(或多重NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的Internet端端口。知道NAT类型并且有了公网IP和port,P2P就方便多了。

XEP-0176 Jingle ICE(Interactive Connectivity Establishment)Transport。即 交互式连接建立,说白了,它就是利用STUN和TURN等协议找到最适合的连接。

XEP-0177 Jingle Raw UDP Transport。纯UDP 传输机制,文件讲述了如何在没有防火墙且在同一网络下建立连接的。

XEP-0180 Jingle Video Content DescriptionFormat。定义了从一个XMPP 实体到另一个的视频传输过程。

XEP-0181 Jingle DTMF(Dual Tone Multi-Frequency)。

XEP-0183 Jingle Telepathy Transport Method。

 

6.1.4 Xmpp介绍

 

6.1.5 协议内容

中文版本:

 

 

 

 

英文版本:

 

6.2  Stun协议:

STUN(Session Traversal Utilities for NAT,NAT会话传输应用程序)是一种网络协议,它允许位于NAT(或多重NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的Internet端端口。这些信息被用来在两个同时处于NAT 路由器之后的主机之间建立UDP通信。该协议由RFC 5389定义。

一旦客户端得知了Internet端的UDP端口,通信就可以开始了。如果NAT是完全圆锥型的,那么双方中的任何一方都可以发起通信。如果NAT是受限圆锥型或端口受限圆锥型,双方必须一起开始传输。

需要注意的是,要使用STUN RFC中描述的技术并不一定需要使用STUN协议——还可以另外设计一个协议并把相同的功能集成到运行该协议的服务器上。

SIP之类的协议是使用UDP分组在Internet上传输音频和/或视频数据的。不幸的是,由于通信的两个末端往往位于NAT之后,因此用传统的方法是无法建立连接的。这也就是STUN发挥作用的地方。

STUN是一个客户机-服务器协议。一个VoIP电话或软件包可能会包括一个STUN客户端。这个客户端会向STUN服务器发送请求,之后,服务器就会向STUN客户端报告NAT路由器的公网IP地址以及NAT为允许传入流量传回内网而开通的端口。

以上的响应同时还使得STUN客户端能够确定正在使用的NAT类型——因为不同的NAT类型处理传入的UDP分组的方式是不同的。四种主要类型中有三种是可以使用的:完全圆锥型NAT、受限圆锥型NAT和端口受限圆锥型NAT——但大型公司网络中经常采用的对称型NAT(又称为双向NAT)则不能使用。

 

6.2.1P2P实现的原理

首先先介绍一些基本概念:

  NAT(NetworkAddress Translators),网络地址转换:网络地址转换是在IP地址日益缺乏的情况下产生的,它的主要目的就是为了能够地址重用。NAT从历史发展上分为 两大类,基本的NAT和NAPT(Network Address/Port Translator)。

  最先提出的是基本的 NAT(注:刚开始其实只是路由器上的一个功能模块),它的产生基于如下事实:一个私有网络(域)中的节点中只有很少的节点需要与外网连 接(这是在上世纪90年代中期提出的)。那么这个子网中其实只有少数的节点需要全球唯一的IP地址,其他的节点的IP地址应该是可以重用的。因此,基本的NAT实现的功能很简单,在子网内使用一个保留的IP子网段,这些IP对外是不可见的。子网内只有少数一些IP地址可以对应到真正全球唯一的 IP地址。如果这些节点需要访问外部网络,那么基本NAT就负责将这个节点的子网内IP转化为一个全球唯一的IP然后发送出去。(基本的NAT会改变IP 包中的原IP地址,但是不会改变IP包中的端口)

关于基本的NAT可以参看RFC 1631

另外一种NAT叫做NAPT,从名称上我们也可以看得出,NAPT不但会改变经过这个NAT设备的IP数据报的IP地址,还会改变IP数据报的TCP/UDP端口。基本NAT的设备可能我们见的不多(基本已经淘汰了),NAPT才是我们真正需要关注的。看下图:

有一个私有网络10.*.*.*,Client A是其中的一台计算机,这个网络的网关(一个NAT设备)的外网IP是155.99.25.11(应该还有一个内网的IP地址,比如 10.0.0.10)。如果Client A中的某个进程(这个进程创建了一个UDP Socket,这个Socket绑定1234端口)想访问外网主机18.181.0.31的1235端口,那么当数据包通过NAT时会发生什么事情呢?

首先NAT会改变这个数据包的原IP地址,改为155.99.25.11。接着NAT会为这个传输创建一个Session(Session是一个抽象的概 念,如果是TCP,也许Session是由一个SYN包开始,以一个FIN包结束。而UDP呢,以这个IP的这个端口的第一个UDP开始,结束呢,呵呵, 也许是几分钟,也许是几小时,这要看具体的实现了)并且给这个Session分配一个端口,比如62000,然后改变这个数据包的源端口为62000。所以本来是(10.0.0.1:1234->18.181.0.31:1235)的数据包到了互联网上变为了(155.99.25.11:62000->18.181.0.31:1235)。

一旦NAT创建了一个Session后,NAT会记住62000端口对应的是10.0.0.1的1234端口,以后从18.181.0.31发送到 62000端口的数据会被NAT自动的转发到10.0.0.1上。(注意:这里是说18.181.0.31发送到62000端口的数据会被转发,其他的 IP发送到这个端口的数据将被NAT抛弃)这样Client A就与ServerS1建立以了一个连接。

上面的是一些基础知识,下面的才是关键的部分了。

看看下面的情况:

接上面的例子,如果Client A的原来那个Socket(绑定了1234端口的那个UDPSocket)又接着向另外一个Server S2发送了一个UDP包,那么这个UDP包在通过NAT时会怎么样呢?

这时可能会有两种情况发生,一种是NAT再次创建一个Session,并且再次为这个Session分配一个端口号(比如:62001)。另外一种是NAT 再次创建一个Session,但是不会新分配一个端口号,而是用原来分配的端口号62000。前一种NAT叫做Symmetric NAT,后一种叫做Cone NAT。如果你的NAT刚好是第一种,那么很可能会有很多P2P软件失灵。(可以庆幸的是,现在绝大多数的NAT属于后者,即Cone NAT)

注:Cone NAT具体又分为3种:

(1)全克隆( Full Cone) : NAT把所有来自相同内部IP地址和端口的请求映射到相同的外部IP地址和端口。任何一个外部主机均可通过该映射发送IP包到该内部主机。

(2)限制性克隆(Restricted Cone) : NAT把所有来自相同内部IP地址和端口的请求映射到相同的外部IP地址和端口。但是,只有当内部主机先给IP地址为X的外部主机发送IP包,该外部主机才能向该内部主机发送IP包。

(3)端口限制性克隆( Port Restricted Cone) :端口限制性克隆与限制性克隆类似,只是多了端口号的限制,即只有内部主机先向IP地址为X,端口号为P的外部主机发送1个IP包,该外部主机才能够把源端口号为P的IP包发送给该内部主机。

好了,我们看到,通过NAT,子网内的计算机向外连结是很容易的(NAT相当于透明的,子网内的和外网的计算机不用知道NAT的情况)。但是如果外部的计算机想访问子网内的计算机就比较困难了(而这正是P2P所需要的)。那么我们如果想从外部发送一个数据报给内网的计算机有什么办法呢?

首先,我们必须在内网的NAT上打上一个“洞”(也就是前面我们说的在NAT上建立一个 Session),这个洞不能由外部来打,只能由内网内的主机来打。而且这个洞是有方向的,比如从内部某台主机(比如:192.168.0.10)向外部的某个IP(比如:219.237.60.1)发送一个UDP包,那么就在这个内网的NAT设备上打了一个方向为219.237.60.1的“洞”,(这就是称为UDP HolePunching的技术)以后219.237.60.1就可以通过这个洞与内网的192.168.0.10联系了。(但是其他的IP不能利用这个洞)。

NAT的四种类型

Full cone NAT,亦即著名的一對一(one-to-one)
NAT

·         一旦一个内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送.任意外部主机都能通过给eAddr:port2发包到达iAddr:port1

 

Address-Restricted cone NAT

·         一旦一个内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送.任意外部主机(hostAddr:any)都能通过给eAddr:port2发包到达iAddr:port1的前提是:iAddr:port1之前发送过包到hostAddr:any.
"any"也就是说端口不受限制

 

Port-Restricted cone NAT

类似受限制錐形NAT(Restricted cone NAT),但是还有端口限制。

·         一旦一个内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送.一个外部主机(hostAddr:port3)能够发包到达iAddr:port1的前提是:iAddr:port1之前发送过包到hostAddr:port3.

 

Symmetric NAT(對稱NAT)

·         每一個來自相同內部IP與port的請求到一個特定目的地的IP地址和端口,映射到一個獨特的外部來源的IP地址和端口。
同一個內部主機發出一個信息包到不同的目的端,不同的映射使用

·         只有曾经收到过内部主机封包的外部主机,才能够把封包发回来

 

 

6.2.2P2P的常用实现

一、普通的直连式P2P实现

通过上面的理论,实现两个内网的主机通讯就差最后一步了:那就是鸡生蛋还是蛋生鸡的问题了,两边都无法主动发出连接请求,谁也不知道谁的公网地址,那我们如何来打这个洞呢?我们需要一个中间人来联系这两个内网主机。

现在我们来看看一个P2P软件的流程,以下图为例:

首先,Client A登录服务器,NAT A为这次的Session分配了一个端口60000,那么Server S收到的ClientA的地址是202.187.45.3:60000,这就是ClientA的外网地址了。      同样,Client B登录Server S,NATB给此次Session分配的端口是40000,那么Server S收到的B的地址是187.34.1.56:40000。

此时,Client A与ClientB都可以与Server S通信了。如果Client A此时想直接发送信息给Client B,那么他可以从Server S那儿获得B的公网地址187.34.1.56:40000,是不是Client A向这个地址发送信息Client B就能收到了呢?答案是不行,因为如果这样发送信息,NAT B会将这个信息丢弃(因为这样的信息是不请自来的,为了安全,大多数NAT都会执行丢弃动作)。现在我们需要的是在NAT B上打一个方向为202.187.45.3(即Client A的外网地址)的洞,那么Client A发送到187.34.1.56:40000的信息,Client B就能收到了。这个打洞命令由谁来发呢?自然是Server S。

总结一下这个过程:如果Client A想向Client B发送信息,那么Client A发送命令给Server S,请求Server S命令Client B向Client A方向打洞。然后Client A就可以通过Client B的外网地址与Client B通信了。

注意:以上过程只适合于Cone NAT的情况,如果是Symmetric NAT,那么当Client B向Client A打洞的端口已经重新分配了,Client B将无法知道这个端口(如果Symmetric NAT的端口是顺序分配的,那么我们或许可以猜测这个端口号,可是由于可能导致失败的因素太多,这种情况下一般放弃P2P)。

二、STUN方式的P2P实现

STUN是RFC3489规定的一种NAT穿透方式,它采用辅助的方法探测NAT的IP和端口。毫无疑问的,它对穿越早期的NAT起了巨大的作用,并且还将继续在NAT穿透中占有一席之地。

STUN 的探测过程需要有一个公网IP的STUN server,在NAT后面的UAC必须和此server配合,互相之间发送若干个UDP数据包。UDP包中包含有UAC需要了解的信息,比如NAT外网 IP,PORT等等。UAC通过是否得到这个UDP包和包中的数据判断自己的NAT类型。

假设有如下UAC(B),NAT(A),SERVER(C),UAC的IP为IPB,NAT的IP为 IPA ,SERVER的 IP为IPC1 、IPC2。请注意,服务器C有两个IP,后面你会理解为什么需要两个IP。

(1)NAT的探测过程

STEP1:B 向C的IPC1的port1端口发送一个UDP包。C收到这个包后,会把它收到包的源IP和port写到UDP包中,然后把此包通过IP1C和port1 发还给B。这个IP和port也就是NAT的外网IP和port,也就是说你在STEP1中就得到了NAT的外网IP。

熟悉NAT工作原理的应该都知道,C返回给B的这个UDP包B一定收到。如果在你的应用中,向一个STUN服务器发送数据包后,你没有收到STUN的任何回应包,那只有两种可能:1、STUN服务器不存在,或者你弄错了port。2、你的NAT设备拒绝一切UDP包从外部向内部通过,如果排除防火墙限制规则,那么这样的NAT设备如果存在,那肯定是坏了„„

当B收到此UDP后,把此UDP中的IP和自己的IP做比较,如果是一样的,就说明自己是在公网,下步NAT将去探测防火墙类型,就不多说了(下面有图)。如果不一样,说明有NAT的存在,系统进行STEP2的操作。

STEP2:B向C的IPC1发送一个UDP包,请求C通过另外一个IPC2和PORT(不同与SETP1的IP1)向B返回一个UDP数据包(现在知道为什么C要有两个IP了吧,为了检测cone NAT的类型)。

我们来分析一下,如果B收到了这个数据包,那说明什么?说明NAT来着不拒,不对数据包进行任何过滤,这也就是STUN标准中的full cone NAT。遗憾的是,full cone nat太少了,这也意味着你能收到这个数据包的可能性不大。如果没收到,那么系统进行STEP3的操作。

STEP3:B向C的IPC2的port2发送一个数据包,C收到数据包后,把它收到包的源IP和port写到UDP包中,然后通过自己的IPC2和port2把此包发还给B。和step1一样,B肯定能收到这个回应UDP包。此包中的port是我们最关心的数据,下面我们来分析:

如果这个port和step1中的port一样,那么可以肯定这个NAT是个CONENAT,否则是对称NAT。道理很简单:根据对称NAT的规则,当目的地址的IP和port有任何一个改变,那么NAT都会重新分配一个port使用,而在step3中,和step1对应,我们改变了IP和port。因此,如果是对称NAT,那这两个port肯定是不同的。

如果在你的应用中,到此步的时候PORT是不同的,那就只能放弃P2P了,原因同上面实现中的一样。如果不同,那么只剩下了restrict cone 和port restrict cone。系统用   step4探测是是那一种。

STEP4:B向C的IP2的一个端口PD发送一个数据请求包,要求C用IP2和不同于PD的port返回一个数据包给B。

我们来分析结果:如果B收到了,那也就意味着只要IP相同,即使port不同,NAT也允许UDP包通过。显然这是restrict cone NAT。如果没收到,没别的好说,port restrict NAT.

 

STUN 使用下列的算法(取自RFC3489)来发现 NAT gateways 以及防火墙(firewalls):

 

一旦路径通过红色箱子的终点时,UDP的连通是沒有可能性的。一旦通过黄色或是绿色的箱子,就有连接的可能。

6.2.3Stun uri

 

   stunURI      = scheme ":" host [ ":" port ]

   scheme       = "stun" / "stuns"

 

 

6.2.4内容

 

6.2.5    中文内容

 

RFC 3489:

 

 

RFC 5389:

 

 

6.2.6    开源服务器

STUNTMAN:http://www.stunprotocol.org/

https://github.com/jselbie/stunserver

rfc5766-turn-server

 

http://sourceforge.net/projects/stun/

 

Stuntman- STUN server and client

High performance, production qualitySTUN server and client library

 

Vovida.orgSTUN server

  • mystun: STUN server and client library from the iptel.org guys. Old but mature. License: GPL, Homepage:http://developer.berlios.de/projects/mystun/. You have to download the file via CVS.
  • Vovida STUN server (stund): STUN server and client library/application for Linux and Windows from the Vovida guys. Old but mature. License: Vovida Software License 1.0, Homepage:http://sourceforge.net/projects/stun/.
  • WinSTUN: A Windows STUN client, part of the Vovida STUN server (see above). A nice application to test your NAT box. Homepage: http://sourceforge.net/projects/stun/files/WinStun/.
  • reTurn: STUN/TURN server and client library, part of the resiprocate project. Server application is provided as well, but it seems incomplete (authentication). License: 3-clause BSD license. Homepage:http://www.resiprocate.org/ReTurn_Overview.
  • restund: STUN/TURN server, supports authentication against a mysql DB. License: 3-clause BSD license. Homepage: http://www.creytiv.com/restund.html.
  • TurnServer: STUN/TURN server. License: GPL3. Homepage: http://turnserver.sourceforge.net/.
  • PJNATH : Open Source ICE, STUN, and TURN Library,http://www.pjsip.org/pjnath/docs/html/
  • Numd:a free STUN/TURN serve,  http://numb.viagenie.ca/

 

6.2.7    公开的免费STUN服务器

//from origin post

stunserver.org

stun.xten.com

stun.fwdnet.net

stun.fwdnet.net:3478

 

stun.wirlab.net

stun01.sipphone.com

 

stun.iptel.org

stun.ekiga.net

stun.fwdnet.net

stun01.sipphone.com (no DNS SRV record)

stun.softjoys.com (no DNS SRV record)

stun.voipbuster.com (no DNS SRV record)

stun.voxgratia.org (no DNS SRV record)

stun.xten.com

stunserver.org

stun.sipgate.net:10000

stun.softjoys.com:3478

 

//fromhttps://gist.github.com/zziuni/3741933

# source : http://code.google.com/p/natvpn/source/browse/trunk/stun_server_list

# A list of available STUN server.

 

stun.l.google.com:19302

stun1.l.google.com:19302

stun2.l.google.com:19302

stun3.l.google.com:19302

stun4.l.google.com:19302

stun01.sipphone.com

stun.ekiga.net

stun.fwdnet.net

stun.ideasip.com

stun.iptel.org

stun.rixtelecom.se

stun.schlund.de

stunserver.org

stun.softjoys.com

stun.voiparound.com

stun.voipbuster.com

stun.voipstunt.com

stun.voxgratia.org

stun.xten.com

 

6.3  Turn协议

6.3.1    概念

TURN(全名 Traversal Using Relay NAT),是一种资料传输协议(data-transfer protocol)。允许在TCP或UDP的连线上跨越 NAT 或防火墙。

 

TURN是一个client-server协议。TURN的NAT穿透方法与STUN类似,都是通过取得应用层中的公有地址达到NAT穿透。但实现TURN client的终端必须在通讯开始前与TURN server进行交互,并要求TURN server产生"relay port", 也就是relayed-transport-address。这时 TURN server会建立peer, 即远端端点(remote endpoints), 开始进行中继(relay)的动作,TURN client利用relay port将资料传送至peer, 再由peer转传到另一方的TURN client。

 

6.3.2    Turn uri

turnURI       = scheme":" host [ ":" port ]

                   [ "?transport="transport ]

  scheme        = "turn" /"turns"

  transport     = "udp" /"tcp" / transport-ext

  transport-ext = 1*unreserved

 

 

   Table 1 shows how the <secure>,<port> and <transport> components are

   populated from various URIs.  For all these examples, the <host>

   component is populated with"example.org".

 

  +---------------------------------+----------+--------+-------------+

   | URI                             | <secure> |<port> | <transport> |

  +---------------------------------+----------+--------+-------------+

   |turn:example.org                |false    |        |             |

   | turns:example.org               | true     |       |             |

   | turn:example.org:8000           | false    | 8000  |             |

   | turn:example.org?transport=udp  | false   |        | UDP         |

   | turn:example.org?transport=tcp  | false   |        | TCP         |

   | turns:example.org?transport=tcp |true     |        | TLS         |

  +---------------------------------+----------+--------+-------------+

 

6.3.3  开源服务器工程

  • Restund OpenSource Modular STUN/TURN Server (BSD License)
  • Numb is a free STUN/TURN server.
  • TurnServer - OpenSource TURN server.
  • reTurn - opensource STUN/TURN server and client library (C++)
  • TURN Server - High-Performance Open Source TURN/STUN server (BSD license) and client library (C)
    https://code.google.com/p/rfc5766-turn-server/
    搭建教程:http://www.dialogic.com/den/developer_forums/f/71/t/10238.aspx
    http://zhangjunli177.blog.163.com/blog/static/138607308201341411384462/

 

6.3.4  开源库

  • AnyFirewall - STUN, TURN & ICE library.
  • Libnice - STUN, TURN & ICE library used in Pidgin, GNOME, MeeGo, etc.
  • ice4j - STUN, TURN & ICE library in Java

 

 

6.4   交互式连接建立(Interactive Connectivity Establishment),一种综合性的NAT穿越的技术。

交互式连接建立是由IETF的MMUSIC工作组开发出来的一种framework,可整合各种NAT穿透技术,如STUN、TURN(Traversal Using Relay NAT,中继NAT实现的穿透)、RSIP(Realm Specific IP,特定域IP)等。该framework可以让SIP的客户端利用各种NAT穿透方式打穿远程的防火墙。

 

一、ICE产生的背景

基于信令协议的多媒体传输是一个两段式传输。首先,通过信令协议(如SIP)建立一个会话连接,通过该连接,会话双方(Agent)通过SIP交互所承载的SDP消息彼此学习传输媒体时所必须的信息,针对媒体传输机制达成共识。然后,通常采用RTP协议进行媒体传输。

基于传输效率的考虑,通常在完成第一阶段的交互之后,通信双方另外建立一条直接的连接传输媒体。这样就会减少传输时延、降低丢包率并减少开销。这样,用于SIP传输的链路就不再用于传输媒体。现在,问题出现了,由于不采用原来的链路,当传输双方中任一方位于NAT之后,新的传输链接必须考虑NAT穿越问题。

通常有四种形式的NAT,对于每一中NAT方式,都有相应的解决方案。然而,每一种NAT穿越解决方案都局限于穿越对应得NAT方式,对于复杂的网络环境来说,将会出现无法进行媒体传输的情况,同时这些方案给整个系统带来了在不同程度上的脆弱性和复杂性。

在这种背景下,InteractiveConnectivity Establishment(交互式连通建立方式)也即ICE解决方案应运而生。ICE方式能够在不增加整个系统的复杂性和脆弱性的情况下,实现对各种形式的NAT进行穿越,使得媒体流在通信双方顺利传输。

二、ICE工作的基本原理及特性

ICE是一种探索和更新式的解决方案。通过收集自己的和通信对端的尽可能多的网络信息(各种网络地址),尝试在这些地址之间建立数据通道,并在此过程中不断更新先前收集到的信息,从而找到和选择一条能够进行NAT穿越的数据通道。

其特性如下:ICE实现不是很复杂,支持TCP穿透,对NAT设备没有要求,支持所有类型的NAT,必须在客户端实现ICE,在网络结构中需要STUN/TURN服务器,具有与协议无关性和良好的可扩展性,安全性和健壮性都比较好。

三、ICE工作的核心

如下内容是ICE实现NAT穿透的所要完成的核心处理。包括收集地址,对地址进行排序、配对,然后执行连通性检查。

1、收集地址

Agent必须确定所有的候选的地址。这些地址包括本地网络接口的地址和由它派生的其他所有地址。本地网络地址包括本地网卡地址、VPN网络地址、MIP网络地址等。派生地址指的是通过本地地址向STUN服务器发送STUN请求获得的网络地址,这些地址分为两类,一类是通过STUN的绑定发现用法得到的地址,称为服务器反向候选地址(ServerReflexive Candidates)或服务器反向地址。另一类是通过中继用法得到的,称为中继地址(RELAYEDCANDIDATES)。上面提到的两种用法在相应的规范中提出。

服务器反向地址实际上就是终端的网络包经过一重或多重NAT穿透之后,由STUN服务器观察到的经过NAT转换之后的地址。中继地址是STUN服务器收到STUN请求后,为请求发起方在本机上分配的代理地址,所有被路由到该地址的网络包将会被转发到服务器反向地址,继而穿透NAT发送到终端,因此如名字所示,它是STUN服务器完成中继功能的地址。

为了找到服务器反向地址,Agent通过每一个主机候选地址(通过绑定主机某个接口和端口而获取的候选地址),使用绑定发现用法(BindingDiscovery Usage [11])发送一个STUN绑定请求给STUN服务器(STUN服务器的地址已经配置或者可以通过某种途径学习到)。当Agent发送绑定请求,NAT将分配一个绑定,它映射该服务器反向地址到主机候选地址。这样,通过主机候选地址发送的外发包,将通过NAT转换为通过服务器反向地址发送的包。发往服务器反向候选地址的包,将被NAT转换为发往该主机候选地址的包,并转发给Agent。

当Agent与STUN服务器之间存在多重NAT,那么STUN请求将会针对每一个NAT创建一个绑定,但是,只有最外部的服务器反向地址会被Agent发现。如果Agent不在任何NAT之后,那么,基候选传输地址将与服务器反向地址相同,服务器反向地址可以忽略。

关于中继地址,STUN中继用法允许STUN服务器作为一个媒体中继器进行工作,在L与R之间进行转发。为了发送消息到L,R必须发送消息给媒体中继器,通过媒体中继器转发给L。反之亦然。

从L到R的消息其地址信息将两次被重写:第一次被NAT,第二次被STUN中继服务器。这样,R所了解的想与之通信的地址就是STUN中继服务器的地址。这个地址就是中继地址。

2、连通性检查

Agent L收集到所有的候选地址后,就将它们按优先级高低进行排序,再通过信令信道发送给AgentR。这些候选地址作为SDP请求的属性被传输。当R收到请求,它执行相同的地址收集过程,并且把它自己的候选地址作为响应消息发给请求者。这样,每个Agent都将有一个完整的包含了双方候选地址的列表,然后准备执行连通性检查。

连通性检查的基本原理是:

l   按照优先顺序对候选地址进行排序。

l   利用每个候选地址发送一个检查包。

l   收到另一个Agent的确认检查包。

首先,Agent将本地地址集和远程地址集进行配对,如本地有n个地址,远程有m个地址,那么配成n*m对。对这些地址对进行连通性检查是通过发送和接收STUN请求和响应完成的,此时,Agent在每个地址对的本地地址上,必须同时充当STUN服务器和STUN客户端的角色。若通信双方以某一地址对通过一个完整的四次握手,那么该地址对就是有效地址对。

四次握手是指:当通过地址对中的本地地址向地址对中远程地址发送一个STUN请求,并成功收到STUN响应,称该地址对是可接收的;当地址对中的本地地址收到地址对中远程地址的一个STUN请求,并成功地响应,则称该地址对为可发送的。若一个地址对是可接收的,同时又是可发送的,则称该地址对是有效的,即通过连通性检查。则此地址对可用于媒体传输。

通常在对称NAT的情况下,在地址对验证过程中,会出现发现以前收集地址时没有收集到的地址对,这时就要对这些新的地址对进行连通性检查。

3、对候选地址进行排序

由于收集候选地址时,收集的是所有的候选地址,为了能够更快更好的找到能够正常工作的候选地址对,对所有组合进行排序是势在必行的。在此说明进行排序的两个基本原则,详细地排序算法将在后续文档中描述。

l  Agent为它的每个候选地址设置一个数值的优先级,这个优先级连同候选地址对一起发送给通信的对端。

l  综合本地的和远程的候选地址的优先级,计算出候选地址对的优先级,这样,双方的同一个候选地址对的优先级相同。以此排序,则通信双方的排序结果相同。

4、进行SDP编码

为了实现基于ICE的NAT穿越,对SDP进行了扩展,主要增加了四个属性。分别是candidate属性、ice-ufrag属性、ice-pwd属性和remote-candidates属性。

candidate属性为通信提供多种可能的候选地址中的一个。这些地址是使用STUN的端到端的连通性检查为有效的。

remote-candidates属性提供请求者想要应答者在应答中使用的远程候选传输地址标识。

ice-pwd属性提供用于保护STUN连通性检查的密码。

ice-ufrag属性提供在STUN连通性检查中组成用户名的片断。

四、一个例子

两个Agent,L和R,使用ICE。它们都有单个IPv4接口。对于Agent L地址为10.0.1.1,对于R,192.0.2.1。它们都配置了单独的STUN服务器(实际上是同一个),STUN服务器在192.0.2.2地址的3478端口监听STUN请求。这个STUN服务器同时支持绑定发现和中继功能。Agent L位于NAT之后,R位于公网。NAT有一个终端独立的映射特性和依靠地址的过滤特性。NAT公网端的地址是192.0.2.3。网络结构图如下所示。

 

为了便于理解,传输地址用变量名代替。变量名的格式是entity-type-seqno,其中entity是具有该传输地址的接口所在实体,具体为是L、R、STUN或NAT之一。type不是PUB(地址位于公网)就是PRIV(地址位于内网)。seqno是在实体上的相同类型的各传输地址各自的序列号。每个变量都有一个IP地址和端口号,分别用varname.IP和varname.port表示,varname就是变量名。

STUN服务器有公网的传输层地址STUN-PUB-1(192.0.2.2:3478),绑定发现用法和中继用法都使用这个地址。但在此处,两个Agent都不使用中继用法。

在呼叫过程中,STUN消息有被许多属性注解。“S=”属性表明消息的源传输地址,“D=”属性表明消息的目标传输地址。“MA”属性用于STUN绑定响应消息,指明映射的地址。

基于以上规定,媒体传输的初始过程如下图所示。

 

消息

双方都对获取的传输地址进行配对,确定优先级并排序。之后,R开始执行其连通性检查(消息9),由于来自L的候选地址是一个私有地址,所以此检查必定失败,而被丢弃。

同时,L收到应答后,除去包含了服务器反向地址的那对检查,只剩一对检查。基于此对地址,执行连通性检查(消息10),经过NAT转换后发送给R(消息11)。R收到之后发送响应给L(消息12),该消息中通过MA属性指明映射地址,经过NAT之后返回给L(消息13),这样L的连通性检查成功。L检查收到的消息13,以NAT-PUB-1为本地地址,R-PUB-1为远程地址创建新的地址对,并添加到有效列表中。

ICE查看有效列表,发现有一对存在,就发送一个更新请求(消息14)给R,这个请求用于删除没有被选中的候选地址,并且指示远程地址。

消息11到达R之后,会触发R执行一个相同地址对的检查,消息16-19反映了这个过程,在收到消息19的响应之后,R会像L一样创建一对新的地址对(以R-PUB-1为本地地址,以NAT-PUB-1为远程地址),并添加到有效列表中。这样就可以进行媒体传输了。

五、总结

本文档从ICE的产生背景入手,讨论了ICE的基本原理及其特性,并对其工作的几个核心部位进行了简单的概述,在此基础之上,分析了一个基于ICE通信的例子。所涉及的内容都是在宏观上的考虑,进一步的详细论述将在后续工作中展开。

1-4获取服务器反向地址。消息5发送一个请求给R,该请求包括了本地主机候选地址和服务器反向地址。R收到消息5之后,通过消息6-7获取服务器反向地址(由于R不在NAT之后,服务器反向地址与主机候选地址相同),然后发送一个应答(消息8)给L,应答中包括主机候选地址。至此,通信双方都获取了彼此的网络信息。 ICE的典型应用环境

 

6.4.1    IETF 规格

·       InteractiveConnectivity Establishment (ICE): A Protocol for Network Address Translator(NAT) Traversal for Offer/Answer ProtocolsRFC5245

·       SessionTraversal Utilities for NAT (STUN): RFC5389

·       TraversalUsing Relays around NAT (TURN): Relay Extensions to STUN RFC5766

 

 

 

6.4.2    开源工程:

PJNATH- Open Source ICE, STUN, and TURN Library

libnice:GLib ICE library

 

 

6.5  XEP-0166Jingle

本文档定义了在Jabber/XMPP客户间初始化及管理点对点的多媒体会话(sessions)(比如,声音和图像的交换)框架,它在一定程度上与现有的Internet标准具有互操作性。

警告:本标准跟踪文档是实验性的。作为XMPP扩展协议发表,并不意味着XMPP标准基金会批准了这个协议。我们鼓励对本协议进行探索性的实现,但在本协议的状态发展为草稿之前,产品性的系统不应实现本协议。


文档信息

系列:XEP

序号:0166

发布者:XMPP标准基金会

状态:实验性的

类型:标准跟踪

版本:0.14

最后更新:2007-04-17

批准机构:XMPP理事会

依赖标准:XMPP核心标准

被替代标准:无

缩略名:未指派

Wiki页:[1]

作者信息

法律通告

讨论地点

相关的XMPP

术语

 

6.5.1    绪论

从Jabber/XMPP客户内部初始化和管理点到点(p2p)互操作(象声音、图像、或文件共享交换)的未广泛采用的标准已经有了。虽然,一些大 的服务提供商和Jabber/XMPP客户已经写出和实现了他们自己独有的用于点对点信号处理的XMPP扩展,但这些技术没有公开,并且总是没有考虑到与公共转换电话网络(PSTN)或跨互联网声音协议(VoIP)的互操作性的需求。这些网络建立在IETF的 __会话初始化协议(SIP)__上,在RFC3261[\[注1\]|XMPP文档列表/XMPP扩展/XEP-0166]及其各种扩展中有详细说明。

与此相反,唯一存在的开放协议是{link:初始化及协商会话的传输|http://www.xmpp.org/extensions/xep-0111.html}[\ [注2\]|XMPP文档列表/XMPP扩展/XEP-0166],它使得初始化及管理点对点会话成为可能,却没有提供足够多的在Jabber/XMPP 客户端中能轻松地实现的关键性的信号处理语义。其结果导致在XMPP社区里有关信号处理的协议支离破碎。基本上,有两中方法可以解决这个问题:

  1. 推荐所有的客户端开发者实现双重(XMPP+SIP)解决方案.
  2. 定义一个XMPP信号处理的完整特征的协议。

实现经验表明,双重方法也许不会在所有的计算平台上都可行-也许Jabber客户端已经写完了,或者虽然可行但并不值当。因此,定义一个XMPP信号处理协议似乎合情合理,这个协议能提供所需的信号处理语义,同时也使得与现有互联网标准的互操作性相对简单。

作为收到的XEP-0111返馈的一个结果,文档的原作者(Joe Hildebrand 和Peter Saint-Andre)开始定义这样的一个信号处理协议,代码名为Jingle。通过与Google Talk小组\[4\]成员交流,发现形成的Jingle方法在概念上(甚至在句法上)都与在Google Talk程序中使用的信号处理协议非常相似。因此,为了保持互操作性和适用性,我们决定协调这两种方法。因此,由本文详细说明的信号处理协议基本上等同于现有的Google Talk协议,只是根据在XMPP标准基金会的标准化的实施及发表进程中收到的反馈作了一些调整。

Jingle的目的不是排挤或替代SIP。因为构建双重XMPP+SIP客户端非常困难,导致本质上程序控制的两个中心,所以,我们将 Jingle设计成纯的XMPP信号处理协议。Jingle意欲与SIP相互作用,这样数百万已布置的XMPP客户端能够加到现有的开放的VoIP网络之 中,而不是将XMPP用户限制在某个分离的独特的VoIP网络中。

6.5.2    需求

这里定义的协议的目标是满足如下需求:

  1. 使得XMPP内多种点对点会话(不限于声音和视频)的管理成为可能\[6\]。
  2. 明确分离信号处理通道(XMPP)与数据通道(例如,在RFC3550中说明的实时传输协议\[7\])。
  3. 明确分离内容描述格式(例如,用于语音聊天的)与内容传输方法(比如,在RFC768\[8\]中说明的用户数据报协议)。
  4. 使得从现有会话中加入、修改、删除内容类型成为可能。
  5. 使得实现支持标准的Jabber/XMPP客户端中的协议相对容易。
  6. 当需要与非XMPP实体通讯的时候,尽可能将复杂性推到XMPP网络与非XMPP网络间的服务器端网关上。

本文档仅定义了信号处理协议。其他文档详细说明了如下内容:

  • 各种内容描述格式(音频、视频等),如有可能,将这些类型映射到会话描述协议(SDP,参见RFC4566[9])中;示例包括经由RTP的Jingle音频[10]及经由RTP的视频[11]。
  • 各种内容传输方法;示例包括Jingle ICE传输方法\[12\]及Jingle原生UDP传输\[13\]。
  • 映射Jingle信号处理协议到现有现有信号处理标准的方法,象IETF的会话初始化协议(SIP;参见RFC2361[14]),ITU的H.323协议(见H.323[15]);这些文档即将完成。


1.1 2.术语表{anchor:术语表}

{table}

术语|定义

会话 |连接两个实体的许多对已协商的内容传输方法和内容描述格式。它被限定在初始化请求和会话结束时间的时间段内。在一个会话的生命周期内,可加入或删除成对的内容描述和内容传输方法。在某一时刻,一个会话至少有一个已协商的内容类型。

内容类型|一个内容描述和一个内容传输方法的组合。

内容描述|内容类型将被建立的格式,从形式上声明了会话的一种用途(如,"voice"或"video")。这是会话的(即,传输的比特位)“是什么”,象建立语音通话时可接受的编码器等。按照Jingle XML语法,内容类型是元素<description/>的命名空间。

传输方法|实体间建立数据流的方法。可能的传输包括ICE-TCP,原生UDP,带内数据(inbanddata)等。这是关于会话“怎样”的部分。按照Jingle XML语法,这是元素<transport/>的命名空间。内容传输方法定义了怎样将比特位从一台主机传到另一台。每种传输方法必须指定是有损的(允许丢包)还是无损的(不允许丢包)。

组件|组件是需要在端点间传输的特定会话上下文中特定内容类型的编号的数据流。协商每个组件的细节是由传输负责。根据内容类型和内容描述,一个内容描述可能需要多个组件来通讯(例如,音频内容类型也许用两个组件:一个传输RTP流,另一个传输RTCP定时信息)。

{table}

 

1.1 4.概念及方法{anchor:概念和方法}

 

Jingle有三部分组成,每部分由自己的语法、语义及状态机:

1. 总会话管理

1. 内容描述格式("什么")

1. 内容传输方法("怎样")

 

本文档定义了总会话管理的语义和语法。另有单独的文档,详细说明了用于内容描述和内容传输方法的可插入式“槽(slots)”。基于完整性的考虑,本文档也包含了与描述格式和传输方法有关的全部动作的示例。

 

从最根本上来说,协商Jingle会话的过程是这样的:

1. 一个用户(“发起方”)向另一个用户(“接收方”)发送一个带内容类型的会话请求,会话请求至少包含一个内容类型。

1. 如果接收者想要处理,它会通过发送一个IQ结果暂时接受这个请求。

1. 发起方和接收方以尽可能快的速度交换可能的传输候选方法(进一步协商前的传输候选方法的快速发送,是为了缩短媒体数据可流动前的必要时间)。

1. 检查这些传输候选方法的连通性。

1. 一旦接收方找到了媒体数据可流动的候选方法,接收方会向初始方发出一个“会话接受”动作。

1. 双发开始通过协商好的候选方法发送媒体数据。

 

如果双方随后发现了更好的候选方法,他们会进行“内容修改”协商,然后转到这个更好的候选方法上。自然他们也会修改与会话相关的其他参数(如给语音聊天加入视频)。

 

1.1 4.1总会话管理{anchor:总会话管理}

 

总会话管理的状态机(也即每个Session ID的状态)如下:

{code}

            o
            |
            |会话开始
            |
            | +-----------+
            |/            | 
        阻塞 o----------+  |
            | |内容接受  |  |
            | |内容修改  |  |
            | |内容移除  |  |
            | |会话信息  |  |
            | |传输信息  |  |
            | +--------+  |
            |             |
            |会话接受       |
            |             |
        活动 o----------+  |
            | |内容接受  |  |
            | |内容增加  |  |
            | |内容修改  |  |
            | |内容移除  |  |
            | |会话信息  |  |
            | |传输信息  |  |
            | +--------+  |
            |             |
            +-------------+
                          |
                          |会话中止
                          |
                          o 结束

{code}

总会话状态有三种:

1. 阻塞

1. 活动

1. 结束

 

与管理总体Jingle会话相关的动作如下:

 

__表2:Jingle动作__

{table}

动作|描述

内容接受|接受从另一方受到的内容增加或内容移除。

内容增加|增加一个或多个内容类型到会话中。这个动作 __不能__ 在会话的 __阻塞__ 状态时发出。当一方发出内容增加的时候,它 __必须__ 忽略从另一方收到的任何动作,直到收到内容增加的确认。

内容修改|改变现有的内容类型。接收方 __不能__ 以另一个内容修改来回应内容修改动作。

内容移除|从会话中移除一个或多个内容类型。

会话接受|最终接受会话协商。表明这个动作也适合内容接受(进而适合描述接受和传输接收)。

会话信息|发送会话级的信息/消息,如响铃消息(对Jingle音频来说)。

会话开始|请求一个新Jingle会话协商。

会话中止|结束现有会话。

传输信息|交换传输候选方法;主要用在XEP-0176中,也可以用在其他规范中。

{table}

 

1.1 5.会话流{anchor:会话流}

1.1 5.1资源确定{anchor:资源确定}

 

为了开始Jingle会话,发起方必须确定接收方的哪种XMPP资源最适合想要的内容描述格式。如果联系方仅有一种XMPP资源,这一任务 __必须__ 用服务发现\[18\]或用在实体能力\[19\]中说明的基于现身(presence-based)的简介(profile)的服务发现来完成。

 

很自然,用实体能力而不是向用户花名册中的每个联系人发送服务发现请求效率更高。由此,某个客户版本对Jingle及各种Jingle内容 描述格式和内容传输方法的支持等一般性信息(而不是每个的JID的)都能确定下来,这些信息随后被缓存。具体细节参考EXP-0115。

 

如果联系方有不止一种XMPP资源,可能仅有一种资源支持Jingle和想要的内容描述格式,在这种情况下,用户 __必须__ 用这一资源初始化Jingle信号处理。

 

如果联系方有超过一种XMPP资源支持Jingle和想要的内容描述格式, __建议__ 用 ~~资源应用优先权~~\[20\]来确定哪种资源最适合初始化Jingle会话。

 

1.1 5.2初始化{anchor:初始化}

一旦发起方发现了接收方哪种XMPP资源适合想要的内容描述格式,就向接收方发送一个会话初始化请求。这个请求是个包含<jingle/>元素的IQ集,元素的命名空间为‘http://www.xmpp.org/extensions/xep-0166.html#ns’。<jingle/> 元素 __必须__ 有‘action‘ 、‘initiator‘和‘sid‘属性(两个字符唯一区分会话)。初始化时,‘action‘属性 __必须__ 是"session-initiate",<jingle/>元素 __必须__ 包含一个或多个<content/>元素,每个元素定义会话期间的要传输的内容类型;每个<content/>元素分别包 含<description/>子元素,指定想要的内容描述格式,<transport/>子元素,指定了可能的内容传输方法。 如果任何一方想要对相同的内容描述使用多种传输方法,则必须发送多个<content/>元素。

 

下面是一个Jingle会话初始化请求的例子,会话包含了音频和视频:

 

__例1. 初始化示例__

{code:xml}

<iqfrom=‘romeo@montague.net/orchard‘ to=‘juliet@capulet.com/balcony‘ id=‘jingle1‘type=‘set‘>

 <jingle xmlns=‘http://www.xmpp.org/extensions/xep-0166.html#ns‘
         action=‘session-initiate‘
         initiator=‘romeo@montague.net/orchard‘
         sid=‘a73sjjvkla37jfea‘>
   <content creator=‘initiator‘ name=‘this-is-the-audio-content‘>
     <description xmlns=‘http://www.xmpp.org/extensions/xep-0167.html#ns‘>
       ...
     </description>
     <transport xmlns=‘http://www.xmpp.org/extensions/xep-0176.html#ns‘/>
   </content>
   <content creator=‘initiator‘ name=‘this-is-the-video-content‘>
     <description xmlns=‘http://www.xmpp.org/extensions/xep-0180.html#ns‘>
       ...
     </description>
     <transport xmlns=‘http://www.xmpp.org/extensions/xep-0176.html#ns‘/>
   </content>
 </jingle>

</iq>

{code}

 

注意:元素<description/>和<transport/>的语法、语义超出了本规范的范围,它们在相关的规范中定义。

 

元素<jingle/>有如下属性:

 

  • ‘action’属性是 __必需的__ ;它指定了本文中列出Jingle动作(如,"session-initiate")。
  • ‘initiator‘属性是发起会话流的实体的完整JID(可能与IQ集中地址‘from‘不同)。
  • ‘reasoncode’属性是 __可选的__ ,指定机器可读的动作发送目的(如,用在会话中止动作的"connectivity-error")。
  • ‘reasontext‘属性是 __可选的__ ,指定人可读的动作发送目的(如,用于会话中止动作的"Sorry,gotta go")。
  • ‘responder‘属性(见下面的例子)是回应发起的实体的完整JID(可能与IQ集中‘to‘地址不同)。
  • ‘sid’属性是由发起方产生的随机的会话识别符;它 __应该__ 符合XML Nmtoken production\[21\],这样对&之类的字符就不需要进行XML字符的转义了。(注意:‘sid‘属性可有效地映射到SIP的‘Call-ID’参数)

 

元素<content/>有如下属性:

  • ‘creator‘属性是 __必需的__ ;它指明了那一方最初产生的内容描述(用于防止修改时的竞态条件(race conditions)的发生)。
  • ‘name’属性是 __必需的__ ;它指明了内容类型的独特的名字或识别符(这个识别符是不透明的,没有语义上的意义)。
  • ‘profile‘属性是 __推荐的__ ;对有些内容类型,它说明了所用的简介(如,在实时传输协议上下文中的"RTP/AVP")
  • ‘senders’属性是 __推荐的__ ;它说明了会话中那个实体将要产生内容;允许的值是"initiator","recipient"或"both"(缺省值是"both")。

 

注意:为了加速会话建立,发起方 __可以__ 在发送“session-initiate”消息后接到接收方响应之前,立即发送传输候选方法(如,用于ICE传输的协商),(也就是说,发起方 __必须__ 认定会话是存在的,即使还没有收到响应)。如果按顺序传输的话,接收方应在收到"session-initiate"消息后,应收到诸如"transport-info"之类的消息(如果没收到,那么接收方返回<unknown-session/>错误是恰当的,因为按照它 的状态机,会话并不存在)。

 

1.1 5.3接收方响应{anchor:接收方响应}

 

除非有错误发生,接收方 __必须__ 收到的发起请求:

 

例2. 接收方响应发起请求

{code:xml}

<iq type=‘result‘from=‘juliet@capulet.com/balcony‘ to=‘romeo@montague.net/orchard‘id=‘jingle1‘/>

{code}

 

如果接收方响应了发起请求,双方都必须认定会话处在 __阻塞__ 状态。

 

有几种原因接收方会返回一个错误,而不是响应发起请求:

  • 对接收方来说发起方是未知的(比如,通过在线(presence)订阅),接收方不能与未知实体通讯。
  • 接收方希望转到另一个地址。
  • 接收方不支持Jingle。
  • 接收方不支持任何指定的内容描述格式。
  • 接收方不支持任何指定的内容传输方法。
  • 发起请求格式是错误的。

 

如果对接收方来说发起方是未知的(比如,通过在线订阅),并且接收方有不与未知实体经由Jingle通讯的策略,则接收方 __应该__ 返回一个<service-unavailable/>错误。

 

例3. 发起方未知

{code:xml}

<iq type=‘error‘from=‘juliet@capulet.com/balcony‘ to=‘romeo@montague.net/orchard‘id=‘jingle1‘>

 <error type=‘cancel‘>
   <service-unavailable xmlns=‘urn:ietf:params:xml:ns:xmpp-stanzas‘/>
 </error>

</iq>

{code}

 

如果接收方希望转向另一个地址,它 __应该__ 返回一个<redirect/>错误。

 

例4. 接收方转向

{code:xml}

<iq type=‘error‘from=‘juliet@capulet.com/balcony‘ to=‘romeo@montague.net/orchard‘id=‘jingle1‘>

 <error type=‘cancel‘>
   <redirect xmlns=‘urn:ietf:params:xml:ns:xmpp-stanzas‘>xmpp:voicemail@capulet.com</redirect>
 </error>

</iq>

{code}

 

如果接收方不支持Jingle,则 __必须__ 返回一个错误。

 

例5. 接收方不支持Jingle

{code:xml}

<iq type=‘error‘from=‘juliet@capulet.com/balcony‘ to=‘romeo@montague.net/orchard‘id=‘jingle1‘>

 <error type=‘cancel‘>
   <service-unavailable xmlns=‘urn:ietf:params:xml:ns:xmpp-stanzas‘/>
 </error>

</iq>

{code}

 

如果接收方不支持任何一种指定的内容描述格式,它 __必须__ 返回一个<feature-not-implemented/>错误,和具有Jingle特性的出错条件的<unsupported-content>。

 

例6. 接收方不支持任何内容描述格式

{code:xml}

<iq type=‘error‘from=‘juliet@capulet.com/balcony‘ to=‘romeo@montague.net/orchard‘id=‘jingle1‘>

 <error type=‘cancel‘>
   <feature-not-implemented xmlns=‘urn:ietf:params:xml:ns:xmpp-stanzas‘/>
   <unsupported-content xmlns=‘http://www.xmpp.org/extensions/xep-0166.html#ns-errors‘/>
 </error>

</iq>

{code}

 

如果接收方不支持任何一种指定的内容传输方法,它 __必须__ 返回一个<feature-not-implemented/>错误,和具有Jingle特性的出错条件的<unsupported-transports>。

 

例7. 接收方不支持任何内容传输方法

{code:xml}

<iq type=‘error‘from=‘juliet@capulet.com/balcony‘ to=‘romeo@montague.net/orchard‘id=‘jingle1‘>

 <error type=‘cancel‘>
   <feature-not-implemented xmlns=‘urn:ietf:params:xml:ns:xmpp-stanzas‘/>
   <unsupported-transports xmlns=‘http://www.xmpp.org/extensions/xep-0166.html#ns-errors‘/>
 </error>

</iq>

{code}

 

如果发起请求的格式错误,接收方 __必须__ 返回一个<bad-request/>错误。

 

例8. 发起请求的格式错误

{code:xml}

<iq type=‘error‘from=‘juliet@capulet.com/balcony‘ to=‘romeo@montague.net/orchard‘id=‘jingle1‘>

 <error type=‘cancel‘>
   <bad-request xmlns=‘urn:ietf:params:xml:ns:xmpp-stanzas‘/>
 </error>

</iq>

{code}

 

1.1 5.4拒绝{anchor:拒绝}

为拒绝会话发起请求,接收方 __必须__ 响应收到的会话发起请求,然后按[中止|XMPP文档列表/XMPP扩展/XEP-0166#中止]中描述的方法中止会话。

 

1.1 5.5 协商{anchor:协商}

 

一般情况下,双方在达成可接受的一系列内容类型、内容描述格式和内容传输方法前,协商是必要的。可能要协商的这些参数的组合是很多的,这里并没有列出全部(有些在各种内容描述格式和内容传输方法规范中列出)。

 

一个会话级的协商是移除一种内容类型。例如,让我们设想,有一天朱丽叶的心情很糟糕,她当然不想在和罗密欧的Jingle会话中包含视频,所以她给罗密欧发送了一个“内容移除”请求:

 

例9. 内容类型移除

{code:xml}

<iqfrom=‘juliet@capulet.com/balcony‘ to=‘romeo@montague.net/orchard‘ id=‘reduce1‘type=‘set‘>

 <jingle xmlns=‘http://www.xmpp.org/extensions/xep-0166.html#ns‘
         action=‘content-remove‘
         initiator=‘romeo@montague.net/orchard‘
         sid=‘a73sjjvkla37jfea‘>
   <content creator=‘initiator‘ name=‘this-is-the-video-content‘/>
 </jingle>

</iq>

{code}

 

实体收到了这个会话缩减请求,然后响应这个请求:

 

例10. 响应

{code:xml}

<iqfrom=‘romeo@montague.net/orchard‘ to=‘juliet@capulet.com/balcony‘ id=‘reduce1‘type=‘result‘/>

{code}

 

如果缩减的结果是会话不再有任何内容类型,收到会话缩减的实体 __应该__ 向另一方发送会话中止动作(因为没有内容类型的会话是无效的)。

 

另一个会话级的协商是增加一个内容类型;然而,这个动作__必不能__ 在会话处于 __阻塞__ 状态时来做,只有在会话处于 __活动__ 状态时才可以。

 

1.1 5.6 接受{anchor:接受}

 

协商过内容传输方法和内容描述格式后,如果接收方确定能够建立连接,它将向发起方法送确定接受:

 

例11. 接收方确定接受呼叫

{code:xml}

<iq type=‘set‘from=‘juliet@capulet.com/balcony‘ to=‘romeo@montague.net/orchard‘id=‘accept1‘>

 <jingle xmlns=‘http://www.xmpp.org/extensions/xep-0166.html#ns‘
         action=‘session-accept‘
         initiator=‘romeo@montague.net/orchard‘
         responder=‘juliet@capulet.com/balcony‘
         sid=‘a73sjjvkla37jfea‘>
   <content creator=‘initiator‘ name=‘this-is-the-audio-content‘>
     <description xmlns=‘http://www.xmpp.org/extensions/xep-0167.html#ns‘>
       ...
     </description>
     <transport xmlns=‘http://www.xmpp.org/extensions/xep-0177.html#ns‘>
       <candidate .../>
     </transport>
   </content>
 </jingle>

</iq>

{code}

 

<jingle/>元素 __必须__ 包含一个或多个<content/>元素,后者 __必须__ 包含一个<description>元素和一个<transport/>元素。<jingle/>元素 __应该__ 有‘responder’属性,以明确指明响应实体的完整JID,有关当前Jingle会话的所有通讯,发起方应该向这个JID发送。

 

然后发起方响应接收方的确认接受:

 

例12. 发起方响应确认接受

{code:xml}

<iq type=‘result‘to=‘juliet@capulet.com/balcony‘ from=‘romeo@montague.net/orchard‘id=‘accept1‘/>

{code}

 

此时,发起方和接收方可以通过协商好的连接开始发送内容了。

 

如果一方无法找到合适的内容传输方法,它 __应该__ 下面描述的那样中止会话。

 

1.1 5.7 修改活动会话{anchor:修改活动会话}

 

为修改一个活动会话,任一方都可向另一方发送"content-remove"、"content-add"、"content-modify", "description-modify"、"transport-modify" 动作。然后接收方发送恰当的"-accept"或"-decline"动作,也可能首先发送一个"-info"动作。

 

如果双方同时发送了修改消息,那么会话发起方的修改消息__必须__ 胜过接收方的修改消息,发起方 __应该__ 返回一个<unexpected-request/>错误。

 

修改活动会话的一个例子是增加一个会话内容。例如,设想一下朱丽叶的心情好了,现在想加入视频。于是向罗密欧发送"content-add"请求:

 

例13. 增加一个内容类型

{code:xml}

<iqfrom=‘juliet@capulet.com/balcony‘ to=‘romeo@montague.net/orchard‘ id=‘add1‘type=‘set‘>

 <jingle xmlns=‘http://www.xmpp.org/extensions/xep-0166.html#ns‘
         action=‘content-add‘
         initiator=‘romeo@montague.net/orchard‘
         sid=‘a73sjjvkla37jfea‘>
   <content creator=‘responder‘ name=‘video-is-back‘>
     <description xmlns=‘http://www.xmpp.org/extensions/xep-0180.html#ns‘>
       ...
     </description>
     <transport xmlns=‘http://www.xmpp.org/extensions/xep-0177.html#ns‘>
       <candidate .../>
     </transport>
   </content>
 </jingle>

</iq>

 

{code}

 

实体接收到会话扩展请求,响应这个请求,如果可接受,返回一个内容接受:

The entity receiving the sessionextension request then acknowledges the request and, if it is acceptable,returns a content-accept:

 

例14. 响应

{code:xml}

<iqfrom=‘romeo@montague.net/orchard‘ to=‘juliet@capulet.com/balcony‘ id=‘add1‘type=‘result‘/>

{code}

 

例15. 内容接受

{code:xml}

<iqfrom=‘romeo@montague.net/orchard‘ to=‘juliet@capulet.com/balcony‘ id=‘add2‘type=‘set‘>

 <jingle xmlns=‘http://www.xmpp.org/extensions/xep-0166.html#ns‘
         action=‘content-accept‘
         initiator=‘romeo@montague.net/orchard‘
         sid=‘a73sjjvkla37jfea‘>
   <content creator=‘responder‘ name=‘video-is-back‘>
     <description xmlns=‘http://www.xmpp.org/extensions/xep-0180.html#ns‘>
       ...
     </description>
     <transport xmlns=‘http://www.xmpp.org/extensions/xep-0177.html#ns‘>
       <candidate .../>
     </transport>
   </content>
 </jingle>

</iq>

{code}

 

另一方响应接受。

 

例16. 响应

{code:xmk}

<iqfrom=‘juliet@capulet.com/balcony‘ to=‘romeo@montague.net/orchard‘ id=‘add2‘type=‘result‘/>

{code}

 

1.1 5.8 中止{anchor:中止}

 

为了顺利地结束会话(在响应了初始化请求后的任何时候都__可以__ 这么做,包括立即想立刻拒绝请求的时候),无论接收方还是初始方都 __必须__ 向对方发送一个“中止”动作。

 

例17 接收方中止会话

{code:xml}

<iqfrom=‘juliet@capulet.com/balcony‘

   id=‘term1‘
   to=‘romeo@montague.net/orchard‘
   type=‘set‘>
 <jingle xmlns=‘http://www.xmpp.org/extensions/xep-0166.html#ns‘
         action=‘session-terminate‘
         initiator=‘romeo@montague.net/orchard‘
         reason=‘Sorry, gotta go!‘
         sid=‘a73sjjvkla37jfea‘/>

</iq>

{code}

 

另一方(这里是初始方)必须响应会话中止:

 

例18. 初始方响应中止

{code:xml}

<iq type=‘result‘to=‘juliet@capulet.com/balcony‘ from=‘romeo@montague.net/orchard‘id=‘term1‘/>

{code}

 

注意:一旦实体发送了"会话中止"动作,它 __必须__ 认定会话已中止(即使在收到对方的响应之前)。如果中止方在发送了“会话中止”动作后收到了对方额外的IQ设置,它 __必须__ 返回一个<unknown-session/>错误。

 

不幸的是,并非所有会话都顺利地结束。下面的事件 __必须__ 认定为会话结束事件,对内容描述格式和内容传输方法的进一步协商 __必须__ 通过协商一个新会话来完成:

  • 从对方那里收到‘会话转向’或‘会话中止‘动作。
  • 从对方那收到<presence type=‘unavailable‘/>。

 

特别地,如果一方从对方收到的在线(presence)类型是"未知(unavailable)"的话,则它 __必须__ 认定会话处于 __结束__ 状态。

 

例19. 接收方离线

{code:xml}

<presencefrom=‘juliet@capulet.com/balcony‘ to=‘romeo@montague.net/orchard‘type=‘unavailable‘/>

{code}

 

自然在这种情况下初始方没什么可响应的。

 

1.1 5.9 通知消息{anchor:通知消息}

Jingle会话开始后的任何时候,任一实体都 __可以__ 向对方发送通知消息,比如,改变内容传输方法或内容描述格式的参数,通知对方一个会话开始请求已经列队等待,设备正在响铃,或一个事先计划的事件已经发生或将要发生。通知消息 __必须__ 是带有<jingle/>元素的IQ设置(IQ-set)指令,<jingle/>元素的‘action‘属性的值 是"session-info","description-info"或“transport-info”之一;<jingle/>元素 __必须__ 进一步包含有效的子元素(会话,内容描述格式或内容传输方法)来说明正在交流的信息。如果一个活动会话的任一方收到了一个空的“session-info”消息,它 __必须__ 返回一个空的IQ结果;这样,一个空的“session-info”消息可用作一个“ping”,来确定会话的活性。(本规范的未来版本也许会定义与“session-info”动作相关的内容载荷。)

 

1.1 6. 出错处理{anchor:出错处理}

Jingle专有的出错条件如下。

 

表3:其他出错条件

{table}

Jingle条件|xmpp条件|说明

<out-of-order/>|<unexpected-request/>|请求不可能在状态机的这一点发生(比如,会话接受后再次初始化)。

<unknown-session/>|<bad-request/>|指定会话的‘sid’属性对接收方未知(例如,根据接收方的状态机会话已经不再有效,因为接收方先前已中止了会话)

<unsupported-content/>|<not-acceptable/>|接收方不支持任何期望的内容描述格式。

<unsupported-transports/>|<not-acceptable/>|接收方不支持任何期望的内容传输方法。

{table}

 

1.1 7. 支持性检测{anchor:支持性检测}

如果一个实体支持Jingle,它 __必须__ 在响应{link:服务发现|http://www.xmpp.org/extensions/xep-0030.html}\[22\]信息请求时,通过返回特性“http://www.xmpp.org/extensions/xep-0166.html#ns”(见有关{link:协议命名空间%7Chttp://www.xmpp.org/extensions/xep-0166.html#ns})将这一事实公布出去。

 

例20. 服务发现信息请求

 

{code:xml}

<iq from=‘romeo@montague.net/orchard‘

   id=‘disco1‘
   to=‘juliet@capulet.com/balcony‘
   type=‘get‘>
 <query xmlns=‘http://jabber.org/protocol/disco#info‘/>

</iq>

{code}

 

例21. 服务发现信息响应

 

{code:xml}

<iq from=‘juliet@capulet.com/balcony‘

   id=‘disco1‘
   to=‘romeo@montague.net/orchard‘
   type=‘result‘>
 <query xmlns=‘http://jabber.org/protocol/disco#info‘>
   ...
   <feature var=‘http://www.xmpp.org/extensions/xep-0166.html#ns‘/>
   ...
 </query>

</iq>

{code}

 

1.1 8.使用协议的一致性{anchor:使用协议的一致性}

 

1.1 8.1 应用类型{anchor:应用类型}

 

说明某种Jingle应用类型的文档(比如,经由RTP的音频) __必须__ 定义:

  1. 为封装进Jingle,如何成功地进行内容协商。
  1. 用于表现内容的<description/>元素及相关语义。
  1. 能否及怎样将内容描述映射到会话描述协议上。
  1. 是通过可靠的还是有损的传输方式(或两者都是)来传输内容。
  1. 通过可靠或有损传输来收发内容的精确说明。

 

1.1 8.2 传输方法{anchor:传输方法}

 

说明Jingle传输方法的文档(比如,纯UDP) __必须__ 定义:

1. 为封装进Jingle,怎样成功地进行传输协商。
1. 用于表现传输类型的<transport/>元素及相关语义。
1. 传输是可靠的还是有损的。
1. 传输是否及怎样处理在这定义的组件(例如,对实时控制协议来说)。

 

1.1 9. 安全性事项{anchor:安全性事项}

1.1 9.1 拒绝服务

Jingle会话可能是资源密集型的。因此,有可能用增加过多Jingle会话负担的方法向一个实体发动决绝服务攻击。必须小心地只从已知的实体那接受内容,并且只接受实体设备能处理的会话。

1.1 9.2 通过网关通讯{anchor:通过网关通讯}

Jingle通讯可通过网关与非XMPP网络完成,这些网络的安全特性与XMPP有很大的不同。(例如,在有些SIP网络中鉴定是可选的,“from”地址可轻易伪造。)与这些网通通讯时必须小心。

1.1 IANA事项{anchor:IANA事项}

本文档要求不与{link:互联网指派数字授权(IANA)|http://www.iana.org/}\[23\]相互作用。

 

1.1 11. XMPP注册处事项{anchor:XMPP注册处事项}

1.1 1\1.1 协议命名空间{anchor:协议命名空间}

在本规范成为草稿状态之前,其相关的命名空间是“http://www.xmpp.org/extensions/xep-0166.html#ns”和"http://www.xmpp.org/extensions/xep-0166.html#ns-errors“;随着规范的发展,{link:XMPP注册员\[24\]|http://www.xmpp.org/registrar/}将按照{link:XMPP注册处功能\[25\]|http://www.xmpp.org/extensions/xep-0053.html}的第四节中定义的过程来发布永久命名空间。

 

1.1 11.2 Jingle内容描述格式注册

XMPP注册处会维护Jingle内容描述格式的注册。整个内容描述格式的注册会在单独的规范中定义(不在本文档中)。定义在XEP系列里 的内容描述格式也 __必须__在XMPP注册处注册,其结果是协议的URN的格式是“urn:xmpp:jingle:description:name”(其中“name”是内容描述格式的注册名)。

 

为提交注册的新值,注册人须按下面的格式定义一个XML段,内容包括相关的XMPP扩展协议,或者将它发送到<registrar@xmpp.org>。

{code:xml}

<content>

 <name>the name of the content description format</name>
 <desc>a natural-language summary of the content description format</desc>
 <transport>whether the content should be sent over a "reliable" or "lossy" transport</transport>
 <doc>the document in which this content description format is specified</doc>

</content>

{code}

 

1.1 11.3 Jingle内容传输方法注册

XMPP注册处会维护Jingle内容传输方法的注册。整个内容传输方法的注册会在单独的规范中定义(不在本文档中)。定义在XEP系列里 的内容传输方法也 __必须__在XMPP注册处注册,其结果是协议的URN的格式是“urn:xmpp:jingle:transport:name”(其中“name”是内容传输方法的注册名)。

 

为提交注册的新值,注册人须按下面的格式定义一个XML段,内容包括相关的XMPP扩展协议,或者将它发送到<registrar@xmpp.org>。

{code:xml}

<transport>

 <name>the name of the content transport method</name>
 <desc>a natural-language summary of the content transport method</desc>
 <type>whether the transport method is "reliable" or "lossy"</type>
 <doc>the document in which this content transport method is specified</doc>

</transport>

{code}

 

1.1 11.4 Jingle原因代码注册{anchor:Jingle原因代码注册}

1.1 11.4.1 过程{anchor:过程}

XMPP注册处会维护一份Jingle动作原因的注册。

 

为提交注册的新值,注册人须按下面的格式定义一个XML段,内容包括相关的XMPP扩展协议,或者将它发送到<registrar@xmpp.org>。

{code:xml}

<reason>

 the value of the ‘reasoncode‘ attribute</name>
 <desc>a natural-language summary of the reason code</desc>
 <doc>the document in which this reason code is specified</doc>

</reason>

{code}

 

1.1 11.4.2 初始注册{anchor:初始注册}

下面提交的原因代码注册从2007年4月开始使用。完整内容和最新的原因代码列表参见注册。

 

{code:xml}

<reason>

 <code>connectivity-error
 <desc>the action (e.g., session-terminate) is related to connectivity problems</desc>
 <doc>XEP-0166</doc>

</reason>

 

<reason>

 general-error
 <desc>the action (e.g., session-terminate) is related to a non-specific application error</desc>
 <doc>XEP-0166</doc>

</reason>

 

<reason>

 media-error
 <desc>the action (e.g., session-terminate) is related to media processing problems</desc>
 <doc>XEP-0166</doc>

</reason>

 

<reason>

 no-error
 <desc>the action is generated during the normal course of state management</desc>
 <doc>XEP-0166</doc>

</reason>

{code}

 

 

1.1 12. XML方案(Schemas){anchor:XML Schemas}

1.1 12.1 Jingle{anchor:Jingle}

{code:xml}

<?xml version=‘1.0‘encoding=‘UTF-8‘?>

 

<xs:schema

   xmlns:xs=‘http://www.w3.org/2001/XMLSchema‘
   targetNamespace=‘http://www.xmpp.org/extensions/xep-0166.html#ns‘
   xmlns=‘http://www.xmpp.org/extensions/xep-0166.html#ns‘
   elementFormDefault=‘qualified‘>

 

 <xs:element name=‘jingle‘>
   <xs:complexType>
     <xs:sequence minOccurs=‘1‘ maxOccurs=‘unlimited‘>
       <xs:element ref=‘content‘/>
     </xs:sequence>
     <xs:attribute name=‘action‘ use=‘required‘>
       <xs:simpleType>
         <xs:restriction base=‘xs:NCName‘>
           <xs:enumeration value=http://www.mamicode.com/‘content-accept‘/>
           <xs:enumeration value=http://www.mamicode.com/‘content-add‘/>
           <xs:enumeration value=http://www.mamicode.com/‘content-modify‘/>
           <xs:enumeration value=http://www.mamicode.com/‘content-remove‘/>
           <xs:enumeration value=http://www.mamicode.com/‘session-accept‘/>
           <xs:enumeration value=http://www.mamicode.com/‘session-info‘/>
           <xs:enumeration value=http://www.mamicode.com/‘session-initiate‘/>
           <xs:enumeration value=http://www.mamicode.com/‘session-terminate‘/>
           <xs:enumeration value=http://www.mamicode.com/‘transport-info‘/>
         </xs:restriction>
       </xs:simpleType>
     </xs:attribute>
     <xs:attribute name=‘initiator‘ type=‘xs:string‘ use=‘required‘/>
     <xs:attribute name=‘reasoncode‘ type=‘xs:string‘ use=‘optional‘/>
     <xs:attribute name=‘reasontext‘ type=‘xs:string‘ use=‘optional‘/>
     <xs:attribute name=‘responder‘ type=‘xs:string‘ use=‘optional‘/>
     <xs:attribute name=‘sid‘ type=‘xs:NMTOKEN‘ use=‘required‘/>
   </xs:complexType>
 </xs:element>

 

 <xs:element name=‘content‘>
   <xs:complexType>
     <xs:choice minOccurs=‘0‘>
       <xs:sequence>
         <xs:any namespace=‘##other‘ minOccurs=‘0‘ maxOccurs=‘unbounded‘/>
       </xs:sequence>
     </xs:choice>
     <xs:attribute name=‘creator‘ use=‘required‘>
       <xs:simpleType>
         <xs:restriction base=‘xs:NCName‘>
           <xs:enumeration value=http://www.mamicode.com/‘initiator‘>
           <xs:enumeration value=http://www.mamicode.com/‘responder‘/>
         </xs:restriction>
       </xs:simpleType>
     </xs:attribute>
     <xs:attribute name=‘name‘ use=‘required‘ type=‘xs:string‘/>
     <xs:attribute name=‘profile‘ use=‘optional‘ type=‘xs:string‘/>
     <xs:attribute name=‘senders‘ use=‘optional‘ default=‘both‘>
       <xs:simpleType>
         <xs:restriction base=‘xs:NCName‘>
           <xs:enumeration value=http://www.mamicode.com/‘both‘>
           <xs:enumeration value=http://www.mamicode.com/‘initiator‘>
           <xs:enumeration value=http://www.mamicode.com/‘responder‘/>
         </xs:restriction>
       </xs:simpleType>
     </xs:attribute>
   </xs:complexType>
 </xs:element>

 

</xs:schema>

{code}

 

1.1 12.2 Jingle出错信息{anchor:Jingle出错信息}

{code:xml}

<?xml version=‘1.0‘encoding=‘UTF-8‘?>

 

<xs:schema

   xmlns:xs=‘http://www.w3.org/2001/XMLSchema‘
   targetNamespace=‘http://www.xmpp.org/extensions/xep-0166.html#ns-errors‘
   xmlns=‘http://www.xmpp.org/extensions/xep-0166.html#ns-errors‘
   elementFormDefault=‘qualified‘>

 

 <xs:element name=‘out-of-order‘ type=‘empty‘/>
 <xs:element name=‘unknown-session‘ type=‘empty‘/>
 <xs:element name=‘unsupported-content‘ type=‘empty‘/>
 <xs:element name=‘unsupported-transports‘ type=‘empty‘/>

 

 <xs:simpleType name=‘empty‘>
   <xs:restriction base=‘xs:string‘>
     <xs:enumeration value=http://www.mamicode.com//>
   </xs:restriction>
 </xs:simpleType>

 

</xs:schema>

{code}

 

 

 

 

6.6  Sctp协议

 

 

 

 

6.7  Rtp协议

 

7  附件:

7.1  Gyp工具

GYP 简介:转载自:http://blog.xiaogaozi.org/2011/10/29/introduction-to-gyp/

说起项目构建工具,Linux 用户最熟悉的恐怕就是 Autotools,它将编译安装这个步骤大大简化。但对于项目作者来说,想要使用 Autotools 生成有效的配置文件着实需要下一番功夫,用现在流行的话来说就是用户体验不够友好。对 Unix shell 的依赖,也使得 Autotools 天生对于跨平台支持不佳。

与其类似的有 CMake,CMake 使用 C++ 编写,原生支持跨平台,不需要像 Autotools 那样写一堆的配置文件,只需一个 CMakeLists.txt 文件即可。简洁的使用方式,强大的功能使得我立马对 CMake 情有独钟。在后来的使用过程中,虽然会遇到一些因为使用习惯带来的小困扰,但我对于 CMake 还是基本满意的。直到我发现了 GYP。

GYP(Generate Your Projects)是由 Chromium 团队开发的跨平台自动化项目构建工具,Chromium 便是通过 GYP 进行项目构建管理。为什么我要选择 GYP,而放弃 CMake 呢?功能上 GYP 和 CMake 很是相似,在我看来,它们的最大区别在于配置文件的编写方式和其中蕴含的思想。

编写 CMake 配置文件相比 Autotools 来说已经简化很多,一个最简单的配置文件只需要写上源文件及生成类型(可执行文件、静态库、动态库等)即可。对分支语句和循环语句的支持也使得 CMake 更加灵活。但是,CMake 最大的问题也是在这个配置文件,请看下面这个示例文件:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

cmake_minimum_required(VERSION 2.8)

project(VP8 CXX)

 

add_definitions(-Wall)

cmake_policy(SET CMP0015 NEW)

include_directories("include")

link_directories("lib")

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "../lib")

set(VP8SRC VP8Encoder.cpp VP8Decoder.cpp)

 

if(X86)

    set(CMAKE_SYSTEM_NAME Darwin)

    set(CMAKE_SYSTEM_PROCESSOR i386)

    set(CMAKE_OSX_ARCHITECTURES "i386")

 

    add_library(vp8 STATIC ${VP8SRC})

elseif(IPHONE)

    if(SIMULATOR)

        set(PLATFORM "iPhoneSimulator")

        set(PROCESSOR i386)

        set(ARCH "i386")

    else()

        set(PLATFORM "iPhoneOS")

        set(PROCESSOR arm)

        set(ARCH "armv7")

    endif()

 

    set(SDKVER "4.0")

    set(DEVROOT "/Developer/Platforms/${PLATFORM}.platform/Developer")

    set(SDKROOT "${DEVROOT}/SDKs/${PLATFORM}${SDKVER}.sdk")

    set(CMAKE_OSX_SYSROOT "${SDKROOT}")

    set(CMAKE_SYSTEM_NAME Generic)

    set(CMAKE_SYSTEM_PROCESSOR ${PROCESSOR})

    set(CMAKE_CXX_COMPILER "${DEVROOT}/usr/bin/g++")

    set(CMAKE_OSX_ARCHITECTURES ${ARCH})

 

    include_directories(SYSTEM "${SDKROOT}/usr/include")

    link_directories(SYSTEM "${SDKROOT}/usr/lib")

 

    add_definitions(-D_PHONE)

    add_library(vp8-armv7-darwin STATIC ${VP8SRC})

endif()

你能一眼看出这个配置文件干了什么吗?其实这个配置文件想要产生的目标(target)只有一个,就是通过 ${VP8SRC} 编译生成的静态库,但因为加上了条件判断,及各种平台相关配置,使得这个配置文件看起来很是复杂。在我看来,编写 CMake 配置文件是一种线性思维,对于同一个目标的配置可能会零散分布在各个地方。而 GYP 则相当不同,GYP 的配置文件更多地强调模块化、结构化。看看下面这个示例文件:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

{

  ‘targets‘: [

    {

      ‘target_name‘: ‘foo‘,

      ‘type‘: ‘<(library)‘,

      ‘dependencies‘: [

        ‘bar‘,

      ],

      ‘defines‘: [

        ‘DEFINE_FOO‘,

        ‘DEFINE_A_VALUE=http://www.mamicode.com/value‘,

      ],

      ‘include_dirs‘: [

        ‘..‘,

      ],

      ‘sources‘: [

        ‘file1.cc‘,

        ‘file2.cc‘,

      ],

      ‘conditions‘: [

        [‘OS=="linux"‘, {

          ‘defines‘: [

            ‘LINUX_DEFINE‘,

          ],

          ‘include_dirs‘: [

            ‘include/linux‘,

          ],

        }],

        [‘OS=="win"‘, {

          ‘defines‘: [

            ‘WINDOWS_SPECIFIC_DEFINE‘,

          ],

        }, { # OS != "win",

          ‘defines‘: [

            ‘NON_WINDOWS_DEFINE‘,

          ],

        }]

      ],

    }

  ],

}

我们可以立马看出上面这个配置文件的输出目标只有一个,也就是 foo,它是一个库文件(至于是静态的还是动态的这需要在生成项目时指定),它依赖的目标、宏定义、包含的头文件路径、源文件是什么,以及根据不同平台设定的不同配置等。这种定义配置文件的方式相比 CMake 来说,让我觉得更加舒服,也更加清晰,特别是当一个输出目标的配置越来越多时,使用 CMake 来管理可能会愈加混乱。

配置文件的编写方式是我区分 GYP 和 CMake 之间最大的不同点,当然 GYP 也有一些小细节值得注意,比如支持跨平台项目工程文件输出,Windows 平台默认是 Visual Studio,Linux 平台默认是Makefile,Mac 平台默认是 Xcode,这个功能CMake 也同样支持,只是缺少了 Xcode。Chromium 团队成员也撰文详细比较了 GYP 和 CMake 之间的优缺点,在开发 GYP 之前,他们也曾试图转到 SCons(这个我没用过,有经验的同学可以比较一下),但是失败了,于是 GYP 就诞生了。

当然 GYP 也不是没有缺点,相反,我觉得它的「缺点」一大堆:

文档不够完整,项目不够正式,某些地方还保留着 Chromium 的影子,看起来像是还没有完全独立出来。

大量的括号嵌套,很容易让人看晕,有过 Lisp 使用经验的同学可以对号入座。对于有括号恐惧症,或者不使用现代编辑器的同学基本可以绕行。

为了支持跨平台,有时不得不加入某些特定平台的配置信息,比如只适用于 Visual Studio 的 RuntimeLibrary 配置,这不利于跨平台配置文件的编写,也无形中增加了编写复杂度。

不支持 make clean,唯一的方法就是将输出目录整个删除或者手动删除其中的某些文件。

如果你已经打算尝试 GYP,那一定记得在生成项目工程文件时加上 --depth 参数,譬如:

$ gyp --depth=.foo.gyp

这也是一个从 Chromium 项目遗留下来的历史问题。

 

 

7.2  Googletest程序

玩转Google开源C++单元测试框架Google Test系列(gtest)(总)

 

 

 

 

7.3  Webrtc库介绍

trunk\webrtc\modules

视频采集---video_capture

   源代码在webrtc/modules/video_capture/main目录下,包含接口和各个平台的源代码。
    在windows平台上,WebRTC采用的是dshow技术,来实现枚举视频的设备信息和视频数据的采集,这意味着可以支持大多数的视频采集设备;对那些需要单独驱动程序的视频采集卡(比如海康高清卡)就无能为力了。
    视频采集支持多种媒体类型,比如I420、YUY2、RGB、UYUY等,并可以进行帧大小和帧率控制。

视频编解码---video_coding
    源代码在webrtc/modules/video_coding目录下。
    WebRTC采用I420/VP8编解码技术。VP8是google收购ON2后的开源实现,并且也用在WebM项目中。VP8能以更少的数据提供更高质量的视频,特别适合视频会议这样的需求。

视频加密--video_engine_encryption
    视频加密是WebRTC的video_engine一部分,相当于视频应用层面的功能,给点对点的视频双方提供了数据上的安全保证,可以防止在Web上视频数据的泄漏。
    视频加密在发送端和接收端进行加解密视频数据,密钥由视频双方协商,代价是会影响视频数据处理的性能;也可以不使用视频加密功能,这样在性能上会好些。
    视频加密的数据源可能是原始的数据流,也可能是编码后的数据流。估计是编码后的数据流,这样加密代价会小一些,需要进一步研究。

视频媒体文件--media_file
    源代码在webrtc/modules/media_file目录下。
    该功能是可以用本地文件作为视频源,有点类似虚拟摄像头的功能;支持的格式有Avi。
    另外,WebRTC还可以录制音视频到本地文件,比较实用的功能。

视频图像处理--video_processing
    源代码在webrtc/modules/video_processing目录下。
    视频图像处理针对每一帧的图像进行处理,包括明暗度检测、颜色增强、降噪处理等功能,用来提升视频质量。

视频显示--video_render
    源代码在webrtc/modules/video_render目录下。
    在windows平台,WebRTC采用direct3d9和directdraw的方式来显示视频,只能这样,必须这样。

 

网络传输与流控
    对于网络视频来讲,数据的传输与控制是核心价值。WebRTC采用的是成熟的RTP/RTCP技术。

 

 

 

 

 

 

 

7.4  webrtc代码相关基础知识

http://blog.csdn.net/chenyufei1013/article/category/1248211

 

 

 

 

7.5  STUN和TURN技术浅析

 

 

7.6  基于ICE的VoIP穿越NAT改进方案

1 引言

    近年来,随着数据网络通信逐渐融入传统的话音业务领域,VoIP技术越来越成为当前商业考虑的对象,并正在向一种正式的商业电话模式演进,而会话初始协议 (SIP,Session Initiation Protoc01)就是用来确保这种演进能够实现而需要的NGN(下一代网络)系列协议中重要的一员。SIP是一个用于建立,更改和终止多媒体会话的应用层控制协议。SIP因其简单、灵活、可扩展性强的特点,已经成为实现VolP系统的热点技术。

    随着计算机网络技术的不断发展,互联网规模飞速膨胀,大量企业和驻地网采用了私有网络通过NAT/防火墙出口来接入公共网络。而由于SIP包头中含有很多 对于路由、接续SIP信令和建立呼叫连接必不可少的地址信息,这样引发了业界对于SIP2穿越NAT/防火墙问题的研究。

    目前,IETF已经对该问题提出了多种解决方案。例如:ALCes(Application Layer Gateways)、MiddleboxControlProtocol、STUN Simple Traversal of UDPthrough NAT)、TURN(Traversal Using Relay NAT)、RSIP(RealmSpecific IP)、Symmetric RTP等。然而,当这些技术应用于不同的网络拓扑时都有着显著的利弊,以至于只能根据不同的接入方式来应用不同的方案,所以,未能很好地解决A11- NATⅢ的问题,同时还会给系统引入许多复杂性和脆弱性因素。此外,由于NAT/防火墙已经大量应用,SIP设备也已经比较成熟,对它们进行升级来支持多 媒体通信穿越NAT/防火墙的代价将相当的大。因此,一种不需要升级任何现有网络设备,能够穿越各种NAT/防火墙并且方便在现有网络中实施的解决方案成 为迫切的需要。

    本文试图寻找一种能够穿越各种类型的NAT/防火墙,无需对现有NAT/防火墙没备做任何改动的解决方案——ICE解决方案,这种方式比以前的解决方案更加灵活,具有广阔的应用前景。

   现有NAT解决方案的比较分析

    主流的NAT穿越解决方案包括STUN、TURN、Proxy及隧道穿越等,这几种方式各具优缺点,比较如下:

    (1)STUN(simpletraversal of UDP over NAT)的原理是通过某种机制预先得到内部私有IP地址对应在出口NAT上的对外公网IP地址,然后在报文负载中所描述的地址信息就直接填写出口NAT上 的对外IP地址。其最大的优点是无需对现有NAT/防火墙设备做任何改动。局限性在于需要应用程序支持STUN CLIENT的功能,同时STUN并不适合支持TCP连接的穿越。

    (2)TURN即通过Relay方式穿越NAT,也是私网中的SIP终端通过某种机制预先得TURN SeI-ver上的公网地址,私网终端发出的报文都要经过TURN Serve:进行Relay转发。这种方式除了具有STUN方式的优点外,还解决了STUN应用无法穿透对称NAT(SymmetricNAT)以及类似 的Firewall设备的缺陷,局限性在于需要SIP终端支持TURN Client,并增大了包的延迟和丢包的可能性。

    (3)Proxy方式是指通过对私网内用户呼叫的信令和媒体||d时做Relay来实现对NAT/防火墙的穿越。由于不用对运营商和客户端的现有网络设备进行任何改造,具有很强的适应性,组网灵活,可满是NGN初期多样化的组网和用户接入。

    (4)隧道穿越技术的基本思想是通过把需要穿越的数据流封装征某种隧道中,从而绕过NAT/防火墙。它在很大程度上解决了对于不问应用协议需要开发不同穿越策略的办法,但是必须多媒体终端和服务器能够支持隧道,这是一个比较大的限制条件。

    穿越NAT/防火墙方案的实现

    3.1 ICE方式

    交互式连通建立方式ICE(InteractiveConnectivityEstablishment)并非一种新的协议,它不需要对STUN,TURN或RSIP进行扩展就可适用于各种NAT。ICE 是通过综合运用上面某几种协议,使之征最适合的情况下工作,以弥补单独使用其中任何一种所带来的固有缺陷。对于SIP来说,ICE只需要定义一些SDP(Sessionescription Protoc01)附加属性即可,对于别的多媒体信令协议也需要制定一些相应的机制来实现。本文是针对SIP呼叫流程实现ICE的功能。

    这种方式的优点是可以根据通讯双方所处的网络环境,选取适合穿越NAT/防火墙的方式。首先,获取用户所征网络中NAT的类型,如果用户没有设置使用何种方式连接,那么默队首先使用UDP连接,如果一定时间内没有连接成功,接着使用TCP连接,同样如果没有在一定时间内连接成功,那么将采用其他方式如 Upnp、Httptunnel。如果所有穿越方案都失败后,将结果返回给用户,由用户决定是否重试。

    3.2 ICE算法流程

    ICE算法流程分为以F几个过程:

    (1)收集本地传输地址

    会话者从服务器上获得主机上一个物理(或虚拟)接口绑定一个端口的本地传输地址。

    (2)启动STUN

    与传统的STUN不同,ICE用户名和密码可以通过信令协议进行交换。

    (3)确定传输地址的优先级

    优先级反映了UA在该地址上接收媒体流的优先级别,取值范围0到1之间,按照被传输媒体流量来确定。

    (4)构建初始化信息(InitiateMessage)

    初始化消息由一系列媒体流组成,每个媒体流的任意Peer之间实现最人连通可能性的传输地址是由公网L转发服务器(如TURN)提供的地址。

    (5)响应处理

    连通性检查和执行ICE算法中描述的地址收集过程。

    (6)生成接受信息(AcceptMessage)

    若接受则发送Accept消息,其构造过程与InitiateMessage类似。

    (7)接受信息处理

    接受过程需要发起者使用Send命令,由服务器转发至响应者。

    (8)附加ICE过程

    Initiate或Accept消息交换过程结束后,双方可能仍将继续收集传输地址。

3.3 ICE算法实现

    当将ICE算法集成到SIP呼叫过程的时候,流程应该是:(1)SIP终端注册,并且访问STUN(STUNT)服务器,判断NAT/防火墙类型,以及 TCP时三种序列的包的过滤情况。(2)当发起呼叫信息(INVITE)或接收到呼叫信息回应(200 OK)之前根据NAT/防火墙类型进行对RTP进行地址收集(任非对称性NAT/防火墙后需要收集NAT映射地址,在对称性NAT/防火墙后还需要收集 TURN地址)。(3)在RTP的地址端口启动接收线程RSTUN服务程序。(4)发送SIP消息,收集的地址放列SDP消息中的alt属性中。 (5)SIP终端得到通讯双方地址后进行地址配对(将双方地址进行组合),并且根据双方网络情况去掉无效的地址对。(6)根据地址对发

    送STUN check的包,其中STUN消息的用户名,密码从alt属性中得到,标识该地址对。(7)当检测到有效的地址对时(可以发送RTP媒体流的地址),停止接收线程STUN服务),开始传输RTP流。

    本文实现采用WinpcapAPI首先捕获TCP连接的SYN--out包,修改lP包头的TTL的值,用pcap—sendpacket()。然后使该socket调用listen函数。实现过程中对应于ICE收集地址的算法描述为:

 

    类中m_nCandidateID对应地址序号,m_nPriority表示地址优先级,m_CandidateAddr表示地址(IP地址,端口)。实现ICE算法的实体算法描述为:

 

    实现ICE中会话发起者和接收者的步骤基本一样,仅任处理流程中先后次序稍微有些不同,本文中实现的会话流程如图l所示。

 

    图1会话流程

    测试

    以安装了SIP软终端的双方都在Full ConeNATNAT/防火墙后为例,进行实例测试。测试过程:

    (1)将两台PC的IP的配置分别为公网59.64.148.187122和私网10.0.0.5/8l

    (2)从公网中的用户代理向私网内的用户代理呼叫,结果能够建立会话,无明显的延时,话音质量良好;

    (3)从私网内的用户代理向公网中的用户代理呼叫,结果能够建立会话,且话音质量良好;

    通过抓包分析可以确定,使用该算法可以成功地穿越NAT/防火墙设备。

    结论

  ICE方式的优势是显而易见的,它消除了现有的机制的许多脆弱性。例如,传统的STUN有几个脆弱点,其中一个就是发现过程需要客户端自己去判断所在 NAT类型,这实际上不是一个可取的做法。而应用ICE之后,这个发现过程己经不需要了。另一点脆弱性在于STUN,TURN等机制都完全依赖于一个附加 的服务器,而ICE利用服务器分配单边地址的同时,还允许客户端直接相连,因此即使STUN或TRUN服务器中有任何一个失败了,ICE方式仍可让呼叫过 程继续下去。此外,传统的STUN最大的缺陷在于,它不能保证在所有网络拓扑结构中都正常工作,对于TURN或类似转发方式工作的协议来说,由于服务器的 负担过重,很容易出现丢包或者延迟情况。而ICE方式正好提供了一种负载均衡的解决方案,它将转发服务作为优先级最低的服务,从而在最大程度上保证了服务 的可靠性和灵活性。此外,ICE的优势还在于对IPv6的支持。由于广泛的适应能力以及对未来网络的支持,ICE作为一种综合的解决方案将有着非常广阔的 应用前景。

 

7.7  ubuntu安装使用stuntman  

1. 官网地址(要FQ):STUNTMAN:http://www.stunprotocol.org/

从http://www.stunprotocol.org/stunserver-1.2.3.tgz 下载源码

2. 编译依赖:

       sudo apt-get install g++

       sudo apt-get install make

       sudo apt-get install libboost-dev # For Boost

       sudo apt-get install libssl-dev # For OpenSSL

3. 编译 stunserver

       cd stunserver

       sudo make

4. 在stunserver目录下生成下面三个程序

       stunclient, stunserver, stuntestcode

5. run the unittest. Should HAVE NO "FAIL" in the end of any line

       ./stuntestcode

       Result of CTestDataStream: PASS

       Result of CTestReader: PASS

       Result of CTestBuilder: PASS

       Result of CTestIntegrity: PASS

       Result of CTestMessageHandler: PASS

       Result of CTestCmdLineParser: PASS

       Testing detection for DirectMapping

       Testing detection for EndpointIndependentmapping

       Testing detection forAddressDependentMapping

       Testing detection forAddressAndPortDependentMapping

       Testing detection forEndpointIndependentFiltering

       Testing detection forAddressDependentFiltering

       Testing detection forAddressAndPortDependentFiltering

       Result of CTestClientLogic: PASS

       Result of CTestRecvFromEx(IPV4): PASS

       Result of CTestRecvFromEx(IPV6): PASS

       Result of CTestFastHash: PASS

       Result of CTestPolling: PASS

       Result of CTestAtomicHelpers: PASS

6. start stunserver......

       ./stunserver --help     # 使用说明。

       nohup ./stunserver --mode full --primaryinterface eth0--altinterface eth1 &

7. stunclient    检测地址端口映射及NAT类型

用法:./stunclient --mode full --localport7777 stun.sipgate.net

 

NOTE: stuntman只具有stun功能,没有转发功能。支持UDP,TCP。兼容RFC3489。

 

7.8  一个开源的ICE库——libnice介绍

 

libnice是一个ICE实现库。它实现了InteractiveConnectivity Establishment (ICE) standard (RFC 5245) 和 theSession Traversal Utilities for NAT (STUN) standard (RFC 5389)。

官网地址:http://nice.freedesktop.org/wiki/

源码git库地址:http://cgit.collabora.com/git/libnice.git

 

1、下载源码:

git clonegit://git.collabora.co.uk/git/libnice.git

2、编译:

2.1、linux平台下:

它依赖:

 glib >= 2.10
 pkg-config
 gupnp-igd >= 0.1.2 (optional)
 gstreamer-0.10 >= 0.10.0 (optional)
gtk-doc-tools     #autogen.sh需要

 

2.2、编译

k@k-C410:/home/libnice$ ./autogen.sh 

k@k-C410:/home/libnice$ ./configure

k@k-C410:/home/libnice$ make

 

3、生成的程序和库

在nice/.libs目录下生成静态库libnice.a、动态库libnice.so
在example目录下生成三个例子程序。

4、例子程序的使用

k@k-C410:/home/libnice/examples$ ./simple-example0 stunserver.org
Copy this line to remote client:

  Tyyp33oInvKVEn1Lo6LkVVy6P5 1,2013266431,192.168.10.17,47748,host

Enter remote data (single line, no wrapping):
>

红色部分表示提供给对等端协商时的验证用户名、密码、外网地址,以空格分隔。

 

启动二个实例,就可以开始IM对话了:

第一个控制台:

k@k-C410:/home/libnice/examples$ ./simple-example0 stunserver.org
Copy this line to remote client:

  Tyyp 33oInvKVEn1Lo6LkVVy6P5 1,2013266431,192.168.10.17,47748,host

Enter remote data (single line, no wrapping):
> h4p1 7M8uL1928RzeRv6cWRDqG8 1,2013266431,192.168.10.17,47758,host

Negotiation complete: ([192.168.10.17]:47748, [192.168.10.17]:47758)

Send lines to remote (Ctrl-D to quit):
> a


第二个控制台:

k@k-C410:/home/libnice/examples$ ./simple-example 0 stunserver.org
Copy this line to remote client:

  h4p1 7M8uL1928RzeRv6cWRDqG8 1,2013266431,192.168.10.17,47758,host

Enter remote data (single line, no wrapping):
> Tyyp 33oInvKVEn1Lo6LkVVy6P5 1,2013266431,192.168.10.17,47748,host

Negotiation complete: ([192.168.10.17]:47758, [192.168.10.17]:47748)

Send lines to remote (Ctrl-D to quit):
> a

 

存在的问题:在linux下,stun服务器地址不能通过域名解析到IP地址。解决方法是,直接用stun服务器的IP地址。本人已向项目提交了补丁包。

 

7.9  4种利用TURN穿越对称型NAT方案的设计与实现

 

 

7.10 基于ICE方式SIP信令穿透Symmetric_NAT技术研究

webrtc教程