概述
在網絡編程中,無論使用netty還是其它的socket通訊框架,都是通過TCP或UDP傳輸二進制流。發送方把要發送的對象轉化成二進制流發送出去;接收方把接收到的二進制流轉化為對象進行處理。
為了能讓接收方和發送方能對同一個二進制流有相同的認識,雙方必須提前約定好一個協議,即對象如何轉化為二進制流,二進制流如何轉化為對象,這樣通信雙方才不會產生誤解。
自定義通信協議
在 easy-im 項目中,定義如下通信協議:
魔數:4字節,一般為固定值,本項目中使用0x88888888。一般我們的應用於某個端口對外開放,為了防止該端口被意外調用,我們可以在收到報文后,取前4個字節與魔數比對,如果不相同,則直接拒絕並關閉連接。
版本號: 1字節,一般是預留字段,為了支持協議升級(這種情況極少出現)。
序列化算法:1字節,表示如何將java對象轉化為二進制數據,以及如何反序列化。
指令:1字節,表示該消息的意圖,如私聊、群聊、登錄等。最多支持256種指令。
數據長度:4字節,表示該字段后數據部分的長度。
數據:具體數據的內容。每種指令對應的數據是不同的。
序列化算法
本項目為了簡單起見,使用json序列化算法。將Java對象轉換成json字符串,再轉化為二進制數據,代碼如下:
首先定義Serializer接口,serialize方法用於將對象序列化為二進制數據,deSerialize方法用於將二進制反序列化成對象。getSerializerAlgorithm方法返回序列化算法。
JsonSerializer是Serializer的實現。
指令設計
定義Packet類,作為所有指令的基類,其中getCommand方法為抽象方法,需由子類實現,返回具體的指令類型。
登錄指令如下,登錄時需要發送userId,useName,password等信息,以及指令command:
指令類型如下:
編解碼實現
定義好序列化算法和指令之后,就可以進行編解碼的實現了。編碼即將通信包轉化為二進制;解碼即將二進制轉化為通信包。
編碼過程比較簡單,代碼如下,參照注釋即可明白,ByteBuf里即是最后要發送的二進制數據:
這里暫時跳過魔數和版本校驗,獲取到序列化算法、指令、數據長度和數據內容。根據指令我們可以知道請求類型是什么(如登錄請求LoginRequestPacket、群聊請求GroupChatRequestPacket),根據序列化算法,將數據內容反序列化成目標對象,解碼結束。
可以看到,編碼與解碼是相反的過程。
總結
基於netty作為網絡通信基礎組件時,我們必須要做如下幾個步驟:
- 定義通訊協議,通信雙方需對此協議有一致的理解;
- 編碼,將Java對象轉化為二進制;
- 解碼,將二進制轉化為Java對象;
- 業務處理
如果采用http、websocket等公有協議通信,netty提供了許多類可以實現步驟1,2,3,無需我們編碼實現,只需調用相應的類和方法即可。
項目地址:https://github.com/sunnick/easy-im
參考文檔:
《netty入門實戰:仿寫微信IM及時通訊系統》
歡迎關注公眾號:程序員順仔和他的朋友們