TeamTalk客户端源码分析六

TeamTalk客户端源码分析六

  • 一,引用计数和锁
  • 二,异步操作
  • 三,数据序列化和反序列化

     上篇文章介绍了httpclient模块,TeamTalk中还有一个网络模块network,它也是一个单独的动态库,内部只支持TCP通信,提供了网络回报异步通知的机制,并且通过Google Protocol Buffer(推荐学习博文Google Protocol Buffer 的使用和原理)对传输数据进行序列化和反序列化操作,减小数据的大小。本章对network的实现进行具体分析。

一,引用计数和锁

TeamTalk客户端源码分析六_第1张图片
     引用计数和锁的实现在common过滤器的util.h文件中,我们先看引用计数基类CRefObject:
TeamTalk客户端源码分析六_第2张图片
     引用计数的原理大概说下:就是类中维持一个变量,每次AddRef()时,该变量+1;每次ReleaseRef()时,该变量-1,当计数为0时,delete this。这里还多了一个借口SetLock用来使这个引用计数基类支持多线程访问,由继承类重写或外部调用。
     util中公用函数就不多介绍,我们直接看Lock.h中的锁的实现。我们都知道,在多线程操作中,如果只是单纯控制访问同步,使用用户态的锁即可,比如windows下的CRITICAL_SECTION,以及linux下的pthread_mutex_t。
     在Lock.h中实现了三个类:CFastLock,CLock和CAutoLock。CFastLock根据平台环境的不同定义不同的锁对象,构造函数中进行锁的初始化,析构中进行锁的释放:
TeamTalk客户端源码分析六_第3张图片
     CLock在CFastLock的基础上新增两个接口lock和unlock进行加锁和解锁操作:
TeamTalk客户端源码分析六_第4张图片
     CAutoLock顾名思义就是一个智能锁,原理也跟我们平常接触到的智能锁一样:通过控制类对象的作用域,在构造函数中调用lock,析构函数中调用unlock,来实现锁的自动加锁和释放功能。
TeamTalk客户端源码分析六_第5张图片
     yaolog.h和yaolog.cpp就是开源的yaolog日志库了,此处就不再介绍了,毕竟每个人使用的日志库不一样,适合自己的就是最好的,原理都大同小异。

二,异步操作

     异步操作一般用在比较耗时或者不确定数据能不能立即回来的场景中,比如网络通信,在前面的章节中也介绍过Module中的回调机制,是通过创建了一个消息窗口进行消息中转,最终又发送到了主线程的窗口过程中。但是在network模块中,异步回调操作是通过两个线程来实现的:线程1只是把请求丢到队列中,线程2从队列中取回请求再去执行(生产者-消费者设计模式),线程2执行完毕后,还是通过消息窗口同步回传到UI线程中。
     IOperation是一个虚基类,Operation继承自它,依旧有一个纯虚函数processOpertion,各个业务模块再继承自Operation,并在processOpertion实现具体的功能。
TeamTalk客户端源码分析六_第6张图片
     OperationManager是一个Operation管理类,包括实时的和延时的(延时的暂未实现),此类是单实例。注意:在TeamTalk中的单实例都是多线程不安全的,只能在单线程中操作(多线程的请参见多线程下的单实例实现)。
     我们看一下OperationManager的主要成员函数和成员变量。
TeamTalk客户端源码分析六_第7张图片
     提供了两个接口startOperation和startOperationWithLambda给外部使用,新增操作请求,在startOperationWithLambda内部最终还是调用的startOperation(LambdaOperation继承Operation并重写了processOpertion,内部调用构造函数保存下来的std::function operationRun)
TeamTalk客户端源码分析六_第8张图片
     startOperation内部就是把operation入队列,并通过条件变量进行通知(std::condition_variable条件变量和std::unique_lock std::mutex结合使用,多用在消费者模式中,参见我的另一篇博文C++消费者模式多种通信方式介绍)
TeamTalk客户端源码分析六_第9张图片
     startup中是一个消费者线程(在主程序的InitInstance中调用的)
TeamTalk客户端源码分析六_第10张图片
     使用C11新语法std::thread创建线程,后面就是一个Lambda表达式(Lambda表达式参见我的另一篇博文C++仿函数和Lambda表达式),同时这里也用到了两个锁std::unique_lock 和std::lock_guard,它们俩本质没有区别,都是用来做自动锁的,但是std::unique_lock可以和条件变量std::condition_variable结合使用,来完成多线程的阻塞等待工作。当链表为空时,不进行无意义的占用CPU的while循环操作,而是让当前线程进入睡眠状态,等待生产线程中的条件变量的触发。这个巧妙的方法在实际项目中很有实用性。
     OperationManager还支持根据name将operation从任务队列中删除:
TeamTalk客户端源码分析六_第11张图片

三,数据序列化和反序列化

     首先介绍一下network中的两个Buffer类,CSimpleBuffer和CByteStream,后者中也是调用前者来实现的。
TeamTalk客户端源码分析六_第12张图片
     在CSimpleBuffer中,数据的存储主要放在m_buffer中,m_alloc_size是分配的大小,m_write_offset是实际写的大小。提供了一个Write接口用来写数据,一个Read接口读数据,每次Read操作会减少之前Write的大小,即m_write_offset减小。当分配的空间不足时,会调用Extend函数来进行空间扩容,将缓冲区追加len+len/4大小。
TeamTalk客户端源码分析六_第13张图片
     CByteStream就是Byte形式的CSimpleBuffer,内部主要实现和是调用CSimpleBuffer接口实现的,以及一些位移操作。

     数据的序列化和反序列的具体封装是在TTPBHeader.h和TTPBHeader.cpp中实现的。
TeamTalk客户端源码分析六_第14张图片
     它的private成员变量可以简单理解为一个结构体,存储的是通信信息,包括长度,版本号,标志信息,模块id,命令id,包序号以及一个保留字段。每次发送网络请求的时候,然后调用成员函数填充各个字段,其次通过getSerializeBuffer()接口获取序列化后的字符串,最后用ProtoBuf将该字符串进行压缩处理,再进行网络传输。unSerialize(byte* headerBuff,UInt16 len)提供反序列操作,获取原始数据。具体的实现可以看TTPBHeader.cpp,比较简单,此处不再复述。
     下一章我们介绍network中socket通信的实现。

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