新技术论坛
搜索
查看: 1279|回复: 0
打印 上一主题 下一主题

[Others] 微信通讯协议的学习

[复制链接]
  • TA的每日心情
    开心
    2016-12-9 18:18
  • 签到天数: 85 天

    连续签到: 1 天

    [LV.6]常住居民II

    扫一扫,手机访问本帖
    楼主
    跳转到指定楼层
    发表于 2016-6-25 09:08:22 来自手机 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    微信协议概览
    微信传输协议,官方公布甚少,在微信技术总监所透漏PPT《微信之道—至简》文档中,有所体现。

    微信从2011年1月发布以来,在一年之内实现了上亿用户,千万级在线,在苹果中国区App Store月下载量排行第一。 腾讯把微信的成功总结为“三位一体”,即产品的精准,项目的敏捷,以及技术的支撑。 敏捷就是试错法,用最快的迭代速度不断追求卓越。敏捷是一种态度,允许发布前十分钟的变更,并给予产品决策以最大的自由度。

    微信使用的同步协议叫做SYNC,参考了微软的ActiveSync YNchronous ommunication:同步通信。没有数据发送时,传输线处于MARK状态。为了表示数据传输的开始,发送方先发送一个或两个特殊字符,该字符称为同步字符。当发送方和接收方达到同步后,就可以一个字符接一个字符地发送一大块数据,而不再需要用起始位和停止位了,这样可以明显地提高数据的传输速率。采用同步方式传送数据时,在发送过程中,收发双方还必须用一个时钟进行协调,用于确定串行传输中每一位的位置。接收数据时,接收方可利用同步字符将内部时钟与发送方保持同步,然后将同步字符后面的数据逐位移入,并转换成并行格式,供CPU读取,直至收到结束符为止。用一个Key来实现状态同步。这样一种协议在后台实现上比业界通用方案要复杂许多,但是能把客户端的实现大大简化,同时在很大程度上能够满足iPhone,安卓,塞班等多个操作系统的不同需求。

    微信秉承“重后台轻客户端”的思路,因为客户端安装在用户手机上,变更成本很高;而后台则可以实现迅速的变更,在不发新版本的情况下实现新功能。以下是一个例子:微信的最初版本是不支持群聊的,第二个版本支持了群聊,但第一版客户端仍然可以在后台的变更处理之下参与群聊,只是不能够发起群聊而已。

    其服务器端目前获知的几部分分别是三网专用网关服务器、登陆服务器组、负载均衡服务器组,主动推送服务器组、后台数据转换服务器组、存储阵列等几部分。由于目前没有任何能够直接从客户端保存至服务器端的功能,推测其服务方并没有用于数据记录的数据库服务器,而是在登陆服务器组中集成了用户数据库,用来记录用户授权。

    因张小龙做邮箱Foxmail起家,继而又做了QQ Mail等,QQ Mail是国内第一个支持Exchange ActiveSync协议的免费邮箱,基于其从业背景,微信从一开始就采取基于ActiveSync的修改版状态同步协议Sync,也就再自然不过了。一句话:增量式、按序、可靠的状态同步传输的微信协议。

    Microsoft Exchange Active Sync协议
    Microsoft Exchange Active Sync协议,简称EAS,分为folderrsync(同步文件夹目录,即邮箱内有哪几个文件夹)和sync(每个文件夹内有哪些文档)两部分。

    某网友总结的协议一次回话大致示范:

    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=****//第三次查询,增量同步

    如何获取新数据呢:

    服务器端通知,客户端获取
    客户端携带最新的SyncKey,发起数据请求
    服务器端生成最新的SyncKey连同最新数据发送给客户端
    基于版本号机制同步协议,可确保数据增量、有序传输
    SyncKey,由服务器端序列号生成器生成,一旦有新消息产生,将会产生最新的SyncKey。类似于版本号

    服务器端通知有状态更新,客户端主动获取自从上次更新之后有变动的状态数据,增量式,顺序式。

    微信的协议
    为保证稳定,微信用了长链接和短链接相结合,微信划分了http模式(short链接)和 tcp 模式(long 链接),分别应对状态协议和数据传输协议

    weixin.qq.com dns check (112.64.237.188 112.64.200.218)
    weixin.qq.com dns check  ( 112.64.237.186 112.64.200.240)

    1)short.weixin.qq.com
    是HTTP协议扩展,运行8080 端口,http body为二进制(protobuf)。主要用途(接口):

    用户登录验证;
    好友关系(获取,添加);
    消息sync (newsync),自有sync机制;
    获取用户图像;
    用户注销;
    行为日志上报。
    朋友圈发表刷新

    2)long.weixin.qq.com
    tcp长连接,端口为8080,类似微软activesync的二进制协议。主要用途(接口):

    接受/发送文本消息;
    接受/发送语音;
    接受/发送图片;
    接受/发送视频文件等。

    所有上面请求都是基于tcp长连接。在发送图片和视频文件等时,分为两个请求;第一个请求是缩略图的方式,第二个请求是全数据的方式。

    3)数据报文方面
    增量上传策略:每次8k左右大小数据上传,服务器确认;在继续传输。
    图片上传:先传缩略图,传文本消息,再传具体文件
    下载:先下载缩略图, 在下载原图,下载的时候,全部一次推送。

    Sync 同样存在一些问题:

    SyncKey 生成维护成本:SyncKey 在ActiveSync中为字符串,客户端不需要解析,但服务端实现要用数字自增,需要强一致性,且不能回退。
    消息的订阅模式:采用类似Zookeeper的One time triggler 还是每条消息都推送一条通知能,ne time trigger能够避免并发通知时,获取消息时重复问题,但增加了交互成本,和客户端实现复杂性。
    自己发的消息,SyncKey怎么获取,其要支持多端同步发消息,保证消息同步;也只好消息发完在给自己同步一遍(自己设备发的可以不带消息体)
    消息推送延时加重:Sync 消息体获取方式:Notify – Ack – get – Mssage, 也就是至少第四个应用包才能返回消息,在移动网络下成本很高。

    手机客户端不再Sync协议
    抓包分析版本:wifi、gprs网络状况下都相同,客户端会依次尝试使用80、8080、443 端口连接服务器;消息发送、接收都使用长连接进行.

    协议格式:

    4byte Packet Len(包含4字节本身)
    2byte Head Len(包含2字节本身) + 2byte Version(1) + 4byte Operation + 4byte SeqId + ….
    (Packet Len – Head Len) Body

    协议交互方式:

    客户端请求(一应一答,通过seqid匹配):seqid = 1 开始,依次递增,服务器回复相同的seqid 作为应答
    服务器推送通知(单向):seqid = 0,Operation = 7a,  客户端不需要应答

    主要业务:

    -心跳包:发起客户端请求,Operation = 0c,长度为16字节,算是最小的包
    -发消息:发起客户端请求,Operation = ed;单点在线时发完消息后,应答携带SyncKey,不再同步,多点在线时,通过通知同步SyncKey。
    -收消息:服务器推送消息,Operation = 7a, Body 中携带消息内容,抓包分析时,通过改变消息体大小,可能到接收方第一个包length 也会随之变化,可确认消息是push的。发起客户端单向请求,即消息的应答Ack。
    -加密:未分析,一般来说像whatsapp那样,使用 hash(密码/OTP + 长连接第一个请求获取RandomCode) 做RC4 的密钥

    APP抓包数据
    1)初始连接记录
    简单记录微信启动之后请求:

    11:20:35 dns查询weixin.qq.com 返回一组IP地址
    11:20:35 DNS查询 weixin.qq.com 返回一组IP地址,本次通信中,微信使用了最后一个IP作为TCP长连接的连接地址。
    11:20:35 http://dns.weixin.qq.com/cgi-bin/micromsg-bin/newgetdns?uin=0&clientversion=620888113&scene=0&net=1 用于请求服务器获得最优IP路径。服务器通过结算返回一个xml定义了域名:IP对应列表。仔细阅读,可看到微信已经开始了国际化的步伐:香港、加拿大、韩国等。
    11:20:35 获取到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) 返回一个名为“micromsgresp.dat”的附件,估计是未阅读的离线消息
    11:21:31 POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/whatsnews HTTP/1.1 (application/octet-stream) 大概是资讯、订阅更新等
    中间进行一些资源请求等,类似于GEThttp://wx.qlogo.cn/mmhead/Q3auHgzwzM7NR4TYFcoNjbxZpfO9aiaE7RU5lXGUw13SMicL6iacWIf2A/96,图片等一些静态资源都会被分配到qlogo.cn域名下面
    POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/downloadpackage HTTP/1.1 (application/octet-stream) 输出为dat文件。
    11:21:47 GET http://support.weixin.qq.com/cgi-bin/mmsupport-bin/reportdevice?channel=34&deviceid=A952001f7a840c2a&clientversion=620888113&platform=0&lang=zh_CN&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) 心跳频率约为5分钟,登陆之后,会建立一个长连接,端口号为8080

    2)初始消息传输
    个人资料、离线未阅读消息部分等通过 POST HTTP短连接单独获取。抽取微信某次HTTP协议方式通信数据,16进制表示,每两个靠近的数字为一个byte字节:

    3)微信协议可能如下:
    一个消息包 = 消息头 + 消息体

    消息头固定16字节长度,消息包长度定义在消息头前4个字节中。单纯摘取第0000行为例,共16个字节的头部:00 00 00 10 00 10 00 01 00 00 00 06 00 00 00 0f16进制表示,每两个紧挨着数字代表一个byte字节。
    微信消息包格式:

    前4字节表示数据包长度,可变值为16时,意味着一个仅仅包含头部的完整的数据包(可能表示着预先定义好的业务意义),后面可能还有会别的消息包
    2个字节表示头部长度,固定值,0x10 = 16
    2个字节表示协议版本,固定值,0x01 = 1
    4个字节操作说明数字,可变
    序列号,可变
    头部后面紧跟着消息体,非明文,加密形式
    一个消息包,最小16 byte字节

    4)新消息获取方式
    TCP长连接接收到服务器通知有新消息需要获取
    APP发起一个HTTP POST请求获取新状态消息,会带上当前SyncKey 地址为:http://short.weixin.qq.com/cgi-bin/micromsg-bin/reportstrategy HTTP/1.1,看不到明文
    APP获取到新的消息,会再次发起一次HTTP POST请求,告诉服务器已确认收到,同时获取最新SyncKey 地址为:http://short.weixin.qq.com/cgi-bin/micromsg-bin/kvreport,看不到明文
    接受一个消息,TCP长连接至少交互两次,客户端发起两次HTTP POST请求
    服务器需要支持:状态消息获取标记,状态消息确认收取标记。只有被确认收到,此状态消息才算是被正确消费掉
    多个不同设备同一账号同时使用微信,同一个状态消息会会被同时分发到多个设备上

    5)发送消息方式
    发送消息走已经建立的TCP长连接通道,发送消息到服务器,然后接受确认信息等,产生一次交互。
    小伙伴接收到信息阅读也都会收到服务器端通知,产生一次交互等。
    可以确定,微信发送消息走TCP长连接方式,因为不对自身状态数据产生影响,应该不交换SyncKey。
    在低速网络下,大概会看到消息发送中的提示,属于消息重发机制
    网络不好有时客户端会出现发送失败的红色感叹号
    已发送到服务器但未收到确认的消息,客户端显示红色感叹号,再次重发,服务器作为重复消息处理,反馈确认
    上传图片,会根据图片大小,分割成若干部分(大概5K被划分为一部分),同一时间点,客户端会发起若干次POST请求,各自上传成功之后,服务器大概会合并成一个完整图片,返回一个缩略图,显示在APP聊天窗口内。APP作为常规的文字消息发送到服务器端
    上传音频,则单独走TCP通道,一个两秒的录制音频,客户端录制完毕,分为两块传输,一块最大5K左右,服务端响应一条数据通知确认收到。共三次数据传输。
    音频和纯文字信息一致,都是走TCP长连接,客户端发送,服务器端确认。

    6)微信协议小结
    发布的消息对应一个ID(只要单个方向唯一即可,服务器端可能会根ID判断重复接收),消息重传机制确保有限次的重试,重试失败给予用户提示,发送成功会反馈确认,客户端只有收到确认信息才知道发送成功。发送消息可能不会产生新SyncKey。
    基于版本号(SynKey)的状态消息同步机制,增量、有序传输需求水到渠成。长连接通知/短连接获取、确认等,交互方式简单,确保了消息可靠谱、准确无误到达。
    客户端/服务器端都会存储消息ID处理记录,避免被重复消费客户端获取最新消息,但未确认,服务器端不会认为该消息被消费掉。下次客户端会重新获取,会查询当前消息是否被处理过。根据一些现象猜测。
    总体上看,微信协议跨平台(TCP或HTPP都可呈现,处理方式可统一),通过“握手”同步,很可靠,无论哪一个平台都可以支持的很好
    微信协议最小成本为16字节,大部分时间若干个消息包和在一起,批量传输。微信协议说不上最简洁,也不是最节省流量,但是非常成功的。
    若服务器检测到一些不确定因素,可能会导致微启用安全套接层SSL协议进行常规的TCP长连接传输。短连接都没有发生变化

    现在版本的微信消息推送,并非Sync方式,而是推送+ack方式;从他们协议设计来看,应该最开始用的是Notify + Sync Req + Sync Rsp 方式,因为协议上是不支持服务器发起请求的;现在改成Sync消息+ 单向Ack Req 的push方式,虽然协议上怪异, 相比Sync 消息接收会更加及时。从以前看到的文章都是说用的Sync协议,应该是是后期版本做了修改,push方式更为高效、而且通过顺序的SyncKey也能够修复丢失的消息。

    Web客户端使用比较标准的Sync协议
    web微信客户端,使用的是比较标准的Sync协议,Sync协议也比较适合web长轮询模型。

    移动客户端模式下,协议是二进制的而且有加密,很难分析;微信侧重手机端,web端主体协议应该保持与移动端一致,可通过web端推测整体协议实现,json也比较好分析。

    接收一条消息后SyncKey变化:

    synckey:1_624161340|2_624162225|3_624162051|11_624161867|201_1420112604|1000_1420104656
    synckey:1_624161340|2_624162226|3_624162051|11_624161867|201_1420112631|1000_1420104656

    可以看出:

    Synckey 有多个,应该是应对不同业务,其中2为为所有个人消息、讨论组消息,其他可能是联系人、朋友圈、订阅号等,201 为当前时间戳。
    SyncKey 的值为数字自增,不是从0开始,应该有个固定的初始值。
    发消息时,发完需要自己给再自己同步回一下SyncKey。
    消息增量同步结构,一堆要同步字段+是否修改FlagMask,同步协议变得很简洁。

    Web抓包数据
    1)发起GET长连接检测是否存在新的需要同步的数据,会携带上最新SyncKey
    https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?callback=jQuery18306073923335455973_1393208247730&r=1393209241862&sid=s7c%2FsxpGRSihgZAA&uin=937355&deviceid=e542565508353877&synckey=1_620943725%7C2_620943769%7C3_620943770%7C11_620942796%7C201_1393208420%7C202_1393209127%7C1000_1393203219&_=1393209241865

    返回内容:
    window.synccheck={retcode:”0″,selector:”2″}
    selector值大于0,表示有新的消息需要同步。据目测,心跳周期为27秒左右。
    2)一旦有新数据,客户端POST请求主动获取同步的数据
    https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=s7c%2FsxpGRSihgZAA&r=1393208447375

    携带消息体:
    {“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}
    会携带上最新的SyncKey,会返回复杂结构体JSON内容。但浏览端收取到消息之后,如何通知服务器端已确认收到了?Web版本微信,没有去做。

    3)发送消息流程
    发起一个POST提交,用于提交用户需要发送的消息
    https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?sid=lQ95vHR52DiaLVqo&r=1393988414386

    发送内容:
    {“BaseRequest”:{“Uin”:937355,”Sid”:”lQ95vHR52DiaLVqo”,”Skey”:”A6A1ECC6A7DE59DEFF6A05F226AA334DECBA457887B25BC6″,”DeviceID”:”e937227863752975″},”Msg”:{“FromUserName”:”yongboy”,”ToUserName”:”hehe057854″,”Type”:1,”Content”:”hello”,”ClientMsgId”:1393988414380,”LocalID”:1393988414380}}

    相应内容:
    {“BaseResponse”: {“Ret”: 0,”ErrMsg”: “”},”MsgID”: 1020944348,”LocalID”: “1393988414380”}

    再次发起一个POST请求,用于申请最新SyncKey
    https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=lQ95vHR52DiaLVqo&r=1393988414756

    发送内容:
    {“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}

    响应的(部分)内容:
    “SKey”: “8F8C6A03489E85E9FDF727ACB95C93C2CDCE9FB9532FC15B”
    终止GET长连接,使用最新SyncKey再次发起一个新的GET长连接
    https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?callback=jQuery1830245810089652082181393988305564&r=1393988415015&sid=lQ95vHR52DiaLVqo&uin=937355&deviceid=e937227863752975&synckey=1620944310%7C2620944348%7C3620944344%7C11620942796%7C2011393988357%7C10001393930108&=1393988415016

    微信多点登陆
    IM产品的多点登陆逻辑特别复杂,很难做到很好的用户体验。微信最开始并不支持多点登陆,后来陆续增加的Web版、Mac版,但并不是完整意义的客户端,要说只是辅助工具。微信允许:一个移动端(下面称之为主客户端) + 一个web/mac 同时在线(下面称之为从客户端),web/mac 只能接收在线消息、发消息,不记录消息历史。这样多点逻辑就变得相对简单很多了。
    1)存储
    服务器不用保存完整消息历史,通过客户端对push消息的ack保证消息送达,协议保证消息至少一次推送到主客户端,然后消息即可删除;服务器只存储未下发到主客户端的消息。
    多点时:主客户端依然采用Push推送消息(只是应该会保留一小段时间消息记录等待从Sync),从客户端Sync消息;如果主不在线,消息记录不会删除,等主重新连上下载离线消息。
    服务器不存储消息历史,一个是安全,再者节省硬件成本,大量短消息的存储和读取成本是非常高的,因为基本都是随机IO。whatapp 用500台机器,支撑1亿在线,100w/s 消息,只离线消息存储量是很少的。
    2)未读数同步
    这个很好解决,如果客户端知道自己处于多端在线情况下时,进入会话时,需要告诉服务器消息已读。消息已读也保存为一条消息,再通过Sync协议,同步到另外的客户端。web 微信会调用webwxstatusnotify 同步未读。未读变化也当成一条消息存储,且只在多端情况下存在,单点在线时未读数由客户端维护。
    3)删除消息
    不管是否多点在线任何删除消息操作都会同步到服务器,避免删除的消息下次有不小心同步回来了,服务器可能及时删除、也可能长期保存,客户端每次上报就没错了。移动客户端消息删除不会同步到 web版。
    4)自己同步自己
    每个操作(发消息、清未读等),应答后,因为SyncKey 变化了,Sync协议上会产生一个空的Sync操作用于更新SyncKey。对于主客户端:
    单点在线时,SyncKey通过应答,节省同步流量。
    多点在线时,发消息和从客户端一样,也会自己同步自己。
    高级模式
    B Color Image Link Quote Code Smilies

    本版积分规则

    手机版|Archiver|开发者俱乐部 ( ICP/ISP证:辽B-2-4-20110106号 IDC证:辽B-1-2-20070003号 )

    GMT+8, 2024-12-23 05:31 , Processed in 0.118830 second(s), 18 queries .

    X+ Open Developer Network (xodn.com)

    © 2009-2017 沈阳讯网网络科技有限公司

    快速回复 返回顶部 返回列表