理解了数据同步原理之后, 再来看看SyncML DS数据传输协议是怎么实现的. 下面把同步交互的数据都打印了出来, 测试使用的是1.2版本协议, 实际传输采用wbxml+gzip,目的是节省带宽; 通讯录数据格式使用的是VCARD2.1版本. 一般会把通讯录数据再进行AES等算法进行加密. 这里是明文显示. 便于观看. VCARD是通讯录的一种标准格式, 有自己的格式规范, 后面会作详细介绍. 客户端每次和云端同步, 都会有四次请求组成, 具体讲解下, 如不清楚, 可结合第一节的介绍, 这样更便于理解.
一、 第一次的请求信息
Client Message #1 :
<?xml version="1.0" encoding="UTF-8"?>
<SyncML>
<SyncHdr>
<VerDTD>1.2</VerDTD>
<VerProto>SyncML/1.2</VerProto>
<SessionID>1325126100</SessionID>
<MsgID>1</MsgID>
<Target>
<LocURI>http://localhost:8080/contact/sync</LocURI>
</Target>
<Source>
<LocURI>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</LocURI>
</Source>
<Cred>
<Meta>
<Format xmlns="syncml:metinf">b64</Format>
<Type xmlns="syncml:metinf">syncml:auth-basic</Type>
</Meta>
<Data>dGVzdDp0ZXN0</Data>
</Cred>
<Meta>
<MaxMsgSize xmlns="syncml:metinf">125000</MaxMsgSize>
</Meta>
</SyncHdr>
<SyncBody>
<Alert>
<CmdID>1</CmdID>
<Data>200</Data>
<Item>
<Target>
<LocURI>card</LocURI>
</Target>
<Source>
<LocURI>contact</LocURI>
</Source>
<Meta>
<Anchor xmlns="syncml:metinf">
<Last>1325125990</Last>
<Next>1325126100</Next>
</Anchor>
</Meta>
</Item>
</Alert>
<Final/>
</SyncBody>
</SyncML>
正常同步情况下, 这是客户端第一次请求所发送的内容, 如果从未同步, 客户端将会增加设备信息, 如系统版本, 固件版本等. 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时, 代表的是服务端刷新全同步, 获取所有数据, 不用根据当前同步时间戳去判定.
<Final/>标签代表的是此次请求信息的结束, 有时一次请求的数据量过大, 需要拆分发送.
服务端的响应信息
Server Message #1:
<?xml version="1.0" encoding="UTF-8"?>
<SyncML>
<SyncHdr>
<VerDTD>1.2</VerDTD>
<VerProto>SyncML/1.2</VerProto>
<SessionID>1325126100</SessionID>
<MsgID>1</MsgID>
<Target>
<LocURI>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</LocURI>
</Target>
<Source>
<LocURI>http://localhost:8080/contact/sync</LocURI>
</Source> <RespURI>http://localhost:8080/contact/sync;jsessionid=7816549EAAE9573234DFB60ACF04269A</RespURI>
</SyncHdr>
<SyncBody>
<Status>
<CmdID>1</CmdID>
<MsgRef>1</MsgRef>
<CmdRef>0</CmdRef>
<Cmd>SyncHdr</Cmd>
<TargetRef>http://localhost:8080/contact/sync</TargetRef>
<SourceRef>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</SourceRef>
<Data>212</Data>
</Status>
<Status>
<CmdID>2</CmdID>
<MsgRef>1</MsgRef>
<CmdRef>1</CmdRef>
<Cmd>Alert</Cmd>
<TargetRef>card</TargetRef>
<SourceRef>contact</SourceRef>
<Data>200</Data>
<Item>
<Data>
<Anchor xmlns="syncml:metinf">
<Next>1325126100</Next>
</Anchor>
</Data>
</Item>
</Status>
<Alert>
<CmdID>3</CmdID>
<Data>200</Data>
<Item>
<Target>
<LocURI>contact</LocURI>
</Target>
<Source>
<LocURI>card</LocURI>
</Source>
<Meta>
<Anchor xmlns='syncml:metinf'>
<Last>1325125990046</Last>
<Next>1325126100890</Next>
</Anchor>
</Meta>
</Item>
</Alert>
<Final/>
</SyncBody>
</SyncML>
这是服务端的正常响应信息, 从status的响应指令可以看出, 212代表的是认证通过, 200代表的状态正常. 双向交互, 可以看到, 服务端也和客户端一样, 把同步时间标记传送给客户端, 客户端将会和服务端一样, 去做校验, 去匹配获取需要同步的数据.
二、 第二次的请求信息
Client Message #2:
<?xml version="1.0" encoding="UTF-8"?>
<SyncML>
<SyncHdr>
<VerDTD>1.2</VerDTD>
<VerProto>SyncML/1.2</VerProto>
<SessionID>1325126100</SessionID>
<MsgID>2</MsgID>
<Target>
<LocURI>http://localhost:8080/contact/sync</LocURI>
</Target>
<Source>
<LocURI>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</LocURI>
</Source>
</SyncHdr>
<SyncBody>
<Status>
<CmdID>1</CmdID>
<MsgRef>1</MsgRef>
<CmdRef>0</CmdRef>
<Cmd>SyncHdr</Cmd>
<TargetRef>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</TargetRef>
<SourceRef>http://localhost:8080/contact/sync</SourceRef>
<Data>200</Data>
</Status>
<Status>
<CmdID>2</CmdID>
<MsgRef>1</MsgRef>
<CmdRef>3</CmdRef>
<Cmd>Alert</Cmd>
<TargetRef>card</TargetRef>
<SourceRef>contact</SourceRef>
<Data>200</Data>
<Item>
<Data>
<Anchor xmlns="syncml:metinf">
<Next>1325126100890</Next>
</Anchor>
</Data>
</Item>
</Status>
<Sync>
<CmdID>3</CmdID>
<Target>
<LocURI>card</LocURI>
</Target>
<Source>
<LocURI>contact</LocURI>
</Source>
<Add>
<CmdID>4</CmdID>
<Item>
<Source>
<LocURI>000000002D6290337F2BEB4AA11137DC016BF7F4A4D12200</LocURI>
</Source>
<Meta>
<Format xmlns="syncml:metinf">bin</Format>
<Type xmlns="syncml:metinf">text/x-vcard</Type>
</Meta>
<Data>
<![CDATA[BEGIN:VCARD
VERSION:2.1
N:test7;;;;
BDAY:
NOTE:
TEL;WORK;FAX:
TEL;VOICE;WORK:
TEL;VOICE;WORK:
TEL;CAR;VOICE:
CATEGORIES:
TEL;WORK;PREF:
FN:test7
EMAIL;INTERNET:test7@ test.cn
EMAIL;INTERNET;HOME:
EMAIL;INTERNET;WORK:
TITLE:test7
TEL;VOICE;HOME:12312345678
TEL;VOICE;HOME:
TEL;HOME;FAX:
URL;HOME:
PRIORITY:1
TEL;CELL:
NICKNAME:
TEL;FAX:
TEL;VOICE:
TEL;PAGER:
TEL;PREF;VOICE:
ROLE:
CLASS:PUBLIC
URL:
ORG:test7;test7;
ADR;HOME:;;;;;;
ADR:;;;;;;
ADR;WORK;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:;;;=E6=B7=B1=E5=9C=B3=E5=B8=82;;=E5=B9=BF=E4=B8=9C=E7=9C=81;
PHOTO:
X-ANNIVERSARY:
X-FUNAMBOL-BILLINGINFO:
TEL;X-FUNAMBOL-CALLBACK:
X-FUNAMBOL-CHILDREN:
X-FUNAMBOL-COMPANIES:
X-FUNAMBOL-FOLDER:DEFAULT_FOLDER
X-FUNAMBOL-GENDER:0
X-FUNAMBOL-HOBBIES:
EMAIL;INTERNET;HOME;X-FUNAMBOL-INSTANTMESSENGER:
X-FUNAMBOL-INITIALS:t.
X-FUNAMBOL-LANGUAGES:
X-MANAGER:
X-FUNAMBOL-MILEAGE:
X-FUNAMBOL-ORGANIZATIONALID:
TEL;X-FUNAMBOL-RADIO:
X-SPOUSE:
X-FUNAMBOL-SUBJECT:test7
TEL;X-FUNAMBOL-TELEX:
X-FUNAMBOL-YOMICOMPANYNAME:
X-FUNAMBOL-YOMIFIRSTNAME:
X-FUNAMBOL-YOMILASTNAME:
END:VCARD
]]>
</Data>
</Item>
</Add>
<Delete>
<CmdID>5</CmdID>
<Item>
<Source> <LocURI>000000002D6290337F2BEB4AA11137DC016BF7F444D12200</LocURI>
</Source>
</Item>
</Delete>
</Sync>
<Final/>
</SyncBody>
</SyncML>
第一次请求的初始化会话建立完毕之后, 第二次请求客户端开始检测并传递数据, 所以看上去内容会比较多些. STATUS是响应标签, 主要看看sync标签的内容.
Sync里面包含了add, delete还有replace标签, 分表代表的是数据的新增, 删除和修改. 每个ITEM代表的是一条数据, source代表的是客户端唯一ID标识(LUID), 服务端会根据这个信息, 去映射表里面匹配数据. Data里面是通讯录的数据, 类似JAVA中的properties文件格式, 每条属性只能占据一行, 不同的属性前缀, 代表的是不同的属性值.
服务端的响应信息:
Server Message #2 :
<?xml version="1.0" encoding="UTF-8"?>
<SyncML>
<SyncHdr>
<VerDTD>1.2</VerDTD>
<VerProto>SyncML/1.2</VerProto>
<SessionID>1325126100</SessionID>
<MsgID>2</MsgID>
<Target>
<LocURI>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</LocURI>
</Target>
<Source>
<LocURI>http://localhost:8080/contact/sync</LocURI>
</Source>
<RespURI>http://localhost:8080/contact/sync;jsessionid=7816549EAAE9573234DFB60ACF04269A</RespURI>
</SyncHdr>
<SyncBody>
<Status>
<CmdID>1</CmdID>
<MsgRef>2</MsgRef>
<CmdRef>0</CmdRef>
<Cmd>SyncHdr</Cmd>
<TargetRef>http://localhost:8080/contact/sync</TargetRef>
<SourceRef>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</SourceRef>
<Data>200</Data>
</Status>
<Status>
<CmdID>2</CmdID>
<MsgRef>2</MsgRef>
<CmdRef>3</CmdRef>
<Cmd>Sync</Cmd>
<TargetRef>card</TargetRef>
<SourceRef>contact</SourceRef>
<Data>200</Data>
</Status>
<Status>
<CmdID>3</CmdID>
<MsgRef>2</MsgRef>
<CmdRef>4</CmdRef>
<Cmd>Add</Cmd>
<SourceRef>000000002D6290337F2BEB4AA11137DC016BF7F4A4D12200</SourceRef>
<Data>201</Data>
</Status>
<Status>
<CmdID>4</CmdID>
<MsgRef>2</MsgRef>
<CmdRef>5</CmdRef>
<Cmd>Delete</Cmd>
<SourceRef>000000002D6290337F2BEB4AA11137DC016BF7F444D12200</SourceRef>
<Data>200</Data>
</Status>
</SyncBody>
</SyncML>
服务端会根据指令, 对数据进行相应的操作, 每条数据都会作出响应, 把操作结果返回给客户端. Status中的data指令会标识操作结果的正确与否, 200 代表OK, 201代表新增成功.
客户端根据返回结果, 就知道哪些数据是同步成功或失败, 同时, 可以做相应的日志记录.
注: 在后台, 数据的增删改, 都会作一个状态标识, 可以让多客户端的数据同步保持一致; 如果数据删除, 会把映射表的数据给物理删除掉.
三、第三次的请求信息
Client Message #3 :
<?xml version="1.0" encoding="UTF-8"?>
<SyncML>
<SyncHdr>
<VerDTD>1.2</VerDTD>
<VerProto>SyncML/1.2</VerProto>
<SessionID>1325126100</SessionID>
<MsgID>3</MsgID>
<Target>
<LocURI>http://localhost:8080/contact/sync</LocURI>
</Target>
<Source>
<LocURI>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</LocURI>
</Source>
</SyncHdr>
<SyncBody>
<Status>
<CmdID>1</CmdID>
<MsgRef>2</MsgRef>
<CmdRef>0</CmdRef>
<Cmd>SyncHdr</Cmd>
<TargetRef>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</TargetRef>
<SourceRef>http://localhost:8080/contact/sync</SourceRef>
<Data>200</Data>
</Status>
<Alert>
<CmdID>2</CmdID>
<Data>222</Data>
<Item>
<Target>
<LocURI>card</LocURI>
</Target>
<Source>
<LocURI>contact</LocURI>
</Source>
</Item>
</Alert>
</SyncBody>
</SyncML>
客户端把数据传递给服务端后, 接下来, 需要从服务端获取同步数据, 这时, 客户端需要再发送一次请求, 也就是第三次请求, Alert指令中的ITEM, 明确告诉服务器, 要获取vcard格式的联系人数据信息. data中的222指令, 代表的是NEXT_MESSAGE, 需要服务器传递数据.
服务端响应信息:
Server Message #3 :
<?xml version="1.0" encoding="UTF-8"?>
<SyncML>
<SyncHdr>
<VerDTD>1.2</VerDTD>
<VerProto>SyncML/1.2</VerProto>
<SessionID>1325126100</SessionID>
<MsgID>3</MsgID>
<Target>
<LocURI>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</LocURI>
</Target>
<Source>
<LocURI>http://localhost:8080/contact/sync</LocURI>
</Source> <RespURI>http://localhost:8080/contact/sync;jsessionid=7816549EAAE9573234DFB60ACF04269A</RespURI>
</SyncHdr>
<SyncBody>
<Status>
<CmdID>1</CmdID>
<MsgRef>3</MsgRef>
<CmdRef>0</CmdRef>
<Cmd>SyncHdr</Cmd>
<TargetRef>http://localhost:8080/contact/sync</TargetRef>
<SourceRef>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</SourceRef>
<Data>200</Data>
</Status>
<Status>
<CmdID>2</CmdID>
<MsgRef>3</MsgRef>
<CmdRef>2</CmdRef>
<Cmd>Alert</Cmd>
<TargetRef>card</TargetRef>
<SourceRef>contact</SourceRef>
<Data>200</Data>
<Item>
<Target>
<LocURI>card</LocURI>
</Target>
<Source>
<LocURI>contact</LocURI>
</Source>
</Item>
</Status>
<Sync>
<CmdID>3</CmdID>
<Target>
<LocURI>contact</LocURI>
</Target>
<Source>
<LocURI>card</LocURI>
</Source>
<Replace>
<CmdID>4</CmdID>
<Meta>
<Type xmlns='syncml:metinf'>text/x-vcard</Type>
</Meta>
<Item>
<Target> <LocURI>000000002D6290337F2BEB4AA11137DC016BF7F484D12200</LocURI>
</Target>
<Data>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-ISENTERPRISE:
X-PRMACCOUNT:
X-GROUPCODE:
X-HEADIMGURL:
X-HEADIMGPATH:
X-MEMO:
END:VCARD
</Data>
</Item>
</Replace>
<Delete>
<CmdID>5</CmdID>
<Item>
<Target>
<LocURI>000000002D6290337F2BEB4AA11137DC016BF7F464D12200</LocURI>
</Target>
</Item>
</Delete>
<Add>
<CmdID>6</CmdID>
<Meta>
<Type xmlns='syncml:metinf'>text/x-vcard</Type>
</Meta>
<Item>
<Source>
<LocURI>7</LocURI>
</Source>
<Data>BEGIN:VCARD
VERSION:2.1
N:test6;test6;;;
FN:test6
NICKNAME:test6
ADR;HOME:;;;;;;
BDAY:
TEL;CELL;HOME:
TEL;VOICE;HOME:
EMAIL;INTERNET;HOME:
ADR;WORK:;;;;;;
TITLE:
ORG:;;
TEL;CELL;WORK:
TEL;VOICE;WORK:
EMAIL;INTERNET;WORK:
URL;WORK:
X-SEX:
X-ISENTERPRISE:
X-PRMACCOUNT:
X-GROUPCODE:
X-HEADIMGURL:
X-HEADIMGPATH:
X-MEMO:
END:VCARD
</Data>
</Item>
</Add>
</Sync>
<Final/>
</SyncBody>
</SyncML>
服务器根据同步时间, 用户和设备ID去获取需要同步的数据, 把联系人信息封装成VCARD格式, 和客户端的第二次请求类似, 只是对象不一样, 这次是从服务端传递数据, 从SYNC指令中可以看到, 包含了Replace, Delete和Add指令, 增删改操作都有, 仍然是以LUID作为数据的唯一标识.
四、第四次的请求信息
Client Message #4 :
<?xml version="1.0" encoding="UTF-8"?>
<SyncML>
<SyncHdr>
<VerDTD>1.2</VerDTD>
<VerProto>SyncML/1.2</VerProto>
<SessionID>1325126100</SessionID>
<MsgID>4</MsgID>
<Target>
<LocURI>http://localhost:8080/contact/sync</LocURI>
</Target>
<Source>
<LocURI>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</LocURI>
</Source>
</SyncHdr>
<SyncBody>
<Status>
<CmdID>1</CmdID>
<MsgRef>3</MsgRef>
<CmdRef>0</CmdRef>
<Cmd>SyncHdr</Cmd>
<TargetRef>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</TargetRef>
<SourceRef>http://localhost:8080/contact/sync</SourceRef>
<Data>200</Data>
</Status>
<Status>
<CmdID>2</CmdID>
<MsgRef>3</MsgRef>
<CmdRef>3</CmdRef>
<Cmd>Sync</Cmd>
<TargetRef>card</TargetRef>
<SourceRef>contact</SourceRef>
<Data>200</Data>
</Status>
<Status>
<CmdID>3</CmdID>
<MsgRef>3</MsgRef>
<CmdRef>4</CmdRef>
<Cmd>Replace</Cmd>
<Data>200</Data>
<Item>
<Source>
<LocURI>000000002D6290337F2BEB4AA11137DC016BF7F484D12200</LocURI>
</Source>
</Item>
</Status>
<Status>
<CmdID>4</CmdID>
<MsgRef>3</MsgRef>
<CmdRef>5</CmdRef>
<Cmd>Delete</Cmd>
<Data>200</Data>
<Item>
<Source>
<LocURI>000000002D6290337F2BEB4AA11137DC016BF7F464D12200</LocURI>
</Source>
</Item>
</Status>
<Status>
<CmdID>5</CmdID>
<MsgRef>3</MsgRef>
<CmdRef>6</CmdRef>
<Cmd>Add</Cmd>
<Data>201</Data>
<Item>
<Source>
<LocURI>7</LocURI>
</Source>
</Item>
</Status>
<Final/>
</SyncBody>
</SyncML>
对于服务端传递的数据, 客户端经过处理, 并把结果返回给服务端, 每个status指令, 代表的是一条数据的处理结果. 到这一步, 两端数据的增删改操作都已结束, 服务端的日志记录存储可以放在这一步去执行, 能够知道每条数据的同步情况, 而且是准确的. 日志记录要做得完善, 需要对每次请求都有记录, 异常情况也要考虑清楚, 需要一定精力.
如果客户端到第三步的时候, 由于网络或系统原因出现中断, 没有发送第四次请求, 响应服务端传递数据的执行结果, 服务端是不会更新映射表的时间标记, 下次同步, 会根据联系人数据的更新时间和映射表的时间标记作比对, 如果发现异常, 会再次进行同步. 所以, 状态响应确认是有非常有作用的.
服务器的响应信息:
Server Message #4 :
<?xml version="1.0" encoding="UTF-8"?>
<SyncML>
<SyncHdr>
<VerDTD>1.2</VerDTD>
<VerProto>SyncML/1.2</VerProto>
<SessionID>1325126100</SessionID>
<MsgID>4</MsgID>
<Target>
<LocURI>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</LocURI>
</Target>
<Source>
<LocURI>http://localhost:8080/contact/sync</LocURI>
</Source> <RespURI>http://localhost:8080/contact/sync;jsessionid=7816549EAAE9573234DFB60ACF04269A</RespURI>
</SyncHdr>
<SyncBody>
<Status>
<CmdID>1</CmdID>
<MsgRef>4</MsgRef>
<CmdRef>0</CmdRef>
<Cmd>SyncHdr</Cmd>
<TargetRef>http://localhost:8080/contact/sync</TargetRef>
<SourceRef>fol-TUlDUk9TT0YtMTY3MDY1OkFkbWluaXN0cmF0b3I=</SourceRef>
<Data>200</Data>
</Status>
<Final/>
</SyncBody>
</SyncML>
服务器作出最后响应, 清空自身缓存, 告知客户端此次会话结束. SyncML1.1版本比1.2多了一次请求, 客户端会发送单独结束会话的请求. SyncML1.2版本是在客户端响应数据的执行情况状态后, 结束会话, 略微简化. 1.2比1.1的鉴权加密算法会稍微复杂些, 也是出于安全考虑.
SyncML DS 的文档讲述得更为详细, 有哪些指令, 每个指令具体是做什么, 都有详细的描述, 官方英文文档, 看起来会稍微吃力些. Funambol DS 框架把SyncML DS 指令的解析组装做得已经很完善, 通过上面的内容, 能够明白其中的处理流程, 理解常用的指令就可以了. 下节, 再从代码开发的角度, 去研究funambol ds的源码, 分析核心代码的实现.