歡迎轉載,轉載請註明出處:juejin.im/post/5c97ae…html
寫在前面
一直想寫一篇關於im即時通信分享的文章,無奈工做太忙,很難抽出時間。今天終於從公司離職了,打算好好休息幾天再從新找工做,趁時間空閒,決定靜下心來寫一篇文章,畢竟從前輩那裏學到了不少東西。工做了五年半,這三四年來一直在作社交相關的項目,有 直播、 即時通信、 短視頻分享、 社區論壇 等產品,深知即時通信技術在一個項目中的重要性,本着開源分享的精神,也趁這機會總結一下,因此寫下這篇文章,文中有不對之處歡迎批評與指正。java
本文將介紹:git
- Protobuf序列化
- TCP拆包與粘包
- 長鏈接握手認證
- 心跳機制
- 重連機制
- 消息重發機制
- 讀寫超時機制
- 離線消息
- 線程池
AIDL跨進程通訊
本想花一部分時間介紹一下利用AIDL實現多進程通訊,提高應用保活率,無奈這種方法在目前大部分Android新版本上已失效,並且也比較複雜,因此考慮再三,把AIDL這一部分去掉,須要瞭解的童鞋能夠私信我。github
先來看看效果: 數據庫
不想看文章的同窗能夠直接移步到Github fork源碼:github地址編程
接下來,讓咱們進入正題。json
爲何使用TCP?
這裏須要簡單解釋一下,TCP/UDP/WebSocket的區別。 這裏就很好地解釋了TCP/UDP的優缺點和區別,以及適用場景,簡單地總結一下:windows
-
優勢:設計模式
- TCP的優勢體如今穩定、可靠上,在傳輸數據以前,會有三次握手來創建鏈接,並且在數據傳遞時,有確認、窗口、重傳、擁塞控制機制,在數據傳完以後,還會斷開鏈接用來節約系統資源。
- UDP的優勢體如今快,比TCP稍安全,UDP沒有TCP擁有的各類機制,是一個無狀態的傳輸協議,因此傳遞數據很是快,沒有TCP的這些機制,被攻擊利用的機制就少一些,可是也沒法避免被攻擊。
-
缺點:緩存
- TCP缺點就是慢,效率低,佔用系統資源高,易被攻擊,TCP在傳遞數據以前要先創建鏈接,這會消耗時間,並且在數據傳遞時,確認機制、重傳機制、擁塞機制等都會消耗大量時間,並且要在每臺設備上維護全部的傳輸鏈接。
- UDP缺點就是不可靠,不穩定,由於沒有TCP的那些機制,UDP在傳輸數據時,若是網絡質量很差,就會很容易丟包,形成數據的缺失。
-
適用場景:
- TCP:當對網絡通信質量有要求時,好比HTTP、HTTPS、FTP等傳輸文件的協議, POP、SMTP等郵件傳輸的協議。
- UDP:對網絡通信質量要求不高時,要求網絡通信速度要快的場景。
至於WebSocket,後續可能會專門寫一篇文章來介紹。 綜上所述,決定採用TCP協議。
爲何使用Protobuf?
對於App網絡傳輸協議,咱們比較常見的、可選的,有三種,分別是json/xml/protobuf,老規矩,咱們先分別來看看這三種格式的優缺點:
-
優勢:
- json優勢就是較XML格式更加小巧,傳輸效率較xml提升了不少,可讀性還不錯。
- xml優勢就是可讀性強,解析方便。
- protobuf優勢就是傳輸效率快(聽說在數據量大的時候,傳輸效率比xml和json快10-20倍),序列化後體積相比Json和XML很小,支持跨平臺多語言,消息格式升級和兼容性還不錯,序列化反序列化速度很快。
-
缺點:
- json缺點就是傳輸效率也不是特別高(比xml快,但比protobuf要慢不少)。
- xml缺點就是效率不高,資源消耗過大。
- protobuf缺點就是使用不太方便。
在一個須要大量的數據傳輸的場景中,若是數據量很大,那麼選擇protobuf能夠明顯的減小數據量,減小網絡IO,從而減小網絡傳輸所消耗的時間。考慮到做爲一個主打社交的產品,消息數據量會很是大,同時爲了節約流量,因此採用protobuf是一個不錯的選擇。
爲何使用Netty?
首先,咱們來了解一下,Netty究竟是個什麼東西。網絡上找到的介紹:Netty是由JBOSS提供的基於Java NIO的開源框架,Netty提供異步非阻塞、事件驅動、高性能、高可靠、高可定製性的網絡應用程序和工具,可用於開發服務端和客戶端。
-
爲何不用Java BIO?
- 一鏈接一線程,因爲線程數是有限的,因此這樣很是消耗資源,最終也致使它不能承受高併發鏈接的需求。
- 性能低,由於頻繁的進行上下文切換,致使CUP利用率低。
- 可靠性差,因爲全部的IO操做都是同步的,即便是業務線程也如此,因此業務線程的IO操做也有可能被阻塞,這將致使系統過度依賴網絡的實時狀況和外部組件的處理能力,可靠性大大下降。
-
爲何不用Java NIO?
- NIO的類庫和API至關複雜,使用它來開發,須要很是熟練地掌握Selector、ByteBuffer、ServerSocketChannel、SocketChannel等。
- 須要不少額外的編程技能來輔助使用NIO,例如,由於NIO涉及了Reactor線程模型,因此必須必須對多線程和網絡編程很是熟悉才能寫出高質量的NIO程序。
- 想要有高可靠性,工做量和難度都很是的大,由於服務端須要面臨客戶端頻繁的接入和斷開、網絡閃斷、半包讀寫、失敗緩存、網絡阻塞的問題,這些將嚴重影響咱們的可靠性,而使用原生NIO解決它們的難度至關大。
- JDK NIO中著名的BUG--epoll空輪詢,當select返回0時,會致使Selector空輪詢而致使CUP100%,官方表示JDK1.6以後修復了這個問題,其實只是發生的機率下降了,沒有根本上解決。
-
爲何用Netty?
- API使用簡單,更容易上手,開發門檻低
- 功能強大,預置了多種編解碼功能,支持多種主流協議
- 定製能力高,能夠經過ChannelHandler對通訊框架進行靈活地拓展
- 高性能,與目前多種NIO主流框架相比,Netty綜合性能最高
- 高穩定性,解決了JDK NIO的BUG
- 經歷了大規模的商業應用考驗,質量和可靠性都有很好的驗證。
以上摘自:爲何要用Netty開發
- 爲何不用第三方SDK,如:融雲、環信、騰訊TIM?
這個就見仁見智了,有的時候,是由於公司的技術選型問題,由於用第三方的SDK,意味着消息數據須要存儲到第三方的服務器上,再者,可擴展性、靈活性確定沒有本身開發的要好,還有一個小問題,就是收費。好比,融雲免費版只支持100個註冊用戶,超過100就要收費,羣聊支持人數有限制等等...
Mina其實跟Netty很像,大部分API都相同,由於是同一個做者開發的。但感受Mina沒有Netty成熟,在使用Netty的過程當中,出了問題很輕易地能夠找到解決方案,因此,Netty是一個不錯的選擇。
好了,廢話很少說,直接開始吧。
準備工做
-
首先,咱們新建一個Project,在Project裏面再新建一個Android Library,Module名稱暫且叫作im_lib,如圖所示:
-
而後,分析一下咱們的消息結構,每條消息應該會有一個消息惟一id,發送者id,接收者id,消息類型,發送時間等,通過分析,整理出一個通用的消息類型,以下:
- msgId 消息id
- fromId 發送者id
- toId 接收者id
- msgType 消息類型
- msgContentType 消息內容類型
- timestamp 消息時間戳
- statusReport 狀態報告
- extend 擴展字段
根據上述所示,我整理了一個思惟導圖,方便你們參考:
這是基礎部分,固然,你們也能夠根據本身須要自定義比較適合本身的消息結構。 咱們根據自定義的消息類型來編寫proto文件。
而後執行命令(我用的mac,windows命令應該也差很少):
而後就會看到,在和proto文件同級目錄下,會生成一個java類,這個就是咱們須要用到的東東:
咱們打開瞄一眼:
東西比較多,不用去管,這是google爲咱們生成的protobuf類,直接用就行,怎麼用呢?直接用這個類文件,拷到咱們開始指定的項目包路徑下就能夠啦:
添加依賴後,能夠看到,MessageProtobuf類文件已經沒有報錯了,順便把netty的jar包也導進來一下,還有fastjson的:
建議用netty-all-x.x.xx.Final的jar包,後續熟悉了,能夠用精簡的jar包。 至此,準備工做已結束,下面,咱們來編寫java代碼,實現即時通信的功能。
封裝
爲何須要封裝呢?說白了,就是爲了解耦,爲了方便往後切換到不一樣框架實現,而無需處處修改調用的地方。舉個栗子,好比Android早期比較流行的圖片加載框架是Universal ImageLoader,後期由於某些緣由,原做者中止了維護該項目,目前比較流行的圖片加載框架是Picasso或Glide,由於圖片加載功能可能調用的地方很是多,若是不做一些封裝,早期使用了Universal ImageLoader的話,如今須要切換到Glide,那改動量將很是很是大,並且還頗有可能會有遺漏,風險度很是高。
那麼,有什麼解決方案呢?
很簡單,咱們能夠用工廠設計模式進行一些封裝,工廠模式有三種:簡單工廠模式、抽象工廠模式、工廠方法模式。在這裏,我採用工廠方法模式進行封裝,具體區別,能夠參見:通俗講講我對簡單工廠、工廠方法、抽象工廠三種設計模式的理解
咱們分析一下,ims(IM Service,下文簡稱ims)應該是有初始化、創建鏈接、重連、關閉鏈接、釋放資源、判斷長鏈接是否關閉、發送消息等功能,基於上述分析,咱們能夠進行一個接口抽象:
OnEventListener是與應用層交互的listener:
IMConnectStatusCallback是ims鏈接狀態回調監聽器:
而後寫一個Netty tcp實現類:
接下來,寫一個工廠方法:
封裝部分到此結束,接下來,就是實現了。
初始化
咱們先實現init(Vector serverUrlList, OnEventListener listener, IMSConnectStatusCallback callback)方法,初始化一些參數,以及進行第一次鏈接等:
其中,MsgDispatcher是消息轉發器,負責將接收到的消息轉發到應用層:
ExecutorServiceFactory是線程池工廠,負責調度重連及心跳線程:
鏈接及重連
resetConnect()方法做爲鏈接的起點,首次鏈接以及重連邏輯,都是在resetConnect()方法進行邏輯處理,咱們來瞄一眼:
能夠看到,非首次進行鏈接,也就是鏈接一個週期失敗後,進行重連時,會先讓線程休眠一段時間,由於這個時候也許網絡情況不太好,接着,判斷ims是否已關閉或者是否正在進行重連操做,因爲重連操做是在子線程執行,爲了不重複重連,須要進行一些併發處理。開始重連任務後,分四個步驟執行:
- 改變重連狀態標識
- 回調鏈接狀態到應用層
- 關閉以前打開的鏈接channel
- 利用線程池執行一個新的重連任務
ResetConnectRunnable是重連任務,核心的重連邏輯都放到這裏執行:
toServer()是真正鏈接服務器的地方:
initBootstrap()是初始化Netty Bootstrap:
注:NioEventLoopGroup線程數設置爲4,能夠知足QPS是一百多萬的狀況了,至於應用若是須要承受上千萬上億流量的,須要另外調整線程數。參考自:
netty實戰之百萬級流量NioEventLoopGroup線程數配置
接着,咱們來看看TCPChannelInitializerHanlder:
其中,
ProtobufEncoder和
ProtobufDecoder是添加對protobuf的支持,
LoginAuthRespHandler是接收到服務端握手認證消息響應的處理handler,
HeartbeatRespHandler是接收到服務端心跳消息響應的處理handler,
TCPReadHandler是接收到服務端其它消息後的處理handler,先不去管,咱們重點來分析下
LengthFieldPrepender和
LengthFieldBasedFrameDecoder,這就須要引伸到TCP的拆包與粘包啦。
TCP的拆包與粘包
-
什麼是TCP拆包?爲何會出現TCP拆包?
簡單地說,咱們都知道TCP是以「流」的形式進行數據傳輸的,並且TCP爲提升性能,發送端會將須要發送的數據刷入緩衝區,等待緩衝區滿了以後,再將緩衝區中的數據發送給接收方,同理,接收方也會有緩衝區這樣的機制,來接收數據。
拆包就是在socket讀取時,沒有完整地讀取一個數據包,只讀取一部分。
-
什麼是TCP粘包?爲何會出現TCP粘包?
同上。
粘包就是在socket讀取時,讀到了實際意義上的兩個或多個數據包的內容,同時將其做爲一個數據包進行處理。
引用網上一張圖片來解釋一下在TCP出現拆包、粘包以及正常狀態下的三種狀況,如侵請聯繫我刪除:
瞭解了TCP出現拆包/粘包的緣由,那麼,如何解決呢?一般來講,有如下四種解決方式:
- 消息定長
- 用回車換行符做爲消息結束標誌
- 用特殊分隔符做爲消息結束標誌,如\t、\n等,回車換行符其實就是特殊分隔符的一種。
- 將消息分爲消息頭和消息體,在消息頭中用字段標識消息總長度。
netty針對以上四種場景,給咱們封裝瞭如下四種對應的解碼器:
- FixedLengthFrameDecoder,定長消息解碼器
- LineBasedFrameDecoder,回車換行符消息解碼器
- DelimiterBasedFrameDecoder,特殊分隔符消息解碼器
- LengthFieldBasedFrameDecoder,自定義長度消息解碼器。
咱們用到的就是LengthFieldBasedFrameDecoder自定義長度消息解碼器,同時配合LengthFieldPrepender編碼器使用,關於參數配置,建議參考netty--最通用TCP黏包解決方案:LengthFieldBasedFrameDecoder和LengthFieldPrepender這篇文章,講解得比較細緻。咱們配置的是消息頭長度爲2個字節,因此消息包的最大長度須要小於65536個字節,netty會把消息內容長度存放消息頭的字段裏,接收方能夠根據消息頭的字段拿到此條消息總長度,固然,netty提供的LengthFieldBasedFrameDecoder已經封裝好了處理邏輯,咱們只須要配置lengthFieldOffset、lengthFieldLength、lengthAdjustment、initialBytesToStrip便可,這樣就能夠解決TCP的拆包與粘包,這也就是netty相較於原生nio的便捷性,原生nio須要本身處理拆包/粘包等問題。
長鏈接握手認證
接着,咱們來看看LoginAuthHandler和HeartbeatRespHandler:
-
LoginAuthRespHandler是當客戶端與服務端長鏈接創建成功後,客戶端主動向服務端發送一條登陸認證消息,帶入與當前用戶相關的參數,好比token,服務端收到此消息後,到數據庫查詢該用戶信息,若是是合法有效的用戶,則返回一條登陸成功消息給該客戶端,反之,返回一條登陸失敗消息給該客戶端,這裏,就是在接收到服務端返回的登陸狀態後的處理handler,好比:
能夠看到,當接收到服務端握手消息響應後,會從擴展字段取出status,若是status=1,則表明握手成功,這個時候就先主動向服務端發送一條心跳消息,而後利用Netty的IdleStateHandler讀寫超時機制,按期向服務端發送心跳消息,維持長鏈接,以及檢測長鏈接是否還存在等。
-
HeartbeatRespHandler是當客戶端接收到服務端登陸成功的消息後,主動向服務端發送一條心跳消息,心跳消息能夠是一個空包,消息包體越小越好,服務端收到客戶端的心跳包後,原樣返回給客戶端,這裏,就是收到服務端返回的心跳消息響應的處理handler,好比:
這個就比較簡單,收到心跳消息響應,無需任務處理,直接打印一下方便咱們分析便可。
心跳機制及讀寫超時機制
心跳包是按期發送,也能夠本身定義一個週期,好比Android微信智能心跳方案,爲了簡單,此處規定應用在前臺時,8秒發送一個心跳包,切換到後臺時,30秒發送一次,根據本身的實際狀況修改一下便可。心跳包用於維持長鏈接以及檢測長鏈接是否斷開等。
接着,咱們利用Netty的讀寫超時機制,來實現一個心跳消息管理handler:
能夠看到,利用
userEventTriggered()方法回調,經過IdleState類型,能夠判斷讀超時/寫超時/讀寫超時,這個在添加
IdleStateHandler時能夠配置,下面會貼上代碼。首先咱們能夠在READER_IDLE事件裏,檢測是否在規定時間內沒有收到服務端心跳包響應,若是是,那就觸發重連操做。在WRITER_IDEL事件能夠檢測客戶端是否在規定時間內沒有向服務端發送心跳包,若是是,那就主動發送一個心跳包。發送心跳包是在子線程中執行,咱們能夠利用以前寫的work線程池進行線程管理。
addHeartbeatHandler()代碼以下:
從圖上可看到,在
IdleStateHandler裏,配置的讀超時爲心跳間隔時長的3倍,也就是3次心跳沒有響應時,則認爲長鏈接已斷開,觸發重連操做。寫超時則爲心跳間隔時長,意味着每隔heartbeatInterval會發送一個心跳包。讀寫超時沒用到,因此配置爲0。
onConnectStatusCallback(int connectStatus)爲鏈接狀態回調,以及一些公共邏輯處理:
鏈接成功後,當即發送一條握手消息,再次梳理一下總體流程:
- 客戶端根據服務端返回的host及port,進行第一次鏈接。
- 鏈接成功後,客戶端向服務端發送一條握手認證消息(1001)
- 服務端在收到客戶端的握手認證消息後,從擴展字段裏取出用戶token,到本地數據庫校驗合法性。
- 校驗完成後,服務端把校驗結果經過1001消息返回給客戶端,也就是握手消息響應。
- 客戶端收到服務端的握手消息響應後,從擴展字段取出校驗結果。若校驗成功,客戶端向服務端發送一條心跳消息(1002),而後進入心跳發送週期,按期間隔向服務端發送心跳消息,維持長鏈接以及實時檢測鏈路可用性,若發現鏈路不可用,等待一段時間觸發重連操做,重連成功後,從新開始握手/心跳的邏輯。
看看TCPReadHandler收到消息是怎麼處理的:
能夠看到,在
channelInactive()及
exceptionCaught()方法都觸發了重連,
channelInactive()方法在當鏈路斷開時會調用,
exceptionCaught()方法在當出現異常時會觸發,另外,還有諸如
channelUnregistered()、
channelReadComplete()等方法能夠重寫,在這裏就不貼了,相信聰明的你一眼就能看出方法的做用。
咱們仔細看一下channelRead()方法的邏輯,在if判斷裏,先判斷消息類型,若是是服務端返回的消息發送狀態報告類型,則判斷消息是否發送成功,若是發送成功,從超時管理器中移除,這個超時管理器是幹嗎的呢?下面講到消息重發機制的時候會詳細地講。在else裏,收到其餘消息後,會立馬給服務端返回一個消息接收狀態報告,告訴服務端,這條消息我已經收到了,這個動做,對於後續須要作的離線消息會有做用。若是不須要支持離線消息功能,這一步能夠省略。最後,調用消息轉發器,把接收到的消息轉發到應用層便可。
代碼寫了這麼多,咱們先來看看運行後的效果,先貼上缺失的消息發送代碼及ims關閉代碼以及一些默認配置項的代碼。
發送消息:
關閉ims:
ims默認配置:
還有,應用層實現的ims client啓動器:
因爲代碼有點多,不太方便所有貼上,若是有興趣能夠下載demo體驗。 額,對了,還有一個簡易的服務端代碼,以下:
調試
咱們先來看看鏈接及重連部分(因爲錄製gif比較麻煩,體積較大,因此我先把重連間隔調小成3秒,方便看效果)。
- 啓動服務端:
- 啓動客戶端:
能夠看到,正常的狀況下已經鏈接成功了,接下來,咱們來試一下異常狀況,好比服務端沒啓動,看看客戶端的重連狀況:
此次咱們先啓動的是客戶端,能夠看到鏈接失敗後一直在進行重連,因爲錄製gif比較麻煩,在第三次鏈接失敗後,我啓動了服務端,這個時候客戶端就會重連成功。
而後,咱們再來調試一下握手認證消息即心跳消息:
能夠看到,長鏈接創建成功後,客戶端會給服務端發送一條握手認證消息(1001),服務端收到握手認證消息會,給客戶端返回了一條握手認證狀態消息,客戶端收到握手認證狀態消息後,即啓動心跳機制。gif不太好演示,下載demo就能夠直觀地看到。
接下來,在講完消息重發機制及離線消息後,我會在應用層作一些簡單的封裝,以及在模擬器上運行,這樣就能夠很直觀地看到運行效果。
消息重發機制
消息重發,顧名思義,即便對發送失敗的消息進行重發。考慮到網絡環境的不穩定性、多變性(好比從進入電梯、進入地鐵、移動網絡切換到wifi等),在消息發送的時候,發送失敗的機率其實不小,這時消息重發機制就頗有必要了。
咱們先來看看實現的代碼邏輯。 MsgTimeoutTimer:
MsgTimeoutTimerManager:
而後,咱們看看收消息的TCPReadHandler的改造:
最後,看看發送消息的改造:
說一下邏輯吧:發送消息時,除了心跳消息、握手消息、狀態報告消息外,消息都加入消息發送超時管理器,立馬開啓一個定時器,好比每隔5秒執行一次,共執行3次,在這個週期內,若是消息沒有發送成功,會進行3次重發,達到3次重發後若是仍是沒有發送成功,那就放棄重發,移除該消息,同時經過消息轉發器通知應用層,由應用層決定是否再次重發。若是消息發送成功,服務端會返回一個消息發送狀態報告,客戶端收到該狀態報告後,從消息發送超時管理器移除該消息,同時中止該消息對應的定時器便可。
另外,在用戶握手認證成功時,應該檢查消息發送超時管理器裏是否有發送超時的消息,若是有,則所有重發:
離線消息
因爲離線消息機制,須要服務端數據庫及緩存上的配合,代碼就不貼了,太多太多,我簡單說一下實現思路吧: 客戶端A發送消息到客戶端B,消息會先到服務端,由服務端進行中轉。這個時候,客戶端B存在兩種狀況:
- 1.長鏈接正常,就是客戶端網絡環境良好,手機有電,應用處在打開的狀況。
- 2.廢話,那確定就是長鏈接不正常咯。這種狀況有不少種緣由,好比wifi不可用、用戶進入了地鐵或電梯等網絡很差的場所、應用沒打開或已退出登陸等,總的來講,就是沒有辦法正常接收消息。
若是是長鏈接正常,那沒什麼可說的,服務端直接轉發便可。
若是長鏈接不正常,須要這樣處理:服務端接收到客戶端A發送給客戶端B的消息後,先給客戶端A回覆一條狀態報告,告訴客戶端A,我已經收到消息,這個時候,客戶端A就不用管了,消息只要到達服務端便可。而後,服務端先嚐試把消息轉發到客戶端B,若是這個時候客戶端B收到服務端轉發過來的消息,須要立馬給服務端回一條狀態報告,告訴服務端,我已經收到消息,服務端在收到客戶端B返回的消息接收狀態報告後,即認爲此消息已經正常發送,不須要再存庫。若是客戶端B不在線,服務端在作轉發的時候,並無收到客戶端B返回的消息接收狀態報告,那麼,這條消息就應該存到數據庫,直到客戶端B上線後,也就是長鏈接創建成功後,客戶端B主動向服務端發送一條離線消息詢問,服務端在收到離線消息詢問後,到數據庫或緩存去查客戶端B的全部離線消息,並分批次返回,客戶端B在收到服務端的離線消息返回後,取出消息id(如有多條就取id集合),經過離線消息應答把消息id返回到服務端,服務端收到後,根據消息id從數據庫把對應的消息刪除便可。
以上是單聊離線消息處理的狀況,羣聊有點不一樣,羣聊的話,是須要服務端確認羣組內全部用戶都收到此消息後,才能從數據庫刪除消息,就說這麼多,若是須要細節的話,能夠私信我。
不知不覺,NettyTcpClient中定義了不少變量,爲了防止你們不明白變量的定義,仍是貼上代碼吧:
應用層封裝
這個就見仁見智啦,每一個人代碼風格不一樣,我把本身簡單封裝的代碼貼上來吧:
MessageProcessor消息處理器:
IMSEventListener與ims交互的listener:
MessageBuilder消息轉換器:
AbstractMessageHandler抽象的消息處理handler,每一個消息類型對應不一樣的messageHandler:
SingleChatMessageHandler單聊消息處理handler:
GroupChatMessageHandler羣聊消息處理handler:
MessageHandlerFactory消息handler工廠:
MessageType消息類型枚舉:
IMSConnectStatusListenerIMS鏈接狀態監聽器:
因爲每一個人代碼風格不一樣,封裝代碼都有本身的思路,因此,在此就不過多講解,只是把本身簡單封裝的代碼所有貼上來,做一個參考便可。只須要知道,接收到消息時,會回調OnEventListener的dispatchMsg(MessageProtobuf.Msg msg)方法:
發送消息須要調用imsClient的sendMsg(MessageProtobuf.Msg msg)方法:
便可,至於怎樣去封裝得更好,你們自由發揮吧。
最後,爲了測試消息收發是否正常,咱們須要改動一下服務端:
能夠看到,當有用戶握手成功後,會保存該用戶對應的channel到容器裏,給用戶發送消息時,根據用戶id從容器裏取出對應的channel,利用該channel發送消息。當用戶斷開鏈接後,會把該用戶對應的channel從容器裏移除掉。
運行一下,看看效果吧:
- 首先,啓動服務端。
- 而後,修改客戶端鏈接的ip地址爲192.168.0.105(這是我本機的ip地址),端口號爲8855,fromId,也就是userId,定義成100001,toId爲100002,啓動客戶端A。
- 再而後,fromId,也就是userId,定義成100002,toId爲100001,啓動客戶端B。
- 客戶端A給客戶端B發送消息,能夠看到在客戶端B的下面,已經接收到了消息。
- 用客戶端B給客戶端A發送消息,也能夠看到在客戶端A的下面,也已經接收到了消息。 至於,消息收發測試成功。至於羣聊或重連等功能,就不一一演示了,仍是那句話,下載demo體驗一下吧。。。
因爲gif錄製體積較大,因此只能簡單演示一下消息收發,具體下載demo體驗吧。。。
若是有須要應用層UI實現(就是聊天頁及會話頁的封裝)的話,我再分享出來吧。
github地址
發現的bug
- MsgTimeoutTimer:
這個bug是本身在檢查代碼時發現的,多是連續熬幾天夜寫文章魔怔了。。。 修改以下:
一我的精力有限,你們在使用過程當中,若是發現其它bug,煩請告訴我,反正我是會虛心接受,堅定不改,呸,必定改,必定改。另外,歡迎fork,期待你們與我一塊兒完善。。。
寫在最後
終於寫完了,這篇文章大概寫了10天左右,有很大部分的緣由是本身有拖延症,每次寫完一小段,總靜不下心來寫下去,致使一直拖到如今,之後得改改。第一次寫技術分享文章,有不少地方也許邏輯不太清晰,因爲篇幅有限,也只是貼了部分代碼,建議你們把源碼下載下來看看。一直想寫這篇文章,之前在網上也嘗試過找過不少im方面的文章,都找不到一篇比較完善的,本文談不上完善,但包含的模塊不少,但願起到一個拋磚引玉的做用,也期待着你們跟我一塊兒發現更多的問題並完善,最後,若是這篇文章對你有用,但願在github上給我一個star哈。。。
應你們要求,精簡了netty-all-4.1.33.Final.jar包。原netty-all-4.1.33.Final.jar包大小爲3.9M,經測試發現目前im_lib庫只須要用到如下jar包:
- netty-buffer-4.1.33.Final.jar
- netty-codec-4.1.33.Final.jar
- netty-common-4.1.33.Final.jar
- netty-handler-4.1.33.Final.jar
- netty-resolver-4.1.33.Final.jar
- netty-transport-4.1.33.Final.jar
因此,抽取以上jar包,從新打成了netty-tcp-4.1.33-1.0.jar,目前自測沒有問題,若是發現bug,請告訴我,謝謝。
附上原jar及裁剪後jar包的大小對比:
代碼已更新到Github.
接下來,會抽時間把下圖想寫的文章都寫了,沒有前後順序,想到哪就寫到哪吧。。。
另外,建立了一個Android即時通信技術交流QQ羣:1015178804,有須要的同窗能夠加進來,不懂的問題,我會盡可能解答,一塊兒學習,一塊兒成長。
The end.