TeamTalk客户端源码分析一

TeamTalk客户端源码分析一

  • 一,Subject被观察者
  • 二,Observer观察者
  • 三,事件的通知
  • 四,总结及业务实例分析

     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文件,如图所示:
TeamTalk客户端源码分析一_第1张图片
     ModuleSubject是一个subject类,是一个被观察者,它有两个成员变量,一个是观察者数组,一个是锁。再就是公有的方法,包括addObserver(添加观察者),removeObserver(移除观察者),以及通知观察者的一系列方法。
     大家肯定也注意到这个类是一个final不可继承的,这不符合常规的观察者设计模式,所以这里还用到了另外一个中间类来调用ModuleSubject中的实现,
TeamTalk客户端源码分析一_第2张图片
     ModuleBase类中就只有一个成员变量m_pModuleSubject,就是我们上面说的被观察者类,在ModuleBase类中所有的函数都只是中转去调用ModuleSubject的实现。外部需要实现被观察者类(即观察对象),直接继承ModuleBase即可。

二,Observer观察者

     被观察者讲完,下面就是观察者,它的实现在文件ModuleObserver.h中
TeamTalk客户端源码分析一_第3张图片
     这个文件中也有两个类,一个是ModuleObserverCtx类,也就是ModuleSubject中的成员变量;另一个是MKOEvent_Impl,它继承了两个虚接口,一个process()来处理事务逻辑,一个release()释放当前指针。这两个类肯定是实现观察者作用的,但是具体怎么实现呢?我们先看看被观察者如何将通知发送到观察者这里来的。我们再回到ModuleSubject.cpp文件,
TeamTalk客户端源码分析一_第4张图片
     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线程和工作线程的切换。
TeamTalk客户端源码分析一_第5张图片
     在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(this), reinterpret_cast(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(wparam)->_processEvent(reinterpret_cast(lparam), TRUE);
		break;
	case WM_TIMER:
		reinterpret_cast(wparam)->_processTimer();
		break;
	default:
		break;
	}
	return ::DefWindowProc(hWnd, message, wparam, lparam);
}

     然后在_processEvent中调用类IEvent的虚函数来处理事务逻辑,因为上面传入的是MKOEvent_Impl,那么调用的就是MKOEvent_Impl::process();
TeamTalk客户端源码分析一_第6张图片
     MKOEvent_Impl::process()中再去遍历当前所有的观察者,调用每一个观察者的回调函数来触发。至此完成从监听到触发的整个过程。
TeamTalk客户端源码分析一_第7张图片

四,总结及业务实例分析

     总结一下整个过程:
1,不同的业务各自继承一个ModuleBase
2,在各个需要用到该业务的类中去将当前类绑定到该业务的观察者中。
3,在触发的地方调用asynNotifyObserver
     以代码中的一处实际应用来举例:当在联系人列表中双击打开一个新的会话时,主窗口需要清除掉对应联系人消息的未读数目,并开启一个新的会话窗口。
     主窗口类的构造函数中添加绑定观察者和回调类。
TeamTalk客户端源码分析一_第8张图片
     双击列表事件中,发出通知
TeamTalk客户端源码分析一_第9张图片
     主窗口类中响应回调消息,根据keyId过滤,处理对应的业务逻辑:打开新的对话框,清除未读消息等等。
TeamTalk客户端源码分析一_第10张图片

你可能感兴趣的:(TeamTalk客户端源码分析)