本文要談的IM通信協議指的是應用層通信「語言」,並非指傳輸層協議(如TCP、UDP)。IM通信協議的制定是IM開發中起點,也是貫穿設計、開發、運維始終的核心所在,通信協議設計的好壞,直接影響後緒環節的用戶體驗(數據流量、耗電量、通信速度)、兼容性(新老版本的無縫融合)、擴展性(後緒的版本升級怎麼辦)等,是個基礎且極其重要的工作之一。
本文將以理論聯繫實際的方式,詳細講解一套典型IM的通信協議設計的方方面面。
本文原作者是58同城網的沈劍:58同城技術委員會主席、高級架構師、優秀講師。前百度hi團隊成員,負責過58同城im系統的架構設計。
IM通信協議的分層設計
所謂「協議」是雙方共同遵守的規則,例如:離婚協議,停戰協議。
協議有語法、語義、時序三要素:
- (1)語法:即數據與控制信息的結構或格式
- (2)語義:即需要發出何種控制信息,完成何種動作以及做出何種響應
- (3)時序:即事件實現順序的詳細說明
一套典型的IM通信協議設計分為三層:應用層、安全層、傳輸層。

IM應用層協議設計
應用層協議選型,常見的有三種:文本協議、二進位協議、流式XML協議。
1、文本協議
文本協議是指 「貼近人類書面語言表達」的通訊傳輸協議,典型的協議是http協議。一個http協議大致長成這樣:
1234GET / HTTP/1.1User-Agent: curlHost: musicml.netAccept: */*
文本協議的特點是:
- a. 可讀性好,便於調試
- b. 擴展性也好(通過key:value擴展)
- c. 解析效率一般(一行一行讀入,按照冒號分割,解析key和value)
- d. 對二進位的支持不好 ,比如語音/視頻
IM中,MSN使用的是文本協議。
2、二進位協議
二進位協議是指binary協議,典型是ip協議,以下是ip協議的一個圖示:

二進位協議一般定長包頭和可擴展變長包體 ,每個欄位固定了含義 ,例如IP協議的前4個bit表示協議版本號 (Version)(詳情請參見《TCP/IP詳解》第3章)。
二進位協議有這樣一些特點:
- a. 可讀性差,難於調試
- b. 擴展性不好 ,如果要擴展欄位,舊版協議就不兼容了,所以一般設計時會有一個Version欄位
- c. 解析效率超高(幾乎沒有解析代價)
- d. 對二進位的支持不好 ,比如語音/視頻
IM中,QQ使用的時二進位協議。
3、流式XML協議
IM的准標準協議xmpp就是使用流式XML,像gtalk,校內通這些im都是基於xmpp的。讓我們來看一個xmpp協議的例子:
1234567<messageto=』[url=mailto:romeo@example.net]romeo@example.net[/url]』from=』[url=mailto:juliet@example.com]juliet@example.com[/url]』type=』chat』xml : lang=』en』><body>Wherefore art thou, Romeo?</body></message>
從xml標籤中大致可以判斷這是一個romeo發給juliet的聊天消息。xmpp協議可以實現跨域的互通。例如gtalk和校內通用戶聊天。只要服務端實現了s2s服務(server to server) ,不過現在的im基本沒有互通需求 ,所以這個服務基本沒有人實現。
XMPP協議有幾個特點:
- a.它是准標準協議,可以跨域互通
- b.XML的優點,可讀性好,擴展性好
- c.解析代價超高(dom解析)
- d.有效數據傳輸率超低(大量的標籤)
個人旗幟鮮明的強烈不建議使用xmpp,特別是無線端im,如果要用,一定要自己做壓縮 ,減少網路流量(用過xmpp的同學都清楚,發一個登錄包需要多少交互,要浪費多少流量)。
4、實際的例子
下面來看一個im協議的實際例子 。一般常見的做法是:定長二進位包頭,可擴展變長包體。包體可以使用用文本、XML等擴展性好的協議。包頭負責傳輸和解析效率,與業務無關。包體保證擴展性,與業務相關。
這是一個實際的16位元組im二進位定長包頭:
123456789//sizeof(cs_essay-header)=16struct cs_essay-header{ uint32_t version; uint32_t magic_num; uint32_t cmd; uint32_t len; uint8_t data[];}__attribute__((packed));
– a. 前4個位元組是version;
– b. 接下來的4個位元組是個「魔法數字(magic_num)「,用來保證數據錯位或丟包問題,常見的做法是,包頭放幾個約定好的特殊字元,包尾放幾個約定好的特殊字元 約定好,發給你的協議,某幾個位元組位置,是0x 01020304 ,才是正常報文;
– c. 接下來是command(命令號),用來區分是keepalive報文、業務報文、密鑰交換報文等;
– d. len(包體長度),告知服務端要接收多長的包體。
這是一個實際的可擴展im變長包體:
123456789message CUserLoginReq{ optional string username = 1; optional string passwd = 2;}message CUserLoginResp{ optional uint64 uid =1;}
使用的是google的Protobuf協議(玩過的人都懂),可以看到,登錄請求包傳入的是用戶名與密碼,登錄響應包返回的是用戶的uid。當然,除了Protobuf,可選擇的可擴展包體協議還有xml、json、mcpack(大家懂?)等。
個人旗幟鮮明的推薦使用Protobuf,主要有幾個原因:
- a. 現成的解析庫種類多,可以生成C++、Java、php等10餘種語言代碼(詳見請點
- b. 自帶壓縮功能
- c. 在工業界已廣泛應用
- d. google製造
IM安全層協議設計
im協議,消息的保密性非常重要 ,誰都不希望自己聊天內容被看到,所以安全層是必不可少的。
1使用SSL
證書管理微微複雜,代價有點高。
2自行加解密
自己來搞加解密,核心在於密鑰的生成與管理,密鑰管理方式有多種,主要有這麼三種:(1)固定密鑰
服務端和客戶端約定好一個密鑰,同時約定好一個加密演算法(eg:AES ),每次客戶端im在發送前,就用約定好的演算法,以及約定好的密鑰加密再傳輸,服務端收到報文後,用約定好的演算法,約定好的密鑰再解密。這種方式,密鑰和演算法對程序員都是透明的。(2)一人一密鑰
簡單說來就是每個人的密鑰是固定的,但是每個人之間又不同,其實就是在固定密鑰的演算法中包含用戶的某一特殊屬性,比如用戶uid、手機號、qq號等。(3)動態密鑰(一session一密鑰)
動態密鑰,一Session一密鑰的安全性更高,每次會話前協商密鑰。密鑰協商的過程要經過2次非對稱密鑰的隨機生成,1次對稱加密密鑰的隨機生成,具體詳情這裡不展開,有興趣的同學可以看下SSL密鑰協商額過程。
IM傳輸層協議設計
可選的協議有TCP和UDP。現在的IM傳輸層基本都是使用TCP,有了epoll等技術後,多連接就不是瓶頸了,單機幾十萬鏈接沒什麼問題。58同城現在線上單機連接好像是10w?(可能單機性能測試可以到百萬,線上一般跑到幾十萬)
關於QQ使用UDP的問題。個人不清楚QQ使用UPD作為傳輸層協議的初衷,但猜測是因為10多年前Client 10K問題沒有得到很好解決,一台伺服器支撐不了1W個TCP連接 ,騰訊的同時在線量高,沒辦法,只有用UDP了,但UDP又不可靠,故只能在UDP上實現TCP的超時/重傳/確認等機制啦。
結語
關於QQ使用UDP協議,在討論的過程中,有同學提出了一個非常好的問題「無線環境下,UDP更好,可以做到狀態無關,而TCP不穩定,進出電梯就要斷線,用戶體驗不好」。
其實吧,「用戶狀態可以設計的與連接狀態無關」,如果大家感興趣,後續我可以撰文和大家聊一聊,傳輸層使用TCP,怎麼做到在線狀態與底層連接無關。
原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/282229.html