客户端架构设计的简单总结

咱们知道,客户端是相对服务端而言的,客户端程序相对普通应用程序,主要是增长了网络通信功能。在这个移动和云存储的年代,大部分终端应用程序都有网络通信功能, 因此均可以称为客户端。常见的客户端如浏览器,IM客户端, 网络会议客户端,邮件客户端,微博和微信客户端等...

经过观察,咱们会发现全部的客户端基本是大同小异,都会包括一些相同的功能组件, 下面简单例举下:

通信协议层

既然客户端都有网络功能,就会涉及到通信方式和数据格式以及协议, 这三者不是彻底独立,而是有机统一的。

首先说通信方式,常见的通信方式包括TCP,UDP, P2P和http(s), 不少时候咱们不会用单一的通信方式,而是多种通信方式的结合。好比说TCP端口被封,走不通时,咱们会转成尝试http(s)。IM中聊天文本走的是TCP, 由服务器转发,可是2个客户端之间的文件传输咱们可能走的又是P2P了, 多我的之间的语音聊天, 咱们走的又是UDP了。

其次说数据格式,常见的数据格式包括二进制编码,开源序列化协议和文本格式。
二进制通常是自定义的私有格式,一般对数值,咱们会转成大头端,对字符串咱们会用UTF8 编码,由于没有冗余数据,它的优势是不会浪费带宽;主要缺点是有硬编码的味道,很差扩充。
开源序列化协议这里主要是指google的protocal buffer,  如今不少公司都在用, 不少人基于它开发了本身的RPC框架。主要优势是数据小,使用简单而高效。
文本格式主要是指xml和json. 相对来讲xml比较清晰和容易扩充,可是冗余数据比较多。json借助javascript对它语言层次的支持,感受主要是前端人员使用的比较多。

最后再说协议,  协议和咱们的应用相关联。好比邮件客户端,固然是走SMTP和POP3了; IM客户端的话,通常走XMPP了;  网络会议的话,能够走ITU的T.120协议, 也能够RFC 6501定义的XCON, 信令走SIP, 数据走RTP等。

通讯协议层是整个客户端网络事件驱动的引擎,它可能会比较简单,也可能会很复杂。若是是基于XMPP的IM, 它可能会比较简单,由于基本上只须要一层文本协议的封包和解包就能够了。 当若是是基于T.120网络会议客户端,就会比较复杂,它数据包走的自定义的二机制格式,按照T.120协议的建议, 在通信协议层又分了3层:TP, MCS和GCC。TP层主要封装数据传输的方式, 可让上层无差异的区分TCP和http(s)。 MCS层主要提供多点传输功能, 它抽象出通道(channel)这个概念, 让不一样session的数据进行逻辑隔离, 上层用户能够同时加入不一样的通道来进行一对一和一对多的数据收发,而且通道中的数据有不一样的优先级, 还有令牌这个机制。咱们也能够在MCS层对数据进行加密和压缩, 还能够对上层的大数据包进行切包等。 GCC层主要封装会议的最基本逻辑,好比建立会议和加入session数据包的格式封装等, 让上层能够经过API调用而不用关心协议要求的数据包格式。不一样的数据包会工做在不一样的层次, 好比心跳包可能在底层TP层就被拦截了,它不要再往上层发,由于上面不用关心这个; 而有些数据包,则须要从底层往上层按照整个协议栈层层转发,固然每层都会剥离掉本身的协议头, 直至上层用户数据到达它的最终用户。

总之,通信协议层封装了客户端和服务端的通信方式及协议格式, 让上层用户不用关心底层的通讯机制, 而只关注应用的接口事件。理论上咱们能够在上层应用不作大调整的前提下,直接将网络会议客户端中的T.120协议成基于SIP的XCON。

功能组件

一个客户端程序一般是由不少功能模块组成,模块按功能来讲能够分为基础组件和应用组件。

基础组件为应用组件提供的基础设施,基础组件是能够在不一样的项目中重复使用的(好比界面控件库,2D渲染引擎Skia, 跨平台的网络和线程库等)。 
应用组件一般和咱们当前的特定应用程序相关,好比咱们的网络会议客户端包含的桌面共享模块, 文档共享模块,视频音频模块,文本聊天模块等。

应用模块自己分为带界面和无界面两种状况, 带界面的状况下咱们一般会给组件提供一个容器窗口的句柄, 让组件本身在内部组织本身的界面。这种带界面的组件一般会为逻辑和界面的分离带来麻烦,在Window上实现一些半透明和动画效果也很难。 好比咱们想提供跨平台的SDK来封装逻辑,这时咱们会更倾向让应用组件采用无界面的模式,组件在跨平台层只封装逻辑和提供数据, 而把数据发到最上层界面层后再统一处理。

对功能组件咱们的设计原则是尽可能保持独立和可复用,最好能以仿COM方式动态升级而不用从新编译, 另外组件之间要保持层次性,避免双向或是循环依赖。

数据存储

客户端自己是处理和收发网络数据, 这里就涉及到对这些数据如何组织和存储的问题。这个一般会根据客户端的类型采用不一样的到处理方式:
对于永久存储的数据,固然是保存成文件或是存入数据库,文件如xml, 数据库如ACCESS,  SQL server, mysql等。
临时数据固然是存入内存了,根据须要采用不一样的数据结构, 组织格式如array,list, map, hashmap等。
还有一种是常见的数据保存方式是内存数据库,最多见是SQLite了, 内存数据库既能高效的分类保存大量数据, 又能够直接用基于SQL语句进行查询和处理, 好比foxmail客户端就是用SQLite存储的邮件信息。
还有一种是跨进程共享的数据,咱们通常固然是内存映射文件了。好比咱们有一个实时显示log的工具, 咱们一般会在对方应用程序里分配共享内存的内存映射文件,全部的log都写到里面,而后在log工具程序里读取和显示。
固然还有一些数据可能存到网上去的, 常见的好比咱们的云笔记, 这时数据分服务端数据和本地cache。

对于数据存储咱们的设计原则是根据须要,选择简单高效的方式。好比咱们在设计网络会议客户端时曾讨论要不要引入SQLite, 理想状况是引入内存数据库后各个组件和上层应用的数据均可以统一存储,采用数据驱动的方式,能够很方便的跟踪整个客户端的运行状况。但后来发现这种设计会把原本各自独立的组件经过数据库耦合在了一块儿,并且各组件的数据格式自己都很不要同样, 很难统一存储, 另外不少数据实际也只是临时数据, 不必把简单的事情作复杂了。


客户端框架

客户端框架通常有两个做用:一是把全部的功能组件组织起来,进行统一的管理和展示; 二是实现整个客户端的主界面。客户端框架在协调各个组件时, 要注意避免让组件之间产生双向依赖, 而是应该让组件把事件通知给框架后由框架统一协调和处理, 因此客户端框架一般是整个客户端逻辑最复杂的部分。 一个好的客户端框架,一般会采用插件方式设计,能够动态插拔须要的组件。最好是能够根据服务端的配置, 动态下载和更新所须要的插件。

对于客户端框架, 咱们的设计原则是低耦合和可扩展。基本上全部下层组件的改动都会影响到咱们的客户端框架,客户的不少新需求和新组件也会致使框架产生坏味道, 这里咱们设计时就要考虑如何让客户端框架能及时的适应这些变化。


界面库

客户端确定会有界面,在Windows平台上,如今的界面大体分为如下几类:
基于Windows原始窗口控件句柄的C++ native 客户端, 基于.net的winform客户端,基于.net的WPF客户端,基于C++的DirectUI客户端, 以嵌入浏览器(如webkit)方式实现的客户端, 还有一类是基于Xaml的Metro客户端。

对于企业级大型安装应用,主要考虑的是开发效率,因此客户端仍是以.net为主; 可是对于互联网企业, 更多的是要求客户端精简而高效, 因此仍是以C++为主。 这里就设及到C++的两种界面库, 一种是基于windows窗口句柄和控件自绘机制的界面库,还有一中是基于DirectUI的界面库, 如今大一点的公司基本上都有本身的DirectUI界面库。基于修改Webkit方式实现的界面库能够经过Canvas和SVG动画等方式实现一些很炫的效果, 可是它和标准程序的用户体验仍是有必定差距:一来web实现的控件跟Windows标准控件有很大不一样, 二来web的流式布局和应用程序的网格布局也不同。

对于界面,我的以为这个东西变化实在太快了,因此我以为应该尽可能用界面和逻辑相分离的方式组织代码。

总结
对于客户端架构设计,我的以为最大的原则就分层设计, 每层都封装一个概念并保持独立, 同时根据依赖倒置的原则, 站在上层客户的角度提供接口。软件工程里面的一条黄金定律:“任何问题均可以经过增长一个间接层来解决。
相关文章
相关标签/搜索