基於Netty的Android系統IM簡單實現原理
基於Netty的Android系統IM簡單實現原理
基於Netty的Android系統IM簡單實現原理
最近在開發MobIM,實現了訊息傳輸和群等功能的IM功能。SDK功能包小,而功能全面。可以與原來的系統進行無縫整合。
自己抽空也實現了一套IM Server和IMClient的業務通訊模式。沒有實現複雜的UI介面,實現簡單的登入註冊,發訊息,收訊息。伺服器端與客戶端都使用Netty通訊。
Netty基於非阻塞(nio),事件驅動的網路應用程式框架和工具。
透過Netty面對大規模的併發請求可以處理的得心用手。用來替代原來的bio網路應用請求框架。
BIO通訊即平時使用的基於Socket,ServerSocket的InputStream和OutStream。
Netty神奇的地方在於是否是阻塞的。
while(true){//主執行緒死迴圈等待新連線到來 Socket socket = serverSocket.accept();//為新的連線建立新的執行緒,客戶端與伺服器上的執行緒數1:1 executor.submit(new ConnectIOnHandler(socket));
在BIO模型中,伺服器透過ServerSocket來開啟監聽,每當有請求的時候開啟一個執行緒來接受處理和維持狀態。這種思想在低併發,小吞吐的應用還可以應付,一旦遇到大併發,大吞吐的請求,必然歇菜。執行緒和客戶端保持著1:1的對應關係,維持著執行緒。維持那麼的多的執行緒,JVM必然不堪重負,伺服器必然崩潰,當機。
而在非阻塞的Netty中,卻可以應付自如。從容應對。Tomcat就是基於BIO的網路通訊模式(Tomcat可以透過一定配置,改成非阻塞模式),而JBoss卻是基於非阻塞的NIO實現。
NIO的網路通訊模式很強勁,但是上手卻一點都不容易。其中解決和牽扯到好多網路問題。如:網路延時,TCP的粘包/拆包,網路故障等一堆一堆的問題。而Netty呢,針對nio複雜的程式設計難題而進行一系列的封裝實現,提供給廣大開發者一套開源簡單,方便使用的API類庫,甚至青出於藍而勝於藍,甚至幾乎完美的解決CPU突然飆升到100%的bug : (其實也沒有真正的解決,只是把復現的機率降到了最低而已)。
用Netty來實現IM實在太合適了。可以在最短的時間裡整出一套思路清晰,架構簡明的IM通訊底層模型。提下需求,底層用JSON 字串String進行通訊,物件透過JSON序列化成JSON String。收到JSON資料後再反序列化成物件。
首先,我們先看伺服器是怎麼實現的。
private static final StringDecoder DECODER = new StringDecoder(); private static final StringEncoder ENCODER = new StringEncoder(); ... //boss執行緒監聽埠,worker執行緒負責資料讀寫 bossGroup = new NioEventLoopGroup(1); workerGroup = new NioEventLoopGroup(); //輔助啟動類 ServerBootstrap bootstrap = new ServerBootstrap(); try { //設定執行緒池 bootstrap.group(bossGroup, workerGroup); //設定socket工廠 bootstrap.channel(NioServerSocketChannel.class); bootstrap.handler(new LoggingHandler(LogLevel.INFO)); //設定管道工廠 bootstrap.childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { //獲取管道 ChannelPipeline pipe = socketChannel.pipeline(); // Add the text line codec combination first, pipe.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); // the encoder and decoder are static as these are sharable //字串編碼器 pipe.addLast(DECODER); //字串解碼器 pipe.addLast(ENCODER); //業務處理類 pipe.addLast(new IMServerHandle()); } }); //繫結埠 // Bind and start to accept incoming connections. ChannelFuture f = bootstrap.bind(port).sync(); if (f.isSuccess()) { Log.debug("server start success... port: " + port + ", main work thread: " + Thread.currentThread().getId()); } ////等待服務端監聽埠關閉 // Wait until the server socket is closed. f.channel().closeFuture().sync(); } finally { //優雅退出,釋放執行緒池資源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }
以上是Netty伺服器啟動的程式碼。其中需要注意childHandler方法。需要把我們要新增的業務處理handler來新增到這裡。透過ChannelPipeline 新增ChannelHandler。而處理字串的就在IMServerHandle裡實現。IMServerHandle繼承了SimpleChannelInboundHandler
看下IMServerHandle的實現方式。
/*** * 面向IM通訊操作的業務類 * @author xhj * */public class IMServerHandle extends SimpleChannelInboundHandler{ /** * user操作業務類 */ private UserBiz userBiz = new UserBiz(); /*** * 訊息操作的業務類 */ private IMMessageBiz immessagebiz = new IMMessageBiz(); /*** * 處理接受到的String型別的JSON資料 */ @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println(" get msg >> "+msg); //把JSON資料進行反序列化 Request req = JSON.parseObject(msg, Request.class); Response respon = new Response(); respon.setSendTime(System.currentTimeMillis()); //判斷是否是合法的請求 if(req != null ) { System.out.println("the req method >> "+req.getMethod()); //獲取操作型別 if(req.getMethod() == IMProtocol.LOGIN) { //獲取要操作的物件 User user = JSON.parseObject(req.getBody(),User.class); //設定返回資料的操作型別 respon.setMethod(IMProtocol.LOGIN); //執行業務操作 boolean bl = userBiz.login(user); if(bl) {//檢驗使用者有效 //設定響應資料 respon.setBody("login ok"); //設定狀態 respon.setStatus(0); //登入成功將連線channel儲存到Groups裡 ChannelGroups.add(ctx.channel()); //將使用者的uname和channelId進行繫結,伺服器向指定使用者傳送訊息的時候需要用到uname和channelId ChannelGroups.putUser(user.getUname(), ctx.channel().id()); //傳送廣播通知某人登入成功了 userBiz.freshUserLoginStatus(user); } else {//使用者密碼錯誤 //設定錯誤描述 respon.setErrorStr("pwd-error"); //設定狀態描述碼 respon.setStatus(-1); } //將Response序列化為json字串 msg = JSON.toJSONString(respon); //傳送josn字串資料,注意後面一定要加"rn" ctx.writeAndFlush(msg+"rn"); } else if(req.getMethod() == IMProtocol.SEND) { IMMessage immsg = JSON.parseObject(req.getBody(), IMMessage.class); immsg.setSendTime(System.currentTimeMillis()); c
透過IMServerHandle可以十分方便的處理獲取到的String字串。處理完後,可以直接透過ChannelHandlerContext的writeAndFlush方法傳送資料。
再看下Netty客戶端如何實現。
private BlockingQueuerequests = new LinkedBlockingQueue(); /** * String字串解碼器 */private static final StringDecoder DECODER = new StringDecoder(); /*** * String字串編碼器 */private static final StringEncoder ENCODER = new StringEncoder(); /** * 客戶端業務處理Handler */ private IMClientHandler clientHandler ; /** * 新增傳送請求Request * @param request */ public void addRequest(Request request) { try { requests.put(request); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 是否繼續進行執行 */ private boolean run = true; public void run() { //遠端IP String host = "172.20.10.7"; //埠號 int port = 10000; //工作執行緒 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { //輔助啟動類 Bootstrap b = new Bootstrap(); // (1) //設定執行緒池 b.group(workerGroup); // (2) //設定socket工廠 不是ServerSocket而是Socket b.channel(NioSocketChannel.class); // (3) b.handler(new LoggingHandler(LogLevel.INFO)); //設定管道工廠 b.handler(new ChannelInitializer () { public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipe = ch.pipeline(); // Add the text line codec combination first, pipe.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); // the encoder and decoder are static as these are sharable //字串解碼器 pipe.addLast(DECODER); //字串編碼器 pipe.addLast(ENCODER); clientHandler = new IMClientHandler(); //IM業務處理類 pipe.addLast(clientHandler); } }); // Start the client. ChannelFuture f = b.connect(host, port).sync(); // (5) Channel ch = f.channel(); ChannelFuture lastWriteFuture = null; while(run) { //將要傳送的Request轉化為JSON String型別 String line = JSON.toJSONString(requests.take()); if(line != null && line.length() > 0) {//判斷非空 // Sends the received line to the server. //傳送資料到伺服器 lastWriteFuture = ch.writeAndFlush(line + "rn"); } } // Wait until all messages are flushed before closing the channel. //關閉寫的埠 if (lastWriteFuture != null) { lastWriteFuture.sync(); } } catch(Exception ex){ ex.printStackTrace(); } finally { //優雅的關閉工作執行緒 workerGroup.shutdownGracefully(); } } /** * 增加訊息監聽接受介面 * @param messgeReceivedListener */ public void addMessgeReceivedListener(MessageSender.MessgeReceivedListener messgeReceivedListener) { clientHandler.addMessgeReceivedListener(messgeReceivedListener); } /*** * 移除訊息監聽介面 * @param messgeReceivedListener */ public void remove(MessageSender.MessgeReceivedListener messgeReceivedListener) { clientHandler.remove(messgeReceivedListener); }
Netty的client端實現和Server實現方式大同小異。比Server端要簡要些了。少一個NIOEventLoop。在Bootstrap 的handle方法中增加ChannelInitializer初始化監聽器,並增加了IMClientHandler的監聽操作。其中IMClientHandler具體處理伺服器返回的通訊資訊。
透過ChannelFuture 獲取Channel,透過Channel在一個迴圈裡傳送請求。如果訊息佇列BlockingQueue非空的時候,獲取Request併傳送。以上傳送,如何接受資料呢?接受到的json被反序列化直接變成了物件Response,對Response進行處理即可。
定義了一個訊息接受到的監聽介面。
public static interface MessgeReceivedListener { public void onMessageReceived(Response msg); public void onMessageDisconnect(); public void onMessageConnect(); }
在介面onMessageReceived方法裡直接對獲取成功的響應進行處理。
而伺服器端對某個客戶端進行傳送操作,把Channel新增到ChannelGroup裡,將uname和channelid對應起來。需要對某個使用者傳送訊息的時候透過uname獲取channelid,透過channelid從ChannelGroup裡獲取channel,透過channel傳送即可。
具體操作如下:
public void transformMessage(IMMessage message) { Channel channel = ChannelGroups.getChannel(ChannelGroups.getChannelId(message.getTo())); if(channel != null && channel.isActive()) { Response response = new Response(); response.setBody(JSON.toJSONString(message)); response.setStatus(0); response.setMethod(IMProtocol.REV); response.setSendTime(System.currentTimeMillis()); channel.writeAndFlush(JSON.toJSON(response)+"rn"); } }
ChannelGroups的程式碼實現:
public class ChannelGroups { private static final MapuserList = new ConcurrentHashMap(); private static final ChannelGroup CHANNEL_GROUP = new DefaultChannelGroup("ChannelGroups", GlobalEventExecutor.INSTANCE); public static void putUser(String uname,ChannelId id) { userList.put(uname,id); }
透過以上程式碼解析應該對IM的通訊模式有了比較全面的認識。具體實現過程可以下載原始碼進行檢視。歡迎大家反饋提出問題。
執行效果圖。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3402/viewspace-2812353/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 基於Netty,徒手擼IM(一):IM系統設計篇Netty
- 跟著原始碼學IM(九):基於Netty實現一套分散式IM系統原始碼Netty分散式
- 開源一個自用的Android IM庫,基於Netty+TCP+Protobuf實現。AndroidNettyTCP
- 基於Netty,從零開發IM(二):編碼實踐篇(im單聊功能)Netty
- netty 實現簡單的rpc呼叫NettyRPC
- 基於SpringBoot+Netty實現一個自己的推送服務系統Spring BootNetty
- Java使用Netty實現簡單的RPCJavaNettyRPC
- Netty實戰:設計一個IM框架就這麼簡單!Netty框架
- 基於websocket的簡單廣播系統Web
- 基於Netty的簡單IM(流程圖詳解互動過程、使用到的Handler以及心跳機制)Netty流程圖
- WinForm的Socket實現簡單的聊天室 IMORM
- 直播系統搭建,簡單實現Android應用的啟動頁Android
- 簡單的實現vue原理Vue
- 簡單的實現React原理React
- 基於IdentityServer4的OIDC實現單點登入(SSO)原理簡析IDEServer
- 簡單的Java實現Netty進行通訊JavaNetty
- 自己用 Netty 實現一個簡單的 RPCNettyRPC
- Android 基於Netty的訊息推送方案之概念和工作原理(二)AndroidNetty
- 簡單實現vuex原理Vue
- 簡單、好懂的Svelte實現原理
- Android中SharePreferences的簡單實現Android
- ServerSocket實現簡單的廣播系統Server
- Android 基於zxing的二維碼掃描功能的簡單實現及優化Android優化
- C++基於armadillo im2col的實現C++
- 基於long pull實現簡易的訊息系統參考
- Netty(二) 實現簡單Http伺服器NettyHTTP伺服器
- 基於Promise實現對Ajax的簡單封裝Promise封裝
- 基於 Formily 的表單設計器實現原理分析 ORM
- Redux 原理和簡單實現Redux
- MapReduce原理及簡單實現
- 簡單快遞系統 java實現Java
- 基於websocket單臺機器支援百萬連線分散式聊天(IM)系統Web分散式
- vue 實現原理及簡單示例實現Vue
- 通過Dapr實現一個簡單的基於.net的微服務電商系統(二十)——Saga框架實現思路分享微服務框架
- 線上直播系統原始碼,簡單實現Android應用的啟動頁原始碼Android
- 基於opencv實現簡單人臉檢測OpenCV
- 基於ES5`defineProperty` 實現簡單的 Mvvm框架MVVM框架
- 基於 electron 實現簡單易用的抓包、mock 工具Mock