微信协议简单调研笔记

 <h2>
            <a id="viewpost1_TitleUrl" href="http://www.blogjava.net/yongboy/archive/2014/03/05/410636.html">微信协议简单调研笔记</a>
        </h2>
        <div class="postText"><h3>前言</h3> <p>微信可调研点很多,这里仅仅从协议角度进行调研,会涉及到微信协议交换、消息收发等。所谓&#8220;弱水三千,只取一瓢&#8221;吧。</p> <p>杂七杂八的,有些长,可直接拉到最后看结论好了。</p> <h3>一。微信协议概览</h3> <p>微信传输协议,官方公布甚少,在微信技术总监所透漏PPT《微信之道&#8212;至简》文档中,有所体现。</p> <p>纯个人理解:</p><pre><code>因张小龙做邮箱Foxmail起家,继而又做了QQ Mail等,QQ Mail是国内第一个支持Exchange ActiveSync协议的免费邮箱,基于其从业背景,微信从一开始就采取基于ActiveSync的修改版状态同步协议Sync,也就再自然不过了。
</code></pre>
<p>一句话:增量式、按序、可靠的状态同步传输的微信协议。</p>
<p>大致交换简图如下:</p>
<p><a href="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/215cc736c7b0_A31F/Image(9)_2.png"><img title="Image(9)" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" alt="Image(9)" src="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/215cc736c7b0_A31F/Image(9)_thumb.png" border="0" height="772" width="690" /></a></p>
<p>如何获取新数据呢:</p>
<ol>
<li>服务器端通知,客户端获取
</li><li>客户端携带最新的SyncKey,发起数据请求
</li><li>服务器端生成最新的SyncKey连同最新数据发送给客户端
</li><li>基于版本号机制同步协议,可确保数据增量、有序传输
</li><li>SyncKey,由服务器端序列号生成器生成,一旦有新消息产生,将会产生最新的SyncKey。类似于版本号 </li></ol>
<p>服务器端通知有状态更新,客户端主动获取自从上次更新之后有变动的状态数据,增量式,顺序式。</p>
<h3>二。微信Web端简单调试</h3>
<p>在线版本微信:</p>
<p><a href="https://webpush.weixin.qq.com/">https://webpush.weixin.qq.com/</a></p>
<p>通过Firefox + Firebug组合调试,也能证实了微信大致通过交换SyncKey方式获取新数据的论述。</p>
<h4>1. 发起GET长连接检测是否存在新的需要同步的数据</h4>
<p>会携带上最新SyncKey</p><pre><code>https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?callback=jQuery18306073923335455973_1393208247730&amp;r=1393209241862&amp;sid=s7c%2FsxpGRSihgZAA&amp;uin=937355&amp;deviceid=e542565508353877&amp;synckey=1_620943725%7C2_620943769%7C3_620943770%7C11_620942796%7C201_1393208420%7C202_1393209127%7C1000_1393203219&amp;_=1393209241865
</code></pre>
<p>返回内容:</p><pre><code> window.synccheck={retcode:"0",selector:"2"}
</code></pre>
<p>selector值大于0,表示有新的消息需要同步。</p>
<p>据目测,心跳周期为27秒左右。</p>
<h4>2. 一旦有新数据,客户端POST请求主动获取同步的数据</h4><pre><code>https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=s7c%2FsxpGRSihgZAA&amp;r=1393208447375
</code></pre>
<p>携带消息体:</p><pre><code>{"BaseRequest":{"Uin":937355,"Sid":"s7c/sxpGRSihgZAA"},"SyncKey":{"Count":6,"List":[{"Key":1,"Val":620943725},{"Key":2,"Val":620943767},{"Key":3,"Val":620943760},{"Key":11,"Val":620942796},{"Key":201,"Val":1393208365},{"Key":1000,"Val":1393203219}]},"rr":1393208447374}
</code></pre>
<p>会携带上最新的SyncKey,会返回复杂结构体JSON内容。</p>
<p>但浏览端收取到消息之后,如何通知服务器端已确认收到了?Web版本微信,没有去做。</p>
<p>在以往使用过程中,曾发现WEB端有丢失消息的现象,但属于偶尔现象。但Android微信客户端(只要登陆连接上来之后)貌似就没有丢失过。</p>
<h4>3. 发送消息流程</h4>
<ol>
<li>
<p>发起一个POST提交,用于提交用户需要发送的消息</p>
<p>https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?sid=lQ95vHR52DiaLVqo&amp;r=1393988414386</p></li></ol>
<p>发送内容:</p><pre><code>{"BaseRequest":{"Uin":937355,"Sid":"lQ95vHR52DiaLVqo","Skey":"A6A1ECC6A7DE59DEFF6A05F226AA334DECBA457887B25BC6","DeviceID":"e937227863752975"},"Msg":{"FromUserName":"yongboy","ToUserName":"hehe057854","Type":1,"Content":"hello","ClientMsgId":1393988414380,"LocalID":1393988414380}}
</code></pre>
<p>相应内容:</p><pre><code>{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
}
,
"MsgID": 1020944348,
"LocalID": "1393988414380"
}
</code></pre>
<ol>
<li>
<p>再次发起一个POST请求,用于申请最新SyncKey</p>
<p>https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=lQ95vHR52DiaLVqo&amp;r=1393988414756</p></li></ol>
<p>发送内容:</p><pre><code>{"BaseRequest":{"Uin":937355,"Sid":"lQ95vHR52DiaLVqo"},"SyncKey":{"Count":6,"List":[{"Key":1,"Val":620944310},{"Key":2,"Val":620944346},{"Key":3,"Val":620944344},{"Key":11,"Val":620942796},{"Key":201,"Val":1393988357},{"Key":1000,"Val":1393930108}]},"rr":1393988414756}
</code></pre>
<p>响应的(部分)内容:</p><pre><code>"SKey": "8F8C6A03489E85E9FDF727ACB95C93C2CDCE9FB9532FC15B"  
</code></pre>
<ol>
<li>
<p>终止GET长连接,使用最新SyncKey再次发起一个新的GET长连接</p>
<p>https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?callback=jQuery183024581008965208218<em>1393988305564&amp;r=1393988415015&amp;sid=lQ95vHR52DiaLVqo&amp;uin=937355&amp;deviceid=e937227863752975&amp;synckey=1</em>620944310%7C2<em>620944348%7C3<em>620944344%7C11</em>620942796%7C201</em>1393988357%7C1000<em>1393930108&amp;</em>=1393988415016</p></li></ol>
<h3>三。微信Android简单分析</h3>
<p>Windows桌面端Android虚拟机中运行最新版微信(5.2),通过tcpdump/Wireshark组合封包分析,以下为分析结果。</p>
<h4>0. 初始连接记录</h4>
<p>简单记录微信启动之后请求:</p><pre><code>11:20:35 dns查询
dns.weixin.qq.com
返回一组IP地址

11:20:35 DNS查询
long.weixin.qq.com
返回一组IP地址,本次通信中,微信使用了最后一个IP作为TCP长连接的连接地址。

11:20:35
http://dns.weixin.qq.com/cgi-bin/micromsg-bin/newgetdns?uin=0&amp;clientversion=620888113&amp;scene=0&amp;net=1
用于请求服务器获得最优IP路径。服务器通过结算返回一个xml定义了域名:IP对应列表。仔细阅读,可看到微信已经开始了国际化的步伐:香港、加拿大、韩国等。
具体文本,请参考:https://gist.github.com/yongboy/9341884

11:20:35
获取到long.weixin.qq.com最优IP,然后建立到101.227.131.105的TCP长连接

11:21:25
POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/getprofile HTTP/1.1  (application/octet-stream)
返回一个名为&#8220;micromsgresp.dat&#8221;的附件,估计是未阅读的离线消息

11:21:31
POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/whatsnews HTTP/1.1  (application/octet-stream)
大概是资讯、订阅更新等

中间进行一些资源请求等,类似于
GET http://wx.qlogo.cn/mmhead/Q3auHgzwzM7NR4TYFcoNjbxZpfO9aiaE7RU5lXGUw13SMicL6iacWIf2A/96
图片等一些静态资源都会被分配到wx.qlogo.cn域名下面

不明白做什么用途
POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/downloadpackage HTTP/1.1  (application/octet-stream)
输出为micromsgresp.dat文件

11:21:47
GET http://support.weixin.qq.com/cgi-bin/mmsupport-bin/reportdevice?channel=34&amp;deviceid=A952001f7a840c2a&amp;clientversion=620888113&amp;platform=0&amp;lang=zh_CN&amp;installtype=0 HTTP/1.1
返回chunked分块数据

11:21:49
POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/reportstrategy HTTP/1.1  (application/octet-stream)
</code></pre>
<h4>1. 心跳频率约为5分钟</h4>
<p>上次使用Wireshark分析有误(得出18分钟结论),再次重新分析,心跳频率在5分钟左右。</p>
<h4>2. 登陆之后,会建立一个长连接,端口号为8080</h4>
<p>简单目测为HTTP,初始以为是双通道HTTP,难道是自定义的用于双通道通信的HTTP协议吗,网络上可见资料都是模棱两可、语焉不详。</p>
<p>具体查看长连接初始数据通信,没有发现任何包含"HTTP"字样的数据,以为是微信自定义的TCP/HTTP通信格式。据分析,用于可能用于获取数据、心跳交换消息等用途吧。这个后面会详谈微信是如何做到的。</p>
<h5>2.0 初始消息传输</h5>
<p>个人资料、离线未阅读消息部分等通过 POST HTTP短连接单独获取。</p>
<h5>2.1 二进制简单分析</h5>
<p>抽取微信某次HTTP协议方式通信数据,16进制表示,每两个靠近的数字为一个byte字节:</p>
<p><a href="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/215cc736c7b0_A31F/2014-03-03_15h07_30_2.png"><img title="2014-03-03_15h07_30" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" alt="2014-03-03_15h07_30" src="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/215cc736c7b0_A31F/2014-03-03_15h07_30_thumb.png" border="0" height="342" width="625" /></a></p>
<p>微信协议可能如下:</p><pre><code>一个消息包 = 消息头 + 消息体
</code></pre>
<p>消息头固定16字节长度,消息包长度定义在消息头前4个字节中。</p>
<p>单纯摘取第0000行为例,共16个字节的头部:</p><pre><code>00 00 00 10 00 10 00 01 00 00 00 06 00 00 00 0f
</code></pre>
<p>16进制表示,每两个紧挨着数字代表一个byte字节。</p>
<p>微信消息包格式: 1. 前4字节表示数据包长度,可变 值为16时,意味着一个仅仅包含头部的完整的数据包(可能表示着预先定义好的业务意义),后面可能还有会别的消息包 2. 2个字节表示头部长度,固定值,0x10 = 16 3. 2个字节表示谢意版本,固定值,0x01 = 1 4. 4个字节操作说明数字,可变 5. 序列号,可变 6. 头部后面紧跟着消息体,非明文,加密形式 7. 一个消息包,最小16 byte字节</p>
<p>通过上图(以及其它数据多次采样)分析:</p>
<ol>
<li>0000 - 0040为单独的数据包
</li><li>0050行为下一个数据包的头部,前四个字节值为0xca = 202,表示包含了从0050-0110共202个字节数据
</li><li>一次数据发送,可能包含若干子数据包
</li><li>换行符\n,16进制表示为0x0a,在00f0行,包含了两个换行符号
</li><li>一个数据体换行符号用于更细粒度的业务数据分割 是否蒙对,需要问问做微信协议的同学
</li><li>所有被标记为HTTP协议通信所发送数据都包含换行符号 </li></ol>
<h5>2.2 动手试试猜想,模拟微信TCP长连接</h5>
<p>开始很不解为什么会出现如此怪异的HTTP双通道长连接请求,难道基于TCP通信,然后做了一些手脚?很常规的TCP长连接,传输数据时(不是所有数据传输),被wireshark误认为HTTP长连接。这个需要做一个实验证实一下自己想法,设想如下:</p>
<p>写一个Ping-Pong客户端、服务器端程序,然后使用Wireshark看一下结果,是否符合判断。</p>
<p>Java版本的请求端,默认请求8080端口: <script src="https://gist.github.com/yongboy/9319660.js"></script></p>
<p>C语言版本的服务器程序,收到什么发送什么,没有任何逻辑,默认绑定8080端口: <script src="https://gist.github.com/yongboy/9341037.js"></script></p>
<p>这里有一个现场图:</p>
<p><a href="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/215cc736c7b0_A31F/2014-03-03_14h53_19_2.png"><img title="2014-03-03_14h53_19" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" alt="2014-03-03_14h53_19" src="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/215cc736c7b0_A31F/2014-03-03_14h53_19_thumb.png" border="0" height="702" width="1052" /></a></p>
<p>可以尝试稍微改变输出内容,去除换行符&#8220;\n&#8221;,把端口换成9000,试试看,就会发现Wireshark输出不同的结果来。</p>
<h5>2.3 结论是什么呢?</h5>
<p>若使用原始TCP进行双向通信,则需要满足以下条件,可以被类似于Wireshark协议拦截器误认为是HTTP长连接:</p>
<ol>
<li>使用80/8080端口(81/3128/8000经测试无效) 也许8080一般被作为WEB代理服务端口,微信才会享用这个红利吧。
</li><li>输出的内容中,一定要包含换行字符"\n" </li></ol>
<p>因此,可以定性为微信使用了基于8080端口TCP长连接,一旦数据包中含有换行"\n"符号,就会被Wireshark误认为HTTP协议。可能微信是无心为之吧。</p>
<h4>3. 新消息获取方式</h4>
<ol>
<li>TCP长连接接收到服务器通知有新消息需要获取
</li><li>APP发起一个HTTP POST请求获取新状态消息,会带上当前SyncKey 地址为:http://short.weixin.qq.com/cgi-bin/micromsg-bin/reportstrategy HTTP/1.1,看不到明文
</li><li>APP获取到新的消息,会再次发起一次HTTP POST请求,告诉服务器已确认收到,同时获取最新SyncKey 地址为:http://short.weixin.qq.com/cgi-bin/micromsg-bin/kvreport,看不到明文
</li><li>接受一个消息,TCP长连接至少交互两次,客户端发起两次HTTP POST请求
<br />具体每次交互内容是什么,有些模糊<br /></li><li>服务器需要支持:状态消息获取标记,状态消息确认收取标记。只有被确认收到,此状态消息才算是被正确消费掉
</li><li>多个不同设备同一账号同时使用微信,同一个状态消息会会被同时分发到多个设备上 </li></ol>
<p>此时消息请求截图如下:</p>
<p><a href="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/215cc736c7b0_A31F/2014-03-03_15h58_15_2.png"><img title="2014-03-03_15h58_15" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" alt="2014-03-03_15h58_15" src="http://www.blogjava.net/images/blogjava_net/yongboy/Windows-Live-Writer/215cc736c7b0_A31F/2014-03-03_15h58_15_thumb.png" border="0" height="371" width="1422" /></a></p>
<h4>4. 发送消息方式</h4>
<p>发送消息走已经建立的TCP长连接通道,发送消息到服务器,然后接受确认信息等,产生一次交互。</p>
<p>小伙伴接收到信息阅读也都会收到服务器端通知,产生一次交互等。</p><!--![](wg/2014-03-03_16h19_14.png)-->
<p>可以确定,微信发送消息走TCP长连接方式,因为不对自身状态数据产生影响,应该不交换SyncKey。</p>
<ul>
<li>在低速网络下,大概会看到消息发送中的提示,属于消息重发机制
</li><li>网络不好有时客户端会出现发送失败的红色感叹号
</li><li>已发送到服务器但未收到确认的消息,客户端显示红色感叹号,再次重发,服务器作为重复消息处理,反馈确认</li><li>上传图片,会根据图片大小,分割成若干部分(大概1.5K被划分为一部分),同一时间点,客户端会发起若干次POST请求,各自上传成功之后,服务器大概会合并成一个完整图片,返回一个缩略图,显示在APP聊天窗口内。APP作为常规的文字消息发送到服务器端</li><li>上传音频,则单独走TCP通道,一个两秒的录制音频,客户端录制完毕,分为两块传输,一块最大1.5K左右,服务端响应一条数据通知确认收到。共三次数据传输。<br />音频和纯文字信息一致,都是走TCP长连接,客户端发送,服务器端确认。</li></ul>
<h3>四。微信协议小结</h3>
<ol>
<li>发布的消息对应一个ID(只要单个方向唯一即可,服务器端可能会根ID判断重复接收),消息重传机制确保有限次的重试,重试失败给予用户提示,发送成功会反馈确认,客户端只有收到确认信息才知道发送成功。发送消息可能不会产生新SyncKey。
</li><li>基于版本号(SynKey)的状态消息同步机制,增量、有序传输需求水到渠成。长连接通知/短连接获取、确认等,交互方式简单,确保了消息可靠谱、准确无误到达。
</li><li>客户端/服务器端都会存储消息ID处理记录,避免被重复消费客户端获取最新消息,但未确认,服务器端不会认为该消息被消费掉。下次客户端会重新获取,会查询当前消息是否被处理过。根据一些现象猜测。
</li><li>总体上看,微信协议跨平台(TCP或HTPP都可呈现,处理方式可统一),通过&#8220;握手&#8221;同步,很可靠,无论哪一个平台都可以支持的很好
</li><li>微信协议最小成本为16字节,大部分时间若干个消息包和在一起,批量传输。微信协议说不上最简洁,也不是最节省流量,但是非常成功的。 </li><li><div>若服务器检测到一些不确定因素,可能会导致微启用安全套接层SSL协议进行常规的TCP长连接传输。短连接都没有发生变化</div><br /></li></ol>
<p>以上,根据有限资料和数据拦截观察总结得出,啰啰嗦嗦,勉强凑成一篇,会存在一些不正确之处,欢迎给予纠正。在多次</p>
<h3>五。附录</h3>
<p>Microsoft Exchange Active Sync协议,简称EAS,分为folderrsync(同步文件夹目录,即邮箱内有哪几个文件夹)和sync(每个文件夹内有哪些文档)两部分。</p>
<p>某网友总结的协议一次回话大致示范:</p><pre><code>Client:   synckey=0 //第一次key为0
Server:  newsynckey=1235434    //第一次返回新key
Client:   synckey=1235434   //使用新key查询
Server:  newsynckey=1647645,data=*****//第一次查询,得到新key和数据
Client:   synckey=1647645
Server:  newsynckey=5637535,data=null //第二次查询,无新消息
Client:   synckey=5637535
Server: newsynckey=8654542, data=****//第三次查询,增量同步
</code></pre>
<ul>
<li>上页中的相邻请求都是隔固定时间的,如两分钟
</li><li>客户端每次使用旧key标记自己的状态,服务端每次将新key和增量数据一起返回。
</li><li>key是递增的,但不要求连续
</li><li>请求的某个参数决定服务器是否立即返回 </li></ul><br><br><div><span style="color: #008000;">----------------------------------------------------------------------------<br /></span><p>转载<a href="http://www.blogjava.net/yongboy/">聂永的博客</a></p>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值