下载地址:
http://code.google.com/p/libjingle/
可以用SVN checkout:
svn checkout http://libjingle.googlecode.com/svn/trunk/ libjingle-read-only
我是用MyJingle来调试的:
http://www.bluehands.de/software/beat/myjingle/
通过MyJingle来调试有如下优点:
1.MyJingle已经帮我们配置好了VC Solution以及Setup打包工程。
2.MyJingle能够连接到Google Talk,这样可以通过Gmail账号连接到google服务器,不用自己搭建XMPP服务器。
3.通过活生生的实例代码,可以编调试,编了解LibJingle的实现方式和XMPP协议。
通过这两天分析Libjingle源代码,了解到如下几点可以借鉴到我们自己的代码中:
1.Signal-Slot机制(这里有相关介绍:http://blog.youkuaiyun.com/smallcraft/article/details/2237802)
sigslot简单说,signal是事件源,而slot是事件处理方法,sigslot机制通过弱耦合的方式把属于两个类对象的事件源和事件处理方法联系起来的。
在LibJIngle里的sigslot的用法如下:
首先定义事件源,即XmppClient类,包含类型为sigslog::signal1<XmppEngine::State>的成员变量SignalStateChange,这里的XmppEngine::State模版类型的功能等会介绍一下。
- class XmppClient
- {
- public:
- sigslot::signal1<XmppEngine::State> SignalStateChange;
- }
然后通过刚才定义的XmppClient对象的成员变量SignalStateChange的connect方法把SignalStateChange事件和TestClient的方法OnstateChange联系起来。SignalStateChange一触发,OnstateChange方法就被立即调用。
- class TestClient: public sigslot::has_slots<>
- {
- TestClient(buzz::XmppClient* xmpp_client):xmpp_client_(xmpp_client)
- {
- xmpp_client_->SignalStateChange.connect(this, &TestClient::OnStateChange);
- }
- void OnStateChange(buzz::XmppEngine::State state)
- {
- if(state == buzz::XmppEngine::STATE_CLOSED)
- {
- printf("Xmpp Closed\r\n");
- cricket::Thread::Current()->Stop();
- }
- else
- {
- switch (state)
- {
- case buzz::XmppEngine::STATE_START:
- printf("Xmpp START\r\n");
- break;
- case buzz::XmppEngine::STATE_OPENING:
- printf("Xmpp OPENING\r\n");
- break;
- case buzz::XmppEngine::STATE_OPEN:
- printf("Xmpp OPENED\r\n");
- InitPresence();
- break;
- }
- }
- }
- };
那么怎么触发事件呢?
很简单,在任何一个地方调用如下语句即可:
在XmppClient类方法里:
- void
- XmppClient::EnsureClosed() {
- if (!d_->signal_closed_) {
- d_->signal_closed_ = true;
- delivering_signal_ = true;
- SignalStateChange(XmppEngine::STATE_CLOSED);
- delivering_signal_ = false;
- }
- }
- void
- XmppClient::Private::OnStateChange(int state) {
- if (state == XmppEngine::STATE_CLOSED) {
- client_->EnsureClosed();
- }
- else {
- client_->SignalStateChange((XmppEngine::State)state);
- }
- client_->Wake();
- }
*此外,在ligjingle的sigslot里最多可支持8个参数。
2.处理同步\异步的多线程处理机制。
如果阅读过Chrome的源代码,我们可以得知Chrome的线程模型全部都是异步的command模式,随处可见PostTask和PostDelayedTask等方法。
在Ligjingle,不仅支持异步模式,而且还支持同步模式,如同windows的窗口消息处理机制,它既有PostMessage,又有SendMessage。
3.当处理网络相关的操作时,大量使用适配器模式。
在计算机编程中,适配器模式(有时候也称包装样式或者包装)将一个类的接口适配成用户所期待的。一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。ligjingle需要支持多平台,windows和Linux的套接口使用方法大同小异,但这个小小的差异会导致两者的代码无法彼此适应,所以需要通过adapter来包裹这一层平台相关的代码。除此之外,当支持SSL时,对于windows,libjingle采用的是SChannel ,linux的是OpenSSL。
4.打洞技术。
如下图所示,在NAT内部的计算机之间通信或者具有防火墙的客户端之间通信需要通过RelayServer来打洞。
LibJingle通过XMPP协议很好的支持了打洞,以及P2P传输。
通过XMPP协议,LibJingle打洞,我们可以:
1.多用户语音。
2.多用户视频。
3.多用户文件共享。
4.一对一远程桌面协助。
5.多用户在线音乐流同步。
6.还有很多我们可以做到。
5.了解XMPP以及Gtalk的实现方式
什么事XMPP?(http://baike.baidu.com/view/189676.htm)
XMPP是一种基于XML的协议,它继承了在XML环境中灵活的发展性。因此,基于XMPP的应用具有超强的可扩展性。经过扩展以后的XMPP可以通过发送扩展的信息来处理用户的需求,以及在XMPP的顶端建立如内容发布系统和基于地址的服务等应用程 序。而且,XMPP包含了针对服务器端的软件协议,使之能与另一个进行通话,这使得开发者更容易建立客户应用程序或给一个配好系统添加功能。
本文主要总结至libjingle源码和官方文章:http://code.google.com/apis/talk/libjingle/libjingle_applications.html
ligjingle的总体架构如下图:
1.Application模块
Ligjingle的应用程序首先调用XMPP Messaging Component的XmppClient对象进行登录,然后做一些message,iq,presence等request/respond操作。
其次,每个application可能包含一个或多个session client用来做P2P操作,比如远程协助,视频会议,音频连接,文件共享等等。
2.XMPP Messaging Component.模块
此模块主要由三个部分组成:XmppClient,LoginHandler和Xmpp Helper Task.此模块主要做相当于一个peer的防火墙的功能,连接服务器和客户端,负责发送所有本地的stanza请求(即XMPP协议内容),并负责接收服务器的stanza请求,并分发到各个Helper Task里。
- XmppClient主要是代理登录,发送stanza,接收stanza。之所以说是代理,是因为真正发送,接收的消息都是通过XmppEngine来实现的。
- XmppEngine能注册多个XmppStanzaHandler回调,然后所有从服务器接收的Stanza都转发到已绑定的XmppStanzaHandler进行过滤,而实际上是回调XmppTask对象。
- XmppStanzaHandler类定义如下:
- //! Callback to deliver stanzas to an Xmpp application module.
- //! Register via XmppEngine.SetDefaultSessionHandler or via
- //! XmppEngine.AddSessionHAndler.
- class XmppStanzaHandler {
- public:
- virtual ~XmppStanzaHandler() {}
- //! Process the given stanza.
- //! The handler must return true if it has handled the stanza.
- //! A false return value causes the stanza to be passed on to
- //! the next registered handler.
- virtual bool HandleStanza(const XmlElement * stanza) = 0;
- };
- 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里:
- class PresencePushTask : public XmppTask {
- public:
- PresencePushTask(XmppTaskParentInterface* parent, CallClient* client)
- : XmppTask(parent, XmppEngine::HL_TYPE),
- client_(client) {}
- virtual int ProcessStart();
- sigslot::signal1<const Status&> SignalStatusUpdate;
- sigslot::signal1<const Jid&> SignalMucJoined;
- sigslot::signal2<const Jid&, int> SignalMucLeft;
- sigslot::signal2<const Jid&, const MucStatus&> SignalMucStatusUpdate;
- protected:
- virtual bool HandleStanza(const XmlElement * stanza);
- void HandlePresence(const Jid& from, const XmlElement * stanza);
- void HandleMucPresence(buzz::Muc* muc,
- const Jid& from, const XmlElement * stanza);
- static void FillStatus(const Jid& from, const XmlElement * stanza,
- Status* status);
- static void FillMucStatus(const Jid& from, const XmlElement * stanza,
- MucStatus* status);
- private:
- CallClient* client_;
- };
- 这里PresencePushTask类,通过HandleStanza过滤所有presence相关的stanza并在ProcessStart里处理所有来自服务器的用户状态更新消息。
- LoginHandler部分是由XmppPump来负责的。主要调用XmppClient的connect和disconnect方法建立、断开连接,监听SignalStateChange事件来获取连接信息,类型为STATE_OPENED的事件表示连接成功。
3.Session Logic and 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方法来发送数据。
- class TransportChannel: public sigslot::has_slots<> {
- public:
- //......
- // Attempts to send the given packet. The return value is < 0 on failure.
- virtual int SendPacket(const char *data, size_t len) = 0;
- // Signalled each time a packet is received on this channel.
- sigslot::signal3<TransportChannel*, const char*, size_t> SignalReadPacket;
- //......
- };
4.Peer to peer Component模块。
此模块才是libjingle核心,libjingle项目的初衷也是能够把模块设计得完美,使得所有需要通过P2P传输数据的应用层调用libjingle时,不用担心数据传输的稳定性,可靠性,高效性。
- 刚才上面提到,当服务器发送stanza时XmppEngine把Stanza发送到XmppTask过滤,在这个模块,SessionManagerTask代理SessionManager过滤session相关的stanza,并转发到SessionManager对象,如下:
- class SessionManagerTask : public buzz::XmppTask {
- public:
- ......
- virtual int ProcessStart() {
- const buzz::XmlElement *stanza = NextStanza();
- if (stanza == NULL)
- return STATE_BLOCKED;
- session_manager_->OnIncomingMessage(stanza);
- return STATE_START;
- }
- protected:
- virtual bool HandleStanza(const buzz::XmlElement *stanza) {
- if (!session_manager_->IsSessionMessage(stanza))
- return false;
- QueueStanza(stanza);
- return true;
- }
- } // namespace cricket
- SessionManager类在这里起到连接上述3个模块的桥梁作用。
- 当上层调用SessionManager创建的Session对象的CreateChannel时,实际上是调用P2PTransport的CreateChannel方法。
- 上层通过P2PTransport创建P2pTransportChannel的类的。
- P2pTransportChannel继承自TransportChannel,并创建多个不同的Connection,每个Connection代表一个TCP或者UDP或者SSL连接。上层传输数据最终是调到P2pTransportChannel的相关方法,当发送,接收数据时,P2pTransportChannel选择表现最好的Connection进行传输。
5.其他
LigJingle提供了很多接口供我们继承,用于特定的个性化Session,同时也提供了不少实例(如pcp,login,call)让调用者更容易的理解框架思路。当需要着手研究libjingle时,如果能够充分的利用已成的实例,对于缩短熟悉时间,很有帮助。