微信通讯协议的学习
微信協議概覽
微信傳輸協議,官方公布甚少,在微信技術總監所透漏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) 大概是資訊、訂閱更新等
- 中間進行一些資源請求等,類似于GET?http://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通過應答,節省同步流量。
- 多點在線時,發消息和從客戶端一樣,也會自己同步自己。
參考鏈接:
- http://www.cnblogs.com/lulu/p/4199770.html
- http://www.blogjava.net/yongboy/archive/2014/03/05/410636.html
- http://www.cnblogs.com/lulu/p/4199770.html
總結
- 上一篇: 面试自我介绍5句话公式
- 下一篇: windows7开机壁纸_如何在Wind