一、Funambol ds 是用于客户端与云端的数据同步, 包含通讯录, 日历行程, 视频及文件等. 这里主要研究通讯录的同步实现机制, 通讯录的同步应用比较广泛, 对DS(Data Synchronization)的同步具备代表性. 之前讲到的funambol dm, 两者都是基于SyncML, 但是DM是属于设备信息和应用升级包的同步. 两者的数据载体类型不一样, DS更着重于数据的完整性与实时性, 同时, 客户端与云端的增删改操作给数据带来更多的变化性, 要求具备完善的数据变化检测机制, 实现起来相比DM会更复杂些.
二、DS同步类型包含双向同步, 慢同步, 单边客户端同步, 单边服务端同步, 客户端刷新同步,服务端刷新同步. 所谓刷新同步, 其实是把所有数据进行同步, 不去检测区分增删改的数据. 应用最多的是双向同步, 也是实现起来最为复杂的一种. 双向同步要求将客户端的操作所变化的数据更新到云端, 同时, 云端也要将变化的数据传递至客户端更新, 可以作交集, 也可以作并集, 其中两端可能会产生数据冲突, 具体遵照实际业务规则, 目的是保持两端数据的一致性, 完整性.
三、双向同步的简要的交互流程, 请看图1:
从发送请求到结束会话, 主要做了两件事情, 第一, 是把客户端的数据更新至服务端; 第二, 是把云端的数据更新至客户端.
四、具体的协议传输流程, 请看图2:
整个流程主要分为四个步骤:
1. 初始化: 彼此握手, 建立会话, 如果之前没有同步, 第一次进行交互时, 客户端会发送自己的设备信息, 服务端也会发送自己所能支持提供的服务信息, 这些信息包含系统版本, 固件版本, 机型, 同步数据类型, 协议版本等等. 同时, 会在这里判断用户是否合法, 标识本次同步的数据类型.
2. 同步客户端数据: 客户端将增删改数据以VCARD格式传递给云端, funambol ds支持vcard 和sifc两种通讯录格式, 早期使用sifc, 目前vcard应用最为广泛. 服务端会进行检测匹配, 保存数据, 并对每条数据的执行结果反馈给客户端, 如果某条数据失败, 是不会影响到其他数据的同步.
3. 同步服务端数据: 服务端会有相应的检测机制, 去获取需要更新到客户端的数据. 客户端接收数据执行更新后, 同样, 需要将每条数据的执行结果传递给服务端.
4. 结束会话: 两端数据交互完成后, 发送标识, 结束本次会话. 日志记录, 结果统计等操作放在这里会比较合适.
如果有兴趣, 想要更深入的了解, 可以研究下官方的文档:
OMA-SyncML-DataSyncProtocol.pdf
funambol-push-design.odt
funambol-ds-service-design-document.odt
一、 同步包含单个客户端设备的数据同步, 云端的数据同步以及多客户端之间的数据同步.在实际操作中, 多个客户端之间数据会不停地操作变化, 以及云端数据的随时更新, 如何能保持这些数据的一致性, 不至于产生错乱呢? 乍看有点棘手, 这里我们需要去仔细分析下, 把各种情况考虑清楚.
无论是单个客户端或是多个客户端的同步, 是单个账号多设备还是单设备多个账号的数据同步, 都可简化分为客户端和云端的同步, 区别只是同步的时间和次数的差异, 把这两端的关系分析处理好, 才能保持数据同步的一致性.
二、funambol的同步流程:
1. funambol的同步矩阵图表
Source A 代表的是客户端的源数据
Source B 代表的是服务端的源数据
A: 代表数据替换为A方客户端; B代表数据替换为B方服务端;
D:代表删除; C:代表冲突; X: 代表不做任何操作
图1
这是funambol的同步处理规则, 基本是考虑到了所有情况, 但是在实际中会发现, 仅仅按照上面的矩阵图处理是不够的, 如果存在冲突数据, 该怎么去处理, 是该遵循怎样的规则, 下面会分析, 还是先继续看完funambol的同步处理流程.
2.同步修改检测模型
图2
同步当中, 必然会存在修改的数据, 如果两端发生修改, 该怎么去处理, 这是funambol的修改检测机制:
A – 我们把它可以看作是客户端的数据.
B – 把它看作是服务端的数据.
Am – 客户端修改的数据.
Bm – 服务端修改的数据.
AmBm – 客户端和服务端都已修改的数据.
(A-Am)Bm – 在客户端没有修改, 但在服务端修改的数据.
Am(B-Bm) – 在服务端没有修改, 但在客户端修改的数据.
考虑得比较周全, 源码也是按照此图先做数据分析, 然后再执行更新. 这样会比较清晰, 把数据归类, 确定是在哪边执行更新操作.
3. 数据关联映射
上面讲到的是funambol同步的主要处理流程, 大致了解后, 再来看看, 还有个重要的问题, 客户端的数据和云端的数据是怎么关联起来的? 是把所有数据抛过去, 再把所有属性一一比对标识? 还是可以通过唯一标识去关联呢? 如果使用唯一标识, 该怎样去建立, 能否正确匹配? 每种方式都有利有弊, 先来看看funambol的数据关联机制是如何实现的:
图3
LUID (Locally Unique Identifier): 代表的是客户端数据唯一标识.
GUID (Global Unique Identifier): 代表的服务端数据唯一标识.
如图3所示, 通过LUID和GUID的映射数据表, 我们就能够正确的找到关联的数据. LUID由客户端生成, GUID由服务端所生成. 客户端生成的标识可以用自增长的ID吗? 服务端是可以, 但客户端不行, 因为云端只有一个, 而客户端会有多个. Funambol的客户端是采用时间戳作为唯一标识的. 每种方式都有其利弊, 这种设计方式也存在一个缺陷, 当客户端重装系统, 从SIM卡导入联系人, 再进行同步时, 生成新的时间戳标识, 就无法关联到以前同步的数据, 会造成重复. 当然, 这种操作方式算是特殊情况, 可以包容. 解决的方法可以在客户端增加全属性比对的合并功能.
4. 同步时间标记
以上讲的是数据的操作以及关联匹配处理, 这是非常核心的内容, 但是仅有这些还不够, 在实际操作中, 如何能保证每次同步能够获取正确的数据? 同步后的数据下次同步不会再重复呢? 不同客户端同步的数据, 彼此又是如何都能检测得到呢? 要解决这些问题, 还需要有个时间标记, 数据同步协议里面已经考虑到这些, 提供了两个时间戳标记, 上次同步时间和当前同步时间:
:
图4
参考图4, 客户端和服务端进行同步时, 会先由客户端发送时间标记, 服务端进行检测, 如果上次同步时间相符, 则会根据当前同步时间去匹配同步数据, 传送给客户端; 如果上次同步时间不符合, 则服务端会传送508指令给客户端, 要求客户端进行全同步. 存在两个流程走向, 这样是为了确保差异化同步时, 数据的正确性.
三、实际同步业务场景
上面图1的同步矩阵图是funambol的建立的一个框架, 在这个基础上, 可以进行改造, 去适应我们实际的业务应用.
下面的图5基本涵盖了所有业务场景, 同步结果的前提是以客户端的数据为最高优先级, 如果以服务端的数据为最高优先级, 同步结果则相反, 虽然包含16种业务场景, 但只要确立好规则, 处理起来并不复杂.
图5
同步看起来是件简单的事情, 但实现角度来讲, 并不简单, 牵涉到数据的增删改, 操作的时间与次数, 数据的正确性与完整性, 要把它做好, 是要花费一定的精力. 把同步原理分析理解清楚, 有利于对funambol ds框架的使用与源码的研究.
一、 第一次的请求信息
Client Message #1 :
正常同步情况下, 这是客户端第一次请求所发送的内容, 如果从未同步, 客户端将会增加设备信息, 如系统版本, 固件版本等. SyncHdr是头部信息, SyncBody是包体信息, 每次请求都由这两部分内容组成.
所有的指令就不一一讲解了,这里主要看下三个重要的信息:
1. 包头中的Cred标签里面包含的是认证鉴权信息, 里面的Format代表的认证格式, 可以是b64也可是MD5等多种形式的组合, type代表的是认证类型, 包含syncml:auth-basic和syncml:auth-md5两种类型, 确定类型及格式, 服务端才能正确解析. 建议采用MD5不可逆算法, 安全系数会高些.
每次和服务端的交互都包含四次请求, 那每次请求都需要发送鉴权信息吗? 不需要, 服务端会依据session缓存机制, 保留鉴权信息的.
SyncML还提供NextNonce指令, 该指令是一个随机生成的密钥, 使用该密钥, 再通过一些组合算法加密用户信息, 这样安全系数会更高.
2. 包体的Source标签中的contact代表的是通讯录数据, SyncML DS 是数据同步协议, 不仅可以同步通讯录, 还可以同步行程, 日历, 文件等数据. Target代表的是通讯录的格式类型, 包含sifc和vcard. 目前使用广泛的是vcard格式, card代表的是vcard类型.
3. 同步原理中讲到的, 同步中有个非常重要的信息, 是同步时间戳标记, Anchor里面的Last和Next分别代表的是上次同步时间和当前同步时间, 上次同步时间, 是用于服务端判断同步是否正确的校验, 而当前同步时间, 是用于服务端匹配获取同步数据信息, 同时会作为下次同步时的上次同步时间.
如果客户端第一次请求ALERT的指令为205时, 代表的是服务端刷新全同步, 获取所有数据, 不用根据当前同步时间戳去判定.
服务端的响应信息
Server Message #1:
这是服务端的正常响应信息, 从status的响应指令可以看出, 212代表的是认证通过, 200代表的状态正常. 双向交互, 可以看到, 服务端也和客户端一样, 把同步时间标记传送给客户端, 客户端将会和服务端一样, 去做校验, 去匹配获取需要同步的数据.
二、 第二次的请求信息
Client Message #2:
第一次请求的初始化会话建立完毕之后, 第二次请求客户端开始检测并传递数据, 所以看上去内容会比较多些. STATUS是响应标签, 主要看看sync标签的内容.
Sync里面包含了add, delete还有replace标签, 分表代表的是数据的新增, 删除和修改. 每个ITEM代表的是一条数据, source代表的是客户端唯一ID标识(LUID), 服务端会根据这个信息, 去映射表里面匹配数据. Data里面是通讯录的数据, 类似JAVA中的properties文件格式, 每条属性只能占据一行, 不同的属性前缀, 代表的是不同的属性值.
服务端的响应信息:
Server Message #2 :
服务端会根据指令, 对数据进行相应的操作, 每条数据都会作出响应, 把操作结果返回给客户端. Status中的data指令会标识操作结果的正确与否, 200 代表OK, 201代表新增成功.
客户端根据返回结果, 就知道哪些数据是同步成功或失败, 同时, 可以做相应的日志记录.
注: 在后台, 数据的增删改, 都会作一个状态标识, 可以让多客户端的数据同步保持一致; 如果数据删除, 会把映射表的数据给物理删除掉.
三、第三次的请求信息
Client Message #3 :
客户端把数据传递给服务端后, 接下来, 需要从服务端获取同步数据, 这时, 客户端需要再发送一次请求, 也就是第三次请求, Alert指令中的ITEM, 明确告诉服务器, 要获取vcard格式的联系人数据信息. data中的222指令, 代表的是NEXT_MESSAGE, 需要服务器传递数据.
服务端响应信息:
Server Message #3 :
服务器根据同步时间, 用户和设备ID去获取需要同步的数据, 把联系人信息封装成VCARD格式, 和客户端的第二次请求类似, 只是对象不一样, 这次是从服务端传递数据, 从SYNC指令中可以看到, 包含了Replace, Delete和Add指令, 增删改操作都有, 仍然是以LUID作为数据的唯一标识.
四、第四次的请求信息
Client Message #4 :
对于服务端传递的数据, 客户端经过处理, 并把结果返回给服务端, 每个status指令, 代表的是一条数据的处理结果. 到这一步, 两端数据的增删改操作都已结束, 服务端的日志记录存储可以放在这一步去执行, 能够知道每条数据的同步情况, 而且是准确的. 日志记录要做得完善, 需要对每次请求都有记录, 异常情况也要考虑清楚, 需要一定精力.
如果客户端到第三步的时候, 由于网络或系统原因出现中断, 没有发送第四次请求, 响应服务端传递数据的执行结果, 服务端是不会更新映射表的时间标记, 下次同步, 会根据联系人数据的更新时间和映射表的时间标记作比对, 如果发现异常, 会再次进行同步. 所以, 状态响应确认是有非常有作用的.
服务器的响应信息:
Server Message #4 :
服务器作出最后响应, 清空自身缓存, 告知客户端此次会话结束. SyncML1.1版本比1.2多了一次请求, 客户端会发送单独结束会话的请求. SyncML1.2版本是在客户端响应数据的执行情况状态后, 结束会话, 略微简化. 1.2比1.1的鉴权加密算法会稍微复杂些, 也是出于安全考虑.
SyncML DS 的文档讲述得更为详细, 有哪些指令, 每个指令具体是做什么, 都有详细的描述, 官方英文文档, 看起来会稍微吃力些. Funambol DS 框架把SyncML DS 指令的解析组装做得已经很完善, 通过上面的内容, 能够明白其中的处理流程, 理解常用的指令就可以了. 下节, 再从代码开发的角度, 去研究funambol ds的源码, 分析核心代码的实现.
准备工作
下载ds源码, 或从funambol官网提供的SVN地址直接检出, 建立成Web Project工程. 主要分为三部分核心代码, ds-server中的framework和server包, 以及modules组件中的foundation包.
项目结构
Framework是主要是用于处理协议的, server是处理交互流程, fundation主要是处理数据层操作.
整个项目没有使用spring, hibernate等主流框架, 数据层和服务层结构不像三层开发那么清晰, 使用JDBC方式直接对数据进行操作, 配备了三个数据源, 应用于不同类型的数据操作, 在tomcat 配置 jndi 进行调用. 项目大量使用了beanxml配置. Log4j在里面配置使用得很好, 对每个重要的处理过程都会有不同的级别控制, 并且设置了MDC动态变量记录交互设备信息, 便于跟踪测试及维护.
接口说明
Funambol的同步接口不像其他soket接口通讯, 有多种接口和复杂的参数, 这里只有一个servlet接口, 通过post方式进行调用, 不用传递其他参数, 只要遵照syncML协议, 可以正常通讯. 数据同步传递的信息可能会过大, 并且移动设备的带宽资源有限, 需要把数据进行压缩, 一般采用Gzip+wbxml方式. 如果数据有压缩, 需要在请求头信息中加以描述.
主要代码
对于比较复杂的代码, 要作深入的研究或改造, 最好是先弄清楚核心的代码及功能, 做好摘记记录下来, 不至于越看越迷糊. 下面是我所摘记的主要代码, 初次看起来会比较繁琐些, 可大致了解下.
看servlet代码, 首先是解析头部信息参数, 然后获取传递的SyncML数据流, 接下来是查找负责处理核心业务的接口缓存, 如果没有, 则创建该接口.
// 获取客户端传送的二进制数据
requestData = getRequestContent(httpRequest, contentEncoding, sessionId);
…
// 获取或创建缓存
holder = createHolder(httpRequest.getSession());
接着是进入数据的处理
resp = holder.processXMLMessage(requestURL, requestData, params, headers);
先要把二进制数据转换成SyncML对象, 使用的是JIBX工具进行解析.
IBindingFactory f = BindingDirectory.getFactory("binding", SyncML.class);
IUnmarshallingContext c = f.createUnmarshallingContext();
Object syncML = c.unmarshalDocument(new StringReader(msg));
return (SyncML) syncML
通过JIBX配置转换, 把SyncML转换成JAVA对象, 每个指令, 作为一个子对象.
下面进入协议流程走向的处理, 调用sessionHandler接口操作.
进入processMessage方法, 通过局部变量currentState标识状态, 这样可以知道是交互过程中的第几次请求. 共有10中状态, 可以正确处理交互过程中的各种情形.
1. 用户鉴权判断: 第一次请求, 状态为STATE_START. 首先会根据设备标识读取设备, 若没有, 则新增. 然后进行鉴权处理, 根据算法解析cread信息, 获取用户名和密码, 去数据库查找相应信息进行验证.
// 服务端对用户密码校验代码片段:
UserProvisioningOfficer : protected Sync4jUser authenticateBasicCredential(Cred credential)
--> DBOfficer : protected Sync4jUser getUser(String userName, String password ..)
--> DBUserManager : public Sync4jUser[] getUsers(Clause clause ..)
处理完成后, 状态会标为STATE_PKG1_RECEIVING, 调用processInitSyncMapMessage方法, 返回SyncML处理指令.
// 处理返回交互结果
response = processInitSyncMapMessage(message);
2. 存储设备信息: 对于首次交互的设备, 会发送服务器的概要信息给客户端,
后续交互将不再发送. 控制代码:
DevicePersistentStore : public boolean store(Object o)
// 新增设备, 首次交互, 发送服务器的吞吐信息
d.setSentServerCaps(false);
3. 通讯录数据的加密解密:
// 服务端进行解密操作:
Sync4jEngine.sync(…
// 可进行B64和DES的加密, DES的密钥采用的是用户的密码applyDataTransformationsOnIncomingItems(clientSource.getUpdatedSyncItems());
服务端进行加密操作(通过 DataTransformerManager.xml中的sourceUriTrasformationsRequired属性去控制是否需加密, 以及用何种方式加密, 默认用户密码作为密钥)
// 先设置加密类型
PIMContactSyncSource.createSyncItem()
// 在执行加密操作
DataTransformerManager.transformOutgoingItem()
4. 通讯录数据同步更新
// 处理数据更新
SyncSessionHandler: List responseCommands = processModifications(modifications)
//把客户端传递数据通过服务端的映射表关联起来, 并设置相应的操作标识, 便于后续处理
SyncSessionHandler: private void prepareMemorySource(MemorySyncSource source …
List
(ModificationCommand) commands[i],
syncItemState, nextTimestamp.start));
// 处理SyncML中的Item指令,转换为同步对象
Sync4jEngine : public SyncItem[] itemsToSyncItems(…
EngineHelpler : public static SyncItem[] itemsToSyncItems(…
// 执行数据更新操作
Sync4jEngine : public void sync(final Sync4jPrincipal principal) ;
// 开始执行更新操作
serverSource.beginSync(syncContext)
…
// 使用快速同步, 按照同步矩阵图获取并记录需要执行的增/删/改操作数据 Sync4jStrategy.prepareFastSync(sources, p, mapping, syncFilterType,
since, syncTimestamp,
EngineHelper.getLastAnchor(uri, dbs), isLastMessage)
//保存增/删/改数据, 数据正式生效, 同时封装返回STATUS指令
Sync4jStrategy.sync(sources, false, mapping, lastAnchor, (SyncOperation[]) operations.get(uri), syncFilterType){
// 执行数据更新
Sync4jStrategy : private SyncOperationStatus[]
execUpdateOperation(ContactSyncSource …
public SyncItem updateSyncItem( SyncItem syncItem)
//执行SQL
ContactDAO : public String updateItem(ContactWrapper cw)
//可以在此方法内记录客户端传递给服务端的更新日志记录
…
}
5. 获取服务端需要同步的数据
同步给客户端的数据, 服务端主要分两步进行处理, 第一, 先整理好需要同步的增删改数据; 第二, 根据整理好的数据ID获取数据信息, 并转换成VCARD格式, 同步给客户端.
1) 整理同步数据:
调用此方法, 会返回封装好的同步增删改数据概要
syncStrategy.prepareFastSync(sources, p, mapping, syncFilterType, since, syncTimestamp, EngineHelper.getLastAnchor(uri, dbs), isLastMessage)
当改变同步时间戳, 客户端进行全同步时, 如果根据映射表找不到服务端的数据, 会根据姓名或电话号码等关键字进行匹配
fixMappedItems(newlyMappedItems, updatedA, sources[0], validMapping, principal); // 进行匹配获取
twinsKey = source.getSyncItemKeysFromTwin(clone);
// 建议注掉, 不允许根据部分字段匹配获取, 因为这样可能会对数据产生误差
PIMContactSyncSource -> getSyncItemKeysFromTwin()
最后, 再由下面的方法, 根据操作标识过滤不必要的数据
commands = syncEngine.operationsToCommands(operations, uri, contentType);
备注: 服务端的标记为新增的数据, 并且检测到客户端已存在该数据, 会标识为更新操作.
对于服务端传递给客户端的删除数据, 客户端执行成功后, 服务端会删除相应的映射信息, 但不会物理删除该条联系人数据, 删除操作相关代码:
Sync4jEngine : public void storeMappings(..)
ClientMappingPersistentStore : public boolean store(Object o) throws PersistentStoreException {…
2) 填充封装同步数据
//之前整理的同步数据, 会记录在缓冲中, 接下来是直接从缓冲里获取数据
SyncSessionHandler: private AbstractCommand[] commandsToSend(
//从syncState.cmdCache3缓存中获取, 该数据是之前调用的方法处理获取的
syncStrategy.prepareFastSync(sources, p, mapping, syncFilterType,
since, syncTimestamp, EngineHelper.getLastAnchor(uri, dbs), isLastMessage)
// 填充数据相关代码:
private boolean splitSyncCommand( ..)
public void completeItemInfo(..
tmp = (AbstractSyncItem) serverSyncSource.getSyncItemFromId(key);
6. 其他相关代码:
1) 关于映射数据表中的上次同步时间标记作用, 如果客户端的上次同步时间和数据表中的时间一致时, 将忽略该数据, 不执行更新.
但是客户端丢失时间戳刷新全同步时, 两端时间标记如果都为0, 会产生数据丢失的情况,需要注意下. 相关代码:
EngineHelper : public static SyncItem[] createSyncItems(SyncItemKey[] keys, char state,
if (!"0".equals(lastAnchor) && StringUtils.equals(lastAnchor, lastAnchorInMapping)) {…
2) 客户端进行刷新全同步时, 服务端会标记为慢同步, 同时会删除之前的映射数据,
映射缓存也会清空, 这样会影响服务端和客户端的数据关联, 造成重复数据.
解决办法: 禁止删除之前映射, 保留映射缓存(全同步, 仍按之前业务规则处理, 只是数据量的大小区别) 相关代码:
Sync4jEngine : public ClientMapping getMapping(Sync4jPrincipal principal, String uri…
3) 对于客户端的刷新全同步, 服务端若发现有更新的数据, 不会传递给客户端, 需修改, 注掉相关代码:
SyncSessionHandler: private List processModifications(SyncModifications modifications)
// if (AlertCode.isClientOnlyCode(dbs[i].getMethod())) {
// continue;
// }
4) 客户端的同步时间与服务端所记录的时间不一致时, 将会传送指令, 要求客户端全同步,
判断时间是否一致的代码:
Sync4jEngine : public void prepareDatabases(Sync4jPrincipal principal, Database[] dbs,
// 其中会判断上次同步时间是否一致.
if (!(last.tagServer.equals(dbs[i].getAnchor().getLast())) && (dbs[i].getMethod() != AlertCode.REFRESH_FROM_SERVER) && (dbs[i].getMethod() != AlertCode.REFRESH_FROM_CLIENT) && (dbs[i].getMethod() != AlertCode.SMART_ONE_WAY_FROM_CLIENT)) { …
5) 服务端全同步操作标识, 由客户端发送指令, 修改代码:
SyncInitialization : public Database[] getDatabasesToBeSynchronized(Sync4jPrincipal principal) {
// 作为服务端, 可通过修改此处进行服务端全同步的调测
//db.setMethod(clientAlerts[i].getData());
db.setMethod(AlertCode.REFRESH_FROM_SERVER);
7. 如何返写客户端执行数据更新后的结果, 简要流程
1) 交互时, 服务端从数据库中取出账户关联的映射数据
ClientMapping.initializeFromMapping(Map
resetMapping();
clientMapping.putAll(mapping);
}
2) 客户端更新完后, 会返回执行结果, 以客户端的LUID作为标识, 从缓存中查找关联的数 据, 并更新数据同步时间标记
Sync4jEngine.getGuidsLuids(Status status, List
if (status.getSourceRef() != null && status.getSourceRef().size() > 0)
{
//需修改, 客户端传来的数据, 因为LUID而非GUID
luid = ((SourceRef) status.getSourceRef().get(0)).getValue();
}
8. 客户端同步完成后, 下次若没数据更新, 很多代码是不会执行, 为方便调测, 可以把同步时间下, 相关代码:
Sync4jEngine: public void prepareDatabases(…
store.read(last);
// 时间可以根据需要进行设置, 减少5天
// last.start = DateUtils.addDays(new Date(), -8).getTime();
dbs[i].setServerAnchor(new Anchor(last.tagClient, next.tagClient));
dbs[i].setSyncStartTimestamp(last.start);
结束
这些是对funambol ds server研究过程中的一些代码摘记, 也是数据同步的主要代码. 使用同步客户端的测试工具, 结合搭建好的工程进行DEBUG跟踪调测, 会更利于理解服务端代码. 下次, 将会介绍客户端工具的使用.
结合Sync Client的测试使用
准备工作
Funambol提供的同步客户端工具有两个, 一个是Funambol Windows Sync Client, 正式的客户端, 支持outlook的联系人同步; 另一个是Java Demo Client, 模拟客户端, 主要用于测试.在funambol官网上可以下载, 安装包funambol-10.0.3主要提供服务端功能, 并且包含了Java Demo Client客户端用于验证测试, 如果没有搭建服务端环境, 可直接使用该安装包提供的服务端; 另一个安装包funambol-windows-sync-client-10.0.1提供的是一个正式客户端Funambol Windows Sync Client, 可以同步联系人, 日历, 行程, 笔记, 文件等数据. 两个文件都须安装, 不分先后. 安装完成后, 把服务先启动好. 为方便理解, 就以安装包提供的服务端作为依据, 进行配置测试.
Funambol Data Synchronization Server使用
服务端提供Administration Tool, 可用于管理配置服务端. 主要包含用户管理, 日志配置, 设备管理等功能.
图1
这里的配置按照默认值就可以, 不用进行其他更改设置. 这里是看不到数据库的数据信息的, 在实际测试中, 我们需要去数据库里面查询数据来比对验证. 服务端默认使用hsqldb作为数据库, 支持MYSQL, oracle等多种数据库. Hsqldb是一个开放源代码的JAVA数据库,其具有标准的SQL语法和JAVA接口. 我们使用MyEclipse的DB Browser作为GUI管理工具, 指向hsqldb.jar路径(安装路径下的hypersonic包内), 默认用户sa, 密码为空.
图2
连接成功后, 可以看到有许多数据表, 简要说明重要的几张表, FNBL_CLIENT_MAPPING是数据映射表, FNBL_LAST_SYNC是同步时间标记记录表, FNBL_PIM_CONTACT是联系人数据信息表, FNBL_USER是用户信息表.
图3
数据库是随着服务一起启动的, 成功启动服务后, 右下角任务图标将会显示为绿色, 打开IE浏览器, 输入http://localhost:8080/funambol/ds 若看到以下信息, 代表正常:
Funambol Data Synchronization Server v.10.0.0
Man=Funambol
Mod=DS Server
SwV=10.0.0
HwV=-
FwV=-
OEM=-
DevID=funambol
DevTyp=server
VerDTD=1.2
UTC=true
SupportLargeObjs=true
SupportNumberOfChanges=true
Ext=X-funambol-smartslow
Java Demo Client使用
1. 配置
Java Demo Client是服务端附带的一个用于同步测试的工具, 只提供通讯录和日历的同步,
操作相对简单直观些. 打开工具, 点击新增按钮, 增加几个联系人, 如下图所示.
图4
图5
接下来就是把这些数据同步到服务端, 在同步之前, 先把配置修改下, 按照下图配置:
图6
Server URL是同步服务接口地址 : http://localhost:8080/funambol/ds
UserName 和Password 是用户名和密码, 每个设备的同步都要求提供账号信息, 可以在Administration Tool 里面的users 管理, 也可以在fnbl_user表里面查看修改, 这里账号和密码使用的都是guest
Remote address book是远程通讯录格式, scard代表的是sifc格式.
Remote calendar 是远程日历格式, 这里我们只作通讯录同步测试.
Synchronizing address book 此项需要勾选, 代表需要同步通讯录数据.
Synchronizing calendar 这项可以不用勾选, 避免日志信息干扰.
Device Id 代表的是设备ID
Message type 是消息类型, 如果测试, 建议选择XML
Log Level 指的就是日志级别了.
2. 第一次全同步
配置完成后, 点击Synchronize按钮, 执行同步, 将会出现相应的日志信息, 这些都非常有用, 从信息里面可以看到同步了多少数据, 有多少条是新增或修改, 同步是否成功, 是否有异常, 异常的错误信息, 都会显示得很清楚, 下面是第一次同步的日志信息:
图7
图8
从上面的结果来看, 这次同步结果是正常的, 由于是第一次同步, 客户端会把所有数据发送给服务端. 可以看到日志中Detected 3 items, 已经检测到刚才新增的这三条数据. 为验证结果, 可以在数据库里查看FNBL_PIM_CONTACT表, 已经成功新增了三条数据.
图9
3. 差异同步
第一次同步之后, 服务端会用时间记录作为标记, 下次同步时, 就是差异同步了, 我们这时做一次新/增/改操作, 先删除[2, test], 修改[1, test]为[11,test], 再新增[4, test], 这时候我们再进行同步, 看下结果:
图10
这是修改后的数据
图11
可以看到, 客户端已经检测到增/删/改三条数据, 日志显示同步成功.
图12
最后再去数据库里面查询验证下, STATUS字段表示的是数据状态, U代表更新, N代表新增, D代表删除, 可以看到, 数据同步结果是正确的.
4. 反向服务端同步
以上, 都是客户端的数据同步到云端, 怎么才能看到云端的数据同步到客户端呢? 有两种方法:
1) 开启两个同步客户端, 可以先把第一个客户端的修改数据同步到云端, 再同步第二个客户端, 这样就等于是从云端的数据同步到客户端. 但是Java Demo Client不能模拟多个客户端, 因为数据是保存在同一个地方. 那怎么办? 上面不是安装了两个包吗, 再打开Funambol Windows Sync Client客户端, 就可以验证测试了.
2) 直接从数据库里面作修改, 这需要对数据同步有个深入的了解, 我把操作直接写下来, 按照这个方式进行修改即可. 先去FNBL_LAST_SYNC表里面查询获取同步开始时间(START_SYNC字段), 然后再修改FNBL_PIM_CONTACT表中的LAST_UPDATE字段, 两个字段的时间都是以时间戳作为记录, 把START_SYNC字段值的时间加大, 但是注意不要超过当前的系统时间, 累加1就行, 然后拷贝到你所要要修改的数据的LAST_UPDATE字段内. 再次同步, 就能看到从服务端返回的信息了.
图13
Funambol Windows Sync Client 使用
下面简要介绍该客户端的使用, 这是一个正式的数据同步客户端, 支持outlook里面的联系人同步, 而且可以支持多种类型的数据同步. 不同于上面的测试客户端, 这里使用的是1.2版本的DS同步协议, 通讯录数据格式采用的是vcard2.1版本. 安装完成后, 在outlook菜单里面会增加funambol操作按钮, 我们先打开这个同步工具
图14
同步之前, 先要进行配置, 打开tools菜单的options选项, 如图所示:
图15
这里我们只测试通讯录同步, 勾选contacts, 其余可以都关闭.
点击左侧Acount图标, 如下图16所示
图16
把接口地址, 用户名和密码配置上去即可. 如果要结合上面的测试工具模拟多客户端的测试, 两边填写的账号信息要保持一致.
配置完成后, 返回主界面, 点击第一栏[Contact]图标, 执行同步, 如果没有弹出任何错误提示, 右侧显示绿色小勾, 代表同步成功. 我把账号信息改成和Java Demo Client工具一致, 来模拟多客户端的测试, 看能不能把数据带到outlook的联系人里面.
图17
和Java Demo Client一致, 都使用guest账号.
图18
可以看到, 同步执行成功.
去outlook联系人里面查看下数据.
图19
可以看到, 已经成功把数据同步过来了.
多客户端测试可以有多种场景的组合, 单边修改, 同时修改, 结果会是怎样? 大家可以结合我第二节讲到的同步原理中的16种同步业务场景来测试看下. 不用去看场景所描述的结果, 这是是我改造源码后, 以客户端数据为依据作为同步规则执行的结果, 和funambol所提供的服务端是会有所差别.
VCARD应用简介
前面有讲到, 同步数据传输的时候, 一般都采用VCARD格式, 目前应用比较广泛, 为什么要使用这种格式进行封装, 有什么好处? 这就和电脑组装一样, 内存和显卡都有不同的品牌和型号, 如何能够组装在一起, 正常工作, 这时, 就需要制定相应的标准, 内存使用DDR接口, 显卡采用 PCI-E规范; 同样, 采用VCARD规范格式, 就能够在其他应用软件上识别解析通讯录数据. 通讯录的规范不是仅VCARD一种, 早期还有SIF-C格式, 就像早期的SDR内存一样, 效率不高, SIF-C实际上是用XML组装数据, 有很多标签, 占用数据量大, 不能灵活处理多种属性格式, 对扩展属性没有很好的支持, 所以, 近乎淘汰, 使用较少.
VCARD格式规范的具体说明, 网上可以找到相关的资料, 如果需要更详尽的, 可参考官方文档vcard-rfc2426. 下面以开发实例, 来讲解下VCARD的具体应用:
1. 数据同步的VCARD信息:
BEGIN:VCARD
VERSION:2.1
N:test4;41234;;;
FN:test4,4
NICKNAME:test4
BDAY:
TEL;CELL;HOME:
TEL;VOICE;HOME:
EMAIL;INTERNET;HOME:
TITLE:
ORG:;;
TEL;CELL;WORK:
TEL;VOICE;WORK:
EMAIL;INTERNET;WORK:
URL;WORK:
X-SEX:
X-ACCOUNT:
X-GROUPCODE:
X-HEADIMGURL:
X-HEADIMGPATH:
X-MEMO:
END:VCAR
这是改造后的VCARD格式信息, 简化了很多, funambol自身会包含较多的信息, 有很多是用不着的. VCARD类似于properties文件格式, 每一个属性占据一行, 如果属性值太多, 不要去用分行符. 不同于properties之处, 在于属性类型的描述, 从上面可以发现, 有些属性前面还会有分号, 因为通讯录的属性类型较多, 单用一个字段, 是无法完整清晰的表述, 因此, 可以在属性类型中用分号补充多个属性加以描述. 例如, 上面的TEL;CELL;HOME: 这里有三部分组成, TEL是属性类型, 代表电话, CELL和HOME是对电话的补充标识, 一个联系人的电话信息, 可能包含固定电话, 私人手机, 办公电话. VCARD只提供了TEL这个属性去标识电话, 那么, 有这么多电话, 具体代表的是哪个? 这时候就需要去看它的补充标识了, 补充标识没有具体的规范约束, 你可以自己去约定名称. 按照VCARD提供的属性, 可能对于我们实际业务来讲, 不能全部满足. 例如, 要传递一个联系人的分组信息, VCARD默认是没有这个属性的, 这时候就需要我们去使用自定义属性. 看上面的X-GROUPCODE:代表的是分组信息, 这里需注意, 自定义属性, 前面必须加上”X-”作为标识, 如果扩充的属性较为复杂, 也可以加上补充标识, 格式和上面所讲的一样.
2. 实际使用VCARD格式定义:
图1
上图是项目实际使用的VCARD格式, 按照业务所需的字段进行定义, 红色部分是比较特殊, 需要注意的地方. 以红色标注的姓名为例, 姓名由两部分组成, 前缀和后缀, 可以看到, 这两个值都是放在名称为N的属性里面, 不同顺序, 代表的归属值不一样. 以;号隔开. 组织信息, 办公地址, 家庭住址, 都是采用这样的格式. VCARD是支持中文等多种语言的, 但需使用 QP UTF-8编码(ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8), 例如工作地址: ADR;WORK;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:=E6=B7=B1=E5=9C=B3=E5=B8=82 组装成这样的格式编码, 才能正确识别中文.