Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 499, comments - 13, trackbacks - 0, articles - 1

           


          申明:本文由作者基于日常實踐整理,希望對初次接觸MINA、Netty的人有所啟發。如需與作者交流,見文簽名,互相學習。 



          學習交流

           

          • 更多學習資料:點此進入 推薦
          • 移動端即時通訊交流: 215891622 推薦

            

          前言

          NIO框架的流行,使得開發大并發、高性能的互聯網服務端成為可能。這其中最流行的無非就是MINA和Netty了,MINA目前的主要版本是MINA2、而Netty的主要版本是Netty3Netty4(Netty5已經被取消開發了:詳見此文)。


          本文將演示的是一個基于Netty4的UDP服務端和一個標準UDP客戶端(Java實現)雙向通信的完整例子。實際上,Netty4的UDP例子非常難找(恕我愚鈍,找遍全網也沒有有價值的代碼,有也是Netty3,而Netty3和Netty4的風格差的不是一點點,參考意義不大),官方的代碼演示里只有一個簡單的UDP廣播例子,不足以用于演示Netty4的UDP通信最佳實踐。

           

          本文亮點

          [1] Netty4的UDP例子太難找:

          Netty4的完整雙向UDP通信例子很難找(官方沒有),本文就是要用代碼來演示這個;

          [2] 本例中客戶端UDP實現無需第3方依賴:

          通常MINA或Netty的例子里,客戶端也都是用的MINA或Netty的客戶端lib,本文將直接使用標準UDP代碼(便于跨平臺實現,比如iOS平臺),不依賴于第3方包;

          [3] 完整可執行源碼、方便學習:

          完整的Demo源碼,適合新手直接運行,便于學習和研究。

           

          題外話

          社區里經常在爭論到底該用MINA還是Netty,比如這幾篇文章:《開源NIO框架八卦——到底是先有MINA還是先有Netty?》、《有關“為何選擇Netty”的11個疑問及解答》、《選Netty還是Mina:深入研究與對比(一)》,哈哈這確實挺難抉擇的。不過,個人觀點是,對那個熟悉就用哪個吧,沒什么好糾結的,必竟二者并比本質區別,也都是出自同一作者之手。

           

          寫個什么樣的Demo?

          言歸正傳,本文要演示的Demo包含兩部分,Java UDP客戶端和Netty4 UDP服務端,客戶端將每隔3秒向服務端發送一條消息,而服務端在收到消息后馬上回復一條消息給客戶端。


          也就是說,服務端和客戶端都要實現消息的發送和接收,這也就實現了雙向通信。如果有心的話,稍加改造,也就很容易實現一個簡陋的聊天程序了。下節將將給出真正的實現代碼。

           

          Netty4 服務端準備工作

          [1] 第一步:下載Netty4

          Netty4的官方網站是:http://netty.io/,直接下最新的Netty 4.1吧,如下圖:


           

          [2] 第二步:找到Netty4的核心庫文件

          直接用這個all in one的jar包吧,反正用在服務端,2M大小的東西無所謂,方便管理,見下圖:


           

          [3] 第三步:建好Java工程準備準備開擼

          個人習慣用Eclipse,你用NetBeans或InteliJ也都沒問題,具體建立過程不熟練的衣自行百度吧,唯一注意的是把Netty4的lib包引用進來就行了,我的見下圖:


           

          服務端代碼

          [1] 服務端主類 EchoServer.java


           1 public class EchoServer

           2 {

           3         public static void main(String[] args) throws InterruptedException

           4         {

           5                 Bootstrap b = new Bootstrap();

           6                 EventLoopGroup group = new NioEventLoopGroup();

           7                 b.group(group)

           8                         .channel(NioDatagramChannel.class)

           9                         .handler(new EchoSeverHandler());

          10                  

          11                 // 服務端監聽在9999端口

          12                 b.bind(9999).sync().channel().closeFuture().await();

          13         }

          14 }


          如上述代碼所示:不得不說,Netty4的UDP服務端代碼實現起來確實很簡單,一個 Bootstrap、一個 EventLoopGroup、外加一個SimpleChannelInboundHandler,就這么被Netty4輕松搞定(準確地說Netty4是在Java NIO上的封裝而已,但最終API對開發者來說確實很友好)。


          吐個槽:話說Netty4的代碼跟MINA相比,風格確實大不相同,雖說兩者有很深的源源,但經過Netty3、Netty4的進化,兩者的差異(至少代碼看起來是這樣)還是很明顯的。另外,b.bind(9999).sync().channel().closeFuture().await() 這一大串的連續方法調用,看起來很詭異,不小心調錯的話,服務器會不會爆炸?

           

          [2] 服務端Handler類 EchoSeverHandler.java


           1 public class EchoSeverHandler extends SimpleChannelInboundHandler<DatagramPacket> 

           2 {

           3         @Override

           4         protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)

           5                         throws Exception

           6         {

           7                 // 讀取收到的數據

           8                 ByteBuf buf = (ByteBuf) packet.copy().content();

           9                 byte[] req = new byte[buf.readableBytes()];

          10                 buf.readBytes(req);

          11                 String body = new String(req, CharsetUtil.UTF_8);

          12                 System.out.println("【NOTE】>>>>>> 收到客戶端的數據:"+body); 

          13                  

          14                 // 回復一條信息給客戶端

          15                 ctx.writeAndFlush(new DatagramPacket(

          16                 Unpooled.copiedBuffer("Hello,我是Server,我的時間戳是"+System.currentTimeMillis()

          17                                 , CharsetUtil.UTF_8)

          18                                 , packet.sender())).sync();

          19         }

          20 }


          如上所示,這個Handler看起來比MINA的類似實現,要簡潔不少。

           

          客戶端代碼

          為了讓客戶端代碼看起來更簡潔,我把Socket管理代碼提煉到 LocalUDPSocketProvider類、把UDP數據監聽和接收提煉到了 LocalUDPDataReciever類(實際上這兩個同名類是簡化自MobileIMSDK工程哦)。

           

          [1] 客戶端主類 EchoClient.java


          public class EchoClient

          {

                  public static void main(String[] args) throws Exception

                  {

                          // 初始化本地UDP的Socket

                          LocalUDPSocketProvider.getInstance().initSocket();

                          // 啟動本地UDP監聽(接收數據用的)

                          LocalUDPDataReciever.getInstance().startup();

                           

                          // 循環發送數據給服務端

                          while(true)

                          {

                                  // 要發送的數據

                                  String toServer = "Hi,我是客戶端,我的時間戳"+System.currentTimeMillis();

                                  byte[] soServerBytes = toServer.getBytes("UTF-8");

                                   

                                  // 開始發送

                                  boolean ok = UDPUtils.send(soServerBytes, soServerBytes.length);

                                  if(ok)

                                          Log.d("EchoClient", "發往服務端的信息已送出.");

                                  else

                                          Log.e("EchoClient", "發往服務端的信息沒有成功發出!!!");

                                   

                                  // 3000秒后進入下一次循環

                                  Thread.sleep(3000);

                          }

                  }

          }


          補充說明:客戶端代碼沒有使用任何依賴,純Java UDP代碼實現(如果是Andriod平臺,代碼也幾乎不用改就能用),部分代碼修改自 開源即時通訊框架 MobileIMSDK(去掉了很多保證健壯性代碼,現在看起來要簡潔的多,便于初學者學習)。

           

          [2] Socket操作類 LocalUDPSocketProvider.java


           1 public class LocalUDPSocketProvider

           2 {

           3         private static final String TAG = LocalUDPSocketProvider.class.getSimpleName();

           4         private static LocalUDPSocketProvider instance = null;

           5         private DatagramSocket localUDPSocket = null;

           6 

           7         public static LocalUDPSocketProvider getInstance()

           8         {

           9                 if (instance == null)

          10                         instance = new LocalUDPSocketProvider();

          11                 return instance;

          12         }

          13          

          14         public void initSocket()

          15         {

          16                 try

          17                 {

          18                         // UDP本地監聽端口(如果為0將表示由系統分配,否則使用指定端口)

          19                         this.localUDPSocket = new DatagramSocket(ConfigEntity.localUDPPort);

          20                         // 調用connect之后,每次send時DatagramPacket就不需要設計目標主機的ip和port了

          21                         // * 注意:connect方法一定要在DatagramSocket.receive()方法之前調用,

          22                         // * 不然整send數據將會被錯誤地阻塞。這或許是官方API的bug,也或許是調

          23                         // * 用規范就應該這樣,但沒有找到官方明確的說明

          24                         this.localUDPSocket.connect(

          25                                         InetAddress.getByName(ConfigEntity.serverIP), ConfigEntity.serverUDPPort);

          26                         this.localUDPSocket.setReuseAddress(true);

          27                         Log.d(TAG, "new DatagramSocket()已成功完成.");

          28                 }

          29                 catch (Exception e)

          30                 {

          31                         Log.w(TAG, "localUDPSocket創建時出錯,原因是:" + e.getMessage(), e);

          32                 }

          33         }

          34 

          35         public DatagramSocket getLocalUDPSocket()

          36         {

          37                 return this.localUDPSocket;

          38         }

          39 }


           

          [3] 數據接收類 LocalUDPDataReciever.java


           1 public class LocalUDPDataReciever

           2 {

           3         private static final String TAG = LocalUDPDataReciever.class.getSimpleName();

           4         private static LocalUDPDataReciever instance = null;

           5         private Thread thread = null;

           6 

           7         public static LocalUDPDataReciever getInstance()

           8         {

           9                 if (instance == null)

          10                         instance = new LocalUDPDataReciever();

          11                 return instance;

          12         }

          13 

          14         public void startup()

          15         {

          16                 this.thread = new Thread(new Runnable()

          17                 {

          18                         public void run()

          19                         {

          20                                 try

          21                                 {

          22                                         Log.d(LocalUDPDataReciever.TAG, "本地UDP端口偵聽中,端口=" + ConfigEntity.localUDPPort + "...");

          23 

          24                                         //開始偵聽

          25                                         LocalUDPDataReciever.this.udpListeningImpl();

          26                                 }

          27                                 catch (Exception eee)

          28                                 {

          29                                         Log.w(LocalUDPDataReciever.TAG, "本地UDP監聽停止了(socket被關閉了?)," + eee.getMessage(), eee);

          30                                 }

          31                         }

          32                 });

          33                 this.thread.start();

          34         }

          35 

          36         private void udpListeningImpl() throws Exception

          37         {

          38                 while (true)

          39                 {

          40                         byte[] data = new byte[1024];

          41                         // 接收數據報的包

          42                         DatagramPacket packet = new DatagramPacket(data, data.length);

          43 

          44                         DatagramSocket localUDPSocket = LocalUDPSocketProvider.getInstance().getLocalUDPSocket();

          45                         if ((localUDPSocket == null) || (localUDPSocket.isClosed()))

          46                                 continue;

          47                          

          48                         // 阻塞直到收到數據

          49                         localUDPSocket.receive(packet);

          50                          

          51                         // 解析服務端發過來的數據

          52                         String pFromServer = new String(packet.getData(), 0 , packet.getLength(), "UTF-8");

          53                         Log.w(LocalUDPDataReciever.TAG, "【NOTE】>>>>>> 收到服務端的消息:"+pFromServer);

          54                 }

          55         }

          56 }


           

          運行效果

          客戶端運行結果:



          服務端運行結果:


           

          補充說明

          客戶端代碼沒什么特別,就是標準的Java UDP代碼,看起來跟其它例子不一樣的原因只是我把它提煉了一下,沒本質區別。同樣的代碼改改也可以很好的用在Android端。實際上,上面的客戶端代碼就是從開源 MobileIMSDK 的Java端扒出來的,有興趣的也可以看看它的Android端Server端iOS端,簡化一下可以用作你自已的各種用途。

           

          更多學習資源

          [1] MINA和Netty的源碼在線學習和查閱:

          MINA-2.x地址是:http://docs.52im.net/extend/docs/src/mina2/

          MINA-1.x地址是:http://docs.52im.net/extend/docs/src/mina1/

          Netty-4.x地址是:http://docs.52im.net/extend/docs/src/netty4/

          Netty-3.x地址是:http://docs.52im.net/extend/docs/src/netty3/ 

           

          [2] MINA和Netty的API文檔在線查閱:

          MINA-2.x API文檔(在線版):http://docs.52im.net/extend/docs/api/mina2/

          MINA-1.x API文檔(在線版):http://docs.52im.net/extend/docs/api/mina1/

          Netty-4.x API文檔(在線版):http://docs.52im.net/extend/docs/api/netty4/

          Netty-3.x API文檔(在線版):http://docs.52im.net/extend/docs/api/netty3/


          [3] 更多有關NIO編程的資料:

          請進入精華資料專輯:http://www.52im.net/forum.php?mod=collection&action=view&ctid=9


          [4] 有關IM聊天應用、消息推送技術的資料:

          請進入精華資料專輯:http://www.52im.net/forum.php?mod=collection&op=all


          [5] 技術交流和學習:

          可直接進入 即時通訊開發者社區 討論和學習網絡編程、IM聊天應用、消息推送應用的開發。

           

          完整源碼工程下載

          不知如何上傳附件,如需完整Eclipse源碼工程請聯系作者,或者進入鏈接 http://www.52im.net/thread-367-1-1.html 自行下載。

          完整源碼工程截圖如下:

          (本文同步發布于:http://www.52im.net/thread-367-1-1.html)



          作者:Jack Jiang (點擊作者姓名進入Github)
          出處:http://www.52im.net/space-uid-1.html
          交流:歡迎加入即時通訊開發交流群 215891622
          討論:http://www.52im.net/
          Jack Jiang同時是【原創Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
          本博文 歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 北辰区| 饶河县| 财经| 万盛区| 定西市| 渝中区| 吉林市| 昆明市| 东城区| 萍乡市| 华容县| 中江县| 曲阳县| 枣强县| 临颍县| 宿松县| 远安县| 于都县| 赤壁市| 仙居县| 响水县| 泗洪县| 独山县| 桂东县| 明水县| 崇左市| 永定县| 普兰县| 卫辉市| 炉霍县| 集安市| 登封市| 肇庆市| 新闻| 镇江市| 淳安县| 永嘉县| 五莲县| 榕江县| 钦州市| 清远市|