TeamTalk客户端源码分析一
TeamTalk的PC客户端是c++语言实现的,整个代码由duilib,gifsmiley,httpclient,libogg,modules等工程构成。
1)duilib,界面库
2)gifsmiley,表情图需要用的库
3)httpclient,纯SOCKET封装实现的HTTP请求库
4)libogg,日志库
5)modules,各个模块类封装的动态库
此系列文章先从基础模块开始讲解,比如回调机制,HTTP封装,数据库封装,网络请求等等,再到界面的布局实现。
首先介绍基础模块:回调机制,回调机制的原理是用观察者模式来实现的。
一,Subject被观察者
打开Modules工程中ModuleSubject.h文件,如图所示:
ModuleSubject是一个subject类,是一个被观察者,它有两个成员变量,一个是观察者数组,一个是锁。再就是公有的方法,包括addObserver(添加观察者),removeObserver(移除观察者),以及通知观察者的一系列方法。
大家肯定也注意到这个类是一个final不可继承的,这不符合常规的观察者设计模式,所以这里还用到了另外一个中间类来调用ModuleSubject中的实现,
ModuleBase类中就只有一个成员变量m_pModuleSubject,就是我们上面说的被观察者类,在ModuleBase类中所有的函数都只是中转去调用ModuleSubject的实现。外部需要实现被观察者类(即观察对象),直接继承ModuleBase即可。
二,Observer观察者
被观察者讲完,下面就是观察者,它的实现在文件ModuleObserver.h中
这个文件中也有两个类,一个是ModuleObserverCtx类,也就是ModuleSubject中的成员变量;另一个是MKOEvent_Impl,它继承了两个虚接口,一个process()来处理事务逻辑,一个release()释放当前指针。这两个类肯定是实现观察者作用的,但是具体怎么实现呢?我们先看看被观察者如何将通知发送到观察者这里来的。我们再回到ModuleSubject.cpp文件,
addObserver时创建了一个ModuleObserverCtx对象,并且把pObserObject(观察者)和handle(仿函数,也即是回调函数)填充到该对象中,加入数组。
三,事件的通知
最后我们再看通知函数
void ModuleSubject::_asynNotifyObserver(IN const std::string& keyId, IN MKOEvent_Impl* pEvent)
{
pEvent->m_keyId = keyId;
module::getEventManager()->asynFireUIEvent(pEvent);
}
很明显它中间又调用了另一个接口类UIEventManager,这就是一个消息窗口管理类,为什么要用到一个消息窗口呢?这是因为,本程序中实现的观察者模式是异步的,所以用了一个消息窗口来进行中转。该消息窗口的创建是在startup函数里执行的,而startup函数是在程序的主线程也就是UI线程中调用的,那么这个用于中转的消息窗口也在主线程中,通过它将工作线程的回调丢回到UI线程去处理了,完成了UI线程和工作线程的切换。
在ModuleSubject::_asynNotifyObserver中通过UIEventManager来触发,具体实现如下:
module::IMCoreErrorCode UIEventManager::asynFireUIEvent(IN const IEvent* const pEvent)
{
assert(m_hWnd);
assert(pEvent);
if (0 == m_hWnd || 0 == pEvent)
return IMCORE_ARGUMENT_ERROR;
if (FALSE == ::PostMessage(m_hWnd, UI_EVENT_MSG, reinterpret_cast<WPARAM>(this), reinterpret_cast<WPARAM>(pEvent)))
return IMCORE_WORK_POSTMESSAGE_ERROR;
return IMCORE_OK;
}
通过postmessage发送消息后,直接返回,不阻塞当前线程。然后在窗口过程函数中处理UI_EVENT_MSG消息。注意这里它把MKOEvent_Impl*参数传进去了。
LRESULT _stdcall UIEventManager::_WindowProc(HWND hWnd
, UINT message
, WPARAM wparam
, LPARAM lparam)
{
switch (message)
{
case UI_EVENT_MSG:
reinterpret_cast<UIEventManager*>(wparam)->_processEvent(reinterpret_cast<IEvent*>(lparam), TRUE);
break;
case WM_TIMER:
reinterpret_cast<UIEventManager*>(wparam)->_processTimer();
break;
default:
break;
}
return ::DefWindowProc(hWnd, message, wparam, lparam);
}
然后在_processEvent中调用类IEvent的虚函数来处理事务逻辑,因为上面传入的是MKOEvent_Impl,那么调用的就是MKOEvent_Impl::process();
MKOEvent_Impl::process()中再去遍历当前所有的观察者,调用每一个观察者的回调函数来触发。至此完成从监听到触发的整个过程。
四,总结及业务实例分析
总结一下整个过程:
1,不同的业务各自继承一个ModuleBase
2,在各个需要用到该业务的类中去将当前类绑定到该业务的观察者中。
3,在触发的地方调用asynNotifyObserver
以代码中的一处实际应用来举例:当在联系人列表中双击打开一个新的会话时,主窗口需要清除掉对应联系人消息的未读数目,并开启一个新的会话窗口。
主窗口类的构造函数中添加绑定观察者和回调类。
双击列表事件中,发出通知
主窗口类中响应回调消息,根据keyId过滤,处理对应的业务逻辑:打开新的对话框,清除未读消息等等。