<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Jack Jiang

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

    前言

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

    本文將演示的是一個基于MINA2的UDP服務(wù)端和一個標(biāo)準(zhǔn)UDP客戶端(Java實現(xiàn))雙向通信的完整例子。

    實際上,MINA2的官方代碼里有完整的UDP通信Demo代碼,但Demo里客戶端是需要依賴MINA2的客戶端庫的,而如果簡單地去掉MINA2的lib依賴,改為標(biāo)準(zhǔn)的Java UDP代碼,則服務(wù)端和客戶端的數(shù)據(jù)收發(fā)都會存在未知多余字節(jié)等。當(dāng)然,本文已解決了這些問題,請繼續(xù)往下閱讀。

    《NIO框架入門》系列文章

    有關(guān)MINA和Netty的入門文章很多,但多數(shù)都是復(fù)制、粘貼的未經(jīng)證實的來路不明內(nèi)容,對于初次接觸的人來說,一個可以運行且編碼規(guī)范的Demo,顯然要比各種“詳解”、“深入分析”之類的要來的直接和有意義。本系列入門文章正是基于此種考慮而寫,雖無精深內(nèi)容,但至少希望對初次接觸MINA、Netty的人有所啟發(fā),起到拋磚引玉的作用。


    本文是《NIO框架入門》系列文章中的第2篇,目錄如下:

    本文亮點

    • Demo中的代碼源自作者的開源工程,有實用價值:
      源碼均修改自作者的即時通訊開源工程 MobileIMSDK,只是為了方便學(xué)習(xí)理解而作了簡化,有一定的實用價值;
    • 本例中客戶端UDP實現(xiàn)無需第3方依賴:
      通常MINA或Netty的例子里,客戶端也都是用的MINA或Netty的客戶端lib,本文將直接使用標(biāo)準(zhǔn)UDP代碼(便于跨平臺實現(xiàn),比如iOS平臺),不依賴于第3方包;
    • 完整可執(zhí)行源碼、方便學(xué)習(xí):
      完整的Demo源碼,適合新手直接運行,便于學(xué)習(xí)和研究。

    Demo的代碼邏輯

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


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


    補充一句,本文中的客戶端代碼跟系列文章中的上篇《NIO框架入門(一):服務(wù)端基于Netty4的UDP雙向通信Demo演示》,用的是相同的代碼,如果您正在評估MIN2和Netty4的UDP服務(wù)端區(qū)別,可對照著上篇,進行詳細對照和分析哦。

    MINA2服務(wù)端準(zhǔn)備工作

    [1] 第一步:下載MINA2

    MINA2的官方網(wǎng)站是:http://mina.apache.org/,最新版本是 Mina 2.0.13,如下圖:


    補充說明:建議2進制和源碼都下載下來,2進制制包中是編譯好的jar包,源碼包中包含了官方的Demo源代碼,需要的時候可以對照著看看,對于最佳實踐來說,有一定的參考價值。


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

    一般情況下,只需要mina-core-2.0.13.jar包就行了,見下圖:


    補充說明:因為MINA2中使用了slf日框架,需要找到相應(yīng)的lib才能最終運行,具體下一段再說明。


    [3] 第三步:建好MINA2的服務(wù)端Java工程,準(zhǔn)備開擼

    個人習(xí)慣用Eclipse,你如用NetBeans或InteliJ也沒問題,具體建立過程不熟練的請自行百度吧,唯一注意的是把MINA2的lib包引用進來,我的見下圖:


    補充說明:MINA2中需要的slf日志框架的jar包,主要額外找到并導(dǎo)進來,不然不能最終運行哦。你也可以下載本Demo中的服務(wù)端源碼,直接用就是了。

    服務(wù)端代碼實現(xiàn)

    [1] 服務(wù)端主類 EchoServer.java:

     1 public class EchoServer

     2 {

     3         private static Logger logger = LoggerFactory.getLogger(EchoServer.class);

     4          

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

     6         {

     7                 // ** Acceptor設(shè)置

     8                 NioDatagramAcceptor acceptor = new NioDatagramAcceptor();

     9                 // 此行代碼能讓你的程序整體性能提升10倍

    10                 acceptor.getFilterChain()

    11                         .addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool())); 

    12                 // 設(shè)置MINA2的IoHandler實現(xiàn)類

    13                 acceptor.setHandler(new EchoSeverHandler());

    14                 // 設(shè)置會話超時時間(單位:毫秒),不設(shè)置則默認(rèn)是10秒,請按需設(shè)置

    15                 acceptor.setSessionRecycler(new ExpiringSessionRecycler(15 * 1000));

    16                  

    17                 // ** UDP通信配置

    18                 DatagramSessionConfig dcfg = acceptor.getSessionConfig();

    19                 dcfg.setReuseAddress(true);

    20                 // 設(shè)置輸入緩沖區(qū)的大小,壓力測試表明:調(diào)整到2048后性能反而降低

    21                 dcfg.setReceiveBufferSize(1024);

    22                 // 設(shè)置輸出緩沖區(qū)的大小,壓力測試表明:調(diào)整到2048后性能反而降低

    23                 dcfg.setSendBufferSize(1024);

    24              

    25                 // ** UDP服務(wù)端開始偵聽

    26                 acceptor.bind(new InetSocketAddress(9999));

    27              

    28                 logger.info("[IMCORE]UDP服務(wù)器正在端口 9999 上監(jiān)聽中...");

    29         }

    30 }

    補充說明:MINA2的UDP服務(wù)端看起來比Netty4的要繁瑣一些(有關(guān)Netty4的UDP服務(wù)端請見:http://www.52im.net/thread-367-1-1.html),實際上MINA2的代碼風(fēng)格更符合一般程序員的編碼習(xí)慣,更好懂一些,而Netty4因歷經(jīng)多個大版本的進化,看起來非常簡潔,但實現(xiàn)上并沒有MINA2看起來那么直觀。當(dāng)然,僅是個人理解,不當(dāng)之外請見諒。


    [2] 服務(wù)端Handler類 EchoSeverHandler.java:

     1 public class EchoSeverHandler extends IoHandlerAdapter

     2 {

     3     private static Logger logger = LoggerFactory.getLogger(EchoSeverHandler.class); 

     4     public static final CharsetDecoder decoder = (Charset.forName("UTF-8")).newDecoder();

     5          

     6     /**

     7      * MINA的異常回調(diào)方法。

     8      * <p>

     9      * 本類中將在異常發(fā)生時,立即close當(dāng)前會話。

    10      *

    11      * @param session 發(fā)生異常的會話

    12      * @param cause 異常內(nèi)容

    13      * @see IoSession#close(boolean)

    14      */

    15     @Override

    16     public void exceptionCaught(IoSession session, Throwable cause) throws Exception

    17     {

    18         logger.error("[IMCORE]exceptionCaught捕獲到錯了,原因是:"+cause.getMessage(), cause);

    19         session.close(true);

    20     }

    21          

    22     /**

    23      * MINA框架中收到客戶端消息的回調(diào)方法。

    24      * <p>

    25      * 本類將在此方法中實現(xiàn)完整的即時通訊數(shù)據(jù)交互和處理策略。

    26      * <p>

    27      * 為了提升并發(fā)性能,本方法將運行在獨立于MINA的IoProcessor之外的線程池中,

    28      * 詳見 {@link ServerLauncher#initAcceptor()}中的MINA設(shè)置代碼 。

    29      *

    30      * @param session 收到消息對應(yīng)的會話引用

    31      * @param message 收到的MINA的原始消息封裝對象,本類中是 {@link IoBuffer}對象

    32      * @throws Exception 當(dāng)有錯誤發(fā)生時將拋出異常

    33      */

    34     @Override

    35     public void messageReceived(IoSession session, Object message)throws Exception

    36     {

    37             //*********************************************** 接收數(shù)據(jù)

    38             // 讀取收到的數(shù)據(jù)

    39             IoBuffer buffer = (IoBuffer) message;

    40             String body = buffer.getString(decoder);

    41             // 注意:當(dāng)客戶使用不依賴于MINA庫的情況下,以下官方推

    42             // 薦的讀取方法會在數(shù)據(jù)首部出現(xiàn)幾個字節(jié)的未知亂碼

    43             // message.toString()

    44             logger.debug("【NOTE】>>>>>> 收到客戶端的數(shù)據(jù):"+body);

    45              

    46             //*********************************************** 回復(fù)數(shù)據(jù)

    47             String strToClient = "Hello,我是Server,我的時間戳是"+System.currentTimeMillis();

    48             byte[] res = strToClient.getBytes("UTF-8");

    49             // 組織IoBuffer數(shù)據(jù)包的方法:本方法才可以正確地讓客戶端UDP收到byte數(shù)組

    50             IoBuffer buf = IoBuffer.wrap(res); 

    51                  

    52             // 向客戶端寫數(shù)據(jù)

    53             WriteFuture future = session.write(buf); 

    54             // 在100毫秒超時間內(nèi)等待寫完成

    55             future.awaitUninterruptibly(100);

    56             // The message has been written successfully

    57             if( future.isWritten() )

    58             {

    59                         // send sucess!

    60             }

    61             // The messsage couldn't be written out completely for some reason.

    62             // (e.g. Connection is closed)

    63             else

    64             {

    65                         logger.warn("[IMCORE]回復(fù)給客戶端的數(shù)據(jù)發(fā)送失敗!");

    66             }

    67     }

    68 }


    [3] 重要說明

    注意點1:
    服務(wù)端的EchoSeverHandler.java文件中,接收數(shù)據(jù)時取得文本內(nèi)容的兩行代碼是解決無MINA2客戶端依賴的關(guān)鍵,具體代碼如下:

    IoBuffer buffer = (IoBuffer) message;

    String body = buffer.getString(decoder);

    而官方的代碼是直接:message.toString(),這會存在不能正確地解碼標(biāo)準(zhǔn)Java UDP代碼實現(xiàn)的客戶端發(fā)過來的數(shù)據(jù)問題哦。


    注意點2:
    服務(wù)端的EchoSeverHandler.java文件中,發(fā)送數(shù)據(jù)時的IoBuffer對象組織方法是解決無MINA2客戶端依賴的客戶端能正確接收MINA2服務(wù)端發(fā)過去的數(shù)據(jù)的關(guān)鍵,具體代碼如下:

    IoBuffer buf = IoBuffer.wrap(res);

    而官方的Demo代碼以及網(wǎng)上的各種實現(xiàn)更為復(fù)雜,但會導(dǎo)致客戶端接收數(shù)據(jù)有多余字節(jié),官方的會產(chǎn)生多余字節(jié)和亂碼的代碼如下:

    IoBuffer buf = IoBuffer.allocate(res.length);  

    buf.setAutoExpand(true); 

    buf.putInt(res.length);

    buf.put(res); 

    buf.flip();

    buf.shrink();

    客戶端代碼實現(xiàn)

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


    [1] 客戶端主類 EchoClient.java:

     1 public class EchoClient

     2 {

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

     4         {

     5                 // 初始化本地UDP的Socket

     6                 LocalUDPSocketProvider.getInstance().initSocket();

     7                 // 啟動本地UDP監(jiān)聽(接收數(shù)據(jù)用的)

     8                 LocalUDPDataReciever.getInstance().startup();

     9                  

    10                 // 循環(huán)發(fā)送數(shù)據(jù)給服務(wù)端

    11                 while(true)

    12                 {

    13                         // 要發(fā)送的數(shù)據(jù)

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

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

    16                          

    17                         // 開始發(fā)送

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

    19                         if(ok)

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

    21                         else

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

    23                          

    24                         // 3000秒后進入下一次循環(huán)

    25                         Thread.sleep(3000);

    26                 }

    27         }

    28 }

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


    [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本地監(jiān)聽端口(如果為0將表示由系統(tǒng)分配,否則使用指定端口)

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

    20                         // 調(diào)用connect之后,每次send時DatagramPacket就不需要設(shè)計目標(biāo)主機的ip和port了

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

    22                         // * 不然整send數(shù)據(jù)將會被錯誤地阻塞。這或許是官方API的bug,也或許是調(diào)

    23                         // * 用規(guī)范就應(yīng)該這樣,但沒有找到官方明確的說明

    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創(chuàng)建時出錯,原因是:" + e.getMessage(), e);

    32                 }

    33         }

    34 

    35         public DatagramSocket getLocalUDPSocket()

    36         {

    37                 return this.localUDPSocket;

    38         }

    39 }


    [3] 數(shù)據(jù)接收類 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監(jiān)聽停止了(socket被關(guān)閉了?)," + 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                         // 接收數(shù)據(jù)報的包

    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                         // 阻塞直到收到數(shù)據(jù)

    49                         localUDPSocket.receive(packet);

    50                          

    51                         // 解析服務(wù)端發(fā)過來的數(shù)據(jù)

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

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

    54                 }

    55         }

    56 }

    Demo執(zhí)行效果

    [1] 客戶端運行結(jié)果:



    [2] 服務(wù)端運行結(jié)果:



    補充說明:服務(wù)端的運行結(jié)果中,控制臺下每次多出的4行l(wèi)og輸出,實際上是MINA2內(nèi)部代碼的debug信息,目前除非關(guān)閉整個log的輸出,不然它一定會出現(xiàn),希望你在運行代碼時看到這莫名其妙的4行l(wèi)og內(nèi)容,不要感到奇怪哦,這4行l(wèi)og大致內(nèi)容如下所示: 

    [DEBUG] - [13:26:36.393]Event MESSAGE_RECEIVED has been fired for session 1 | (IoFilterEvent^fire:117)

    [DEBUG] - [13:26:36.393]Firing a MESSAGE_SENT event for session 1 | (IoFilterEvent^fire:68)

    [DEBUG] - [13:26:36.393]Event MESSAGE_SENT has been fired for session 1 | (IoFilterEvent^fire:117)

    [DEBUG] - [13:26:39.393]Firing a MESSAGE_RECEIVED event for session 1 | (IoFilterEvent^fire:68)

    結(jié)語

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


    如果你閱讀過本系列的上一篇《NIO框架入門(一):服務(wù)端基于Netty4的UDP雙向通信Demo演示》,應(yīng)該能明顯地感覺的出來MINA2的UDP服務(wù)端API接口使用要是Netty4的繁瑣,而且MINA2還存在獨立客戶端(非依賴于MINA2客戶端)實現(xiàn)時的多余字節(jié)和亂碼問題。但個人認(rèn)為MINA2的代碼風(fēng)格更符合一般程序員的編碼習(xí)慣,更好懂一些,而Netty4因歷經(jīng)多個大版本的進化,雖起來非常簡潔,但實現(xiàn)并不是那么直觀。當(dāng)然,至于MINA還是Netty,請客觀一評估和使用,因為二者并無本質(zhì)區(qū)別。

    更多學(xué)習(xí)資源

    [1] MINA和Netty的源碼在線學(xué)習(xí)和查閱:

    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] 更多有關(guān)NIO編程的資料:

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


    [4] 有關(guān)IM聊天應(yīng)用、消息推送技術(shù)的資料:

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


    [5] 技術(shù)交流和學(xué)習(xí):

    可直接進入 即時通訊開發(fā)者社區(qū) 討論和學(xué)習(xí)網(wǎng)絡(luò)編程、IM聊天應(yīng)用、消息推送應(yīng)用的開發(fā)。

    完整源碼工程下載

    如需完整Eclipse源碼工程請聯(lián)系作者,或者進入鏈接 http://www.52im.net/thread-373-1-1.html 自行下載。

    完整源碼工程截圖如下:


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



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


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


    網(wǎng)站導(dǎo)航:
     
    Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
    主站蜘蛛池模板: MM131亚洲国产美女久久| 精品国产免费观看一区| 性做久久久久久免费观看| 国产精品久久久久免费a∨| 免费激情视频网站| 亚洲AV日韩精品一区二区三区| 亚洲日韩国产成网在线观看| 久久综合九九亚洲一区| 亚洲啪啪免费视频| 精品久久久久亚洲| 美女巨胸喷奶水视频www免费| 色猫咪免费人成网站在线观看| 最近中文字幕免费mv视频7| 亚洲成A∨人片天堂网无码| 亚洲AV无码专区电影在线观看| 亚洲人成电影院在线观看| 美女免费视频一区二区| 国内精品久久久久影院免费| 性做久久久久久久免费看| 久久亚洲欧洲国产综合| 亚洲性猛交xx乱| 特级做a爰片毛片免费看| 99视频在线免费看| 国产精品成人四虎免费视频| 蜜桃视频在线观看免费视频网站WWW| 国产h视频在线观看免费| 亚洲国产精品嫩草影院久久| 久久久久亚洲精品日久生情| 国产精品日本亚洲777| 毛片无码免费无码播放| 国产乱色精品成人免费视频| 亚洲资源在线观看| 国产亚洲男人的天堂在线观看| 色欲色香天天天综合网站免费| 全黄性性激高免费视频| 亚洲视频在线观看网站| 成人免费夜片在线观看| 青草草色A免费观看在线| 日韩亚洲变态另类中文| 亚洲无人区码一二三码区别图片| 免费无码一区二区三区蜜桃|