【原創】NIO框架入門(四):Android與MINA2、Netty4的跨平台UDP雙向通信實戰


概述

本文演示的是一個Android客戶端程序,通過UDP協議與兩個典型的NIO框架服務端,實現跨平台雙向通信的完整Demo

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

本文中,服務端將分別用MINA2和Netty4進行實現,但在你實際的項目中服務端實現只需選其一就行了。本文中的Demo同時用MINA2和Netty4分別實現服務端的目的,是因為很多人都在糾結到底是用MINA還是Netty來實現高並發的Java網絡通信服務端,在此干脆兩個都實現了,就看你怎么選擇。

實際上,MINA2和Netty4的官方代碼里有UDP通信的Demo代碼,但卻不存在針對移動端(主要是Android和iOS端)的Demo,本文將演示用Android客戶端來實現這種跨平台的雙向網絡通信。Demo中,已經解決跨平台通信時的常見的亂碼、數據字節異常等問題,如覺得有用,你可直接使用之。

學習交流

- 更多即時通訊技術資料:http://www.52im.net/forum.php?mod=collection&op=all

- 移動端即時通訊交流群:215891622 推薦

《NIO框架入門》系列文章目錄

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

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

本篇亮點

  • 客戶端基於Android移動端平台:
    直接使用Android的標准UDP代碼,不依賴第3方包,且已解決與Java NIO服務端的跨平台通信問題,是個難得的Android端實踐入門示例;
  • 完整可執行源碼、方便學習:
    完整的Demo源碼,適合新手直接運行,便於學習和研究。
  • Demo中的代碼源自作者的開源工程,有實用價值:
    源碼均修改自作者的即時通訊開源工程 MobileIMSDK,只是為了方便學習理解而作了簡化,有一定的實用價值;

本文中Demo演示的功能

本文中的Demo代碼實現包含兩部分,Android UDP客戶端和NIO框架實現的服務端(包括MINA2和Netty4實現兩個方案),客戶端每隔5秒向服務端發送消息,而服務端在收到消息后馬上回復一條消息給客戶端。

如上所述,服務端(PC服務器)和客戶端(Android移動端)都要實現消息的發送和接收,即實現跨平台的雙向通信。下節將將給出真正的實現代碼。

Android客戶端准備工作

[Step 1]:准備好開發環境

這兩年,Google官方已經基本放棄Eclipse+ADT這樣的IDE組合,轉而大力開發Android Studio,但不得不承認,由於我的OS仍然是XP(Android Studio不支持XP),所以Eclipse+ADT還得繼續用(這個組合雖然一直被吐槽,但又不得不用)。

如果你習慣使用Eclipse+ADT這樣的IDE,可以下載我打好包的版本,內含Eclipse4.2+ADT+Android SDK:


如果你需要Android Studio,可進入此鏈接下載

[Step 2]:新建一個普通的Android工程,准備開擼

本文以Eclipse+ADT為開發Android開發工具(如你使用Android Studio道理也是一樣的),按照提示新建工程即可,無需特殊的設置或其它前前置條件。

我建好的工程,如下圖所示(很多都是默認生成的,用不上的東西就別去管它了):


補充說明:因為需要進行網絡通信,建好的工程里,請務必在 AndroidManifest.xml 加上網絡權限的許可,如下圖:

Android客戶端代碼實現

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

/*
 * Copyright (C) 2016 即時通訊網(52im.net) - 即時通訊開發者社區.
 * All rights reserved.
 */
package net.x52im.example.android.udp;
 
import net.x52im.example.android.udp.utils.UDPUtils;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
 
/**
 * Demo主類。
 * 
 * @author jack.jiang@52im.net, 2016-06-27
 * @version 1.0
 */
public class MainActivity extends ActionBarActivity 
{
        private final static String TAG = MainActivity.class.getSimpleName();
        // 重復發送的時間間隔(單位:毫秒)
        public static int INTERVAL = 5000;
        private Handler handler = null;
        private Runnable runnable = null;
 
        @Override
        protected void onCreate(Bundle savedInstanceState) 
        {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
 
                // 初始化本地UDP的Socket
                LocalUDPSocketProvider.getInstance().initSocket();
                // 啟動本地UDP監聽(接收數據用的)
                LocalUDPDataReciever.getInstance(this).startup();
 
                // 自動循環發送
                handler = new Handler();
                runnable = new Runnable(){
                        @Override
                        public void run()
                        {
                                sendMessageToServer();
                                 
                                // 開始下一次循環
                                handler.postDelayed(runnable, INTERVAL);
                        }
                };
                 
                // 立即開始發送
                handler.postDelayed(runnable, 0);
        }
 
        private void sendMessageToServer()
        {
                try
                {
                        // 要發送的數據
                        String toServer = "Hi,我是客戶端,我的時間戳"+System.currentTimeMillis();
                        byte[] soServerBytes = toServer.getBytes("UTF-8");
                        // 開始發送
                        boolean ok = UDPUtils.send(soServerBytes, soServerBytes.length);
                        if(ok)
                                Log.d(TAG, "發往服務端的信息已送出.");
                        else
                                Log.e(TAG, "發往服務端的信息沒有成功發出!!!");
                }
                catch (Exception e)
                {
                        Log.w(TAG, e.getMessage(), e);
                }
        }
}

補充說明:本類沒有去寫UI代碼,只是作為本次Demo的主入口類而已,需要查看數據輸出的,請在Eclipse下的DDMS控制台看查看log輸出哦。

[2] 客戶端本地 UDP Socket 管理類 LocalUDPSocketProvider.java:

/*
 * Copyright (C) 2016 即時通訊網(52im.net) - 即時通訊開發者社區.
 * All rights reserved.
 */
package net.x52im.example.android.udp;
 
import net.x52im.example.android.udp.utils.UDPUtils;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
 
/**
 * Demo主類。
 * 
 * @author jack.jiang@52im.net, 2016-06-27
 * @version 1.0
 */
public class MainActivity extends ActionBarActivity 
{
        private final static String TAG = MainActivity.class.getSimpleName();
        // 重復發送的時間間隔(單位:毫秒)
        public static int INTERVAL = 5000;
        private Handler handler = null;
        private Runnable runnable = null;
 
        @Override
        protected void onCreate(Bundle savedInstanceState) 
        {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
 
                // 初始化本地UDP的Socket
                LocalUDPSocketProvider.getInstance().initSocket();
                // 啟動本地UDP監聽(接收數據用的)
                LocalUDPDataReciever.getInstance(this).startup();
 
                // 自動循環發送
                handler = new Handler();
                runnable = new Runnable(){
                        @Override
                        public void run()
                        {
                                sendMessageToServer();
                                 
                                // 開始下一次循環
                                handler.postDelayed(runnable, INTERVAL);
                        }
                };
                 
                // 立即開始發送
                handler.postDelayed(runnable, 0);
        }
 
        private void sendMessageToServer()
        {
                try
                {
                        // 要發送的數據
                        String toServer = "Hi,我是客戶端,我的時間戳"+System.currentTimeMillis();
                        byte[] soServerBytes = toServer.getBytes("UTF-8");
                        // 開始發送
                        boolean ok = UDPUtils.send(soServerBytes, soServerBytes.length);
                        if(ok)
                                Log.d(TAG, "發往服務端的信息已送出.");
                        else
                                Log.e(TAG, "發往服務端的信息沒有成功發出!!!");
                }
                catch (Exception e)
                {
                        Log.w(TAG, e.getMessage(), e);
                }
        }
}

補充說明:以上代碼使用的是Android的標准UDP Socket代碼,如果你對此不太熟悉請先查閱更多Android UDP通訊的相關實例。

[3] 客戶端本地UDP端口監聽和數據接收類 LocalUDPDataSender.java:

/*
 * Copyright (C) 2016 即時通訊網(52im.net) - 即時通訊開發者社區.
 * All rights reserved.
 */
package net.x52im.example.android.udp;
 
import java.net.DatagramPacket;
import java.net.DatagramSocket;
 
import net.x52im.example.android.udp.utils.ConfigEntity;
import android.content.Context;
import android.util.Log;
 
/**
 * 本地UDP端口監聽和數據接收類。
 * 
 * @author jack.jiang@52im.net, 2016-06-27
 * @version 1.0
 */
public class LocalUDPDataReciever
{
        private static final String TAG = LocalUDPDataReciever.class.getSimpleName();
        private static LocalUDPDataReciever instance = null;
        private Thread thread = null;
        private Context context = null;
         
        public static LocalUDPDataReciever getInstance(Context context)
        {
                if (instance == null)
                        instance = new LocalUDPDataReciever(context);
                return instance;
        }
 
        private LocalUDPDataReciever(Context context)
        {
                this.context = context;
        }
 
        public void startup()
        {
                this.thread = new Thread(new Runnable()
                {
                        public void run()
                        {
                                try
                                {
                                        Log.d(LocalUDPDataReciever.TAG, "本地UDP端口偵聽中,端口=" + ConfigEntity.localUDPPort + "...");
 
                                        //開始偵聽
                                        LocalUDPDataReciever.this.udpListeningImpl();
                                }
                                catch (Exception eee)
                                {
                                        Log.w(LocalUDPDataReciever.TAG, "本地UDP監聽停止了(socket被關閉了?)," + eee.getMessage(), eee);
                                }
                        }
                });
                this.thread.start();
        }
 
        private void udpListeningImpl() throws Exception
        {
                while (true)
                {
                        byte[] data = new byte[1024];
                        // 接收數據報的包
                        DatagramPacket packet = new DatagramPacket(data, data.length);
 
                        DatagramSocket localUDPSocket = LocalUDPSocketProvider.getInstance().getLocalUDPSocket();
                        if ((localUDPSocket == null) || (localUDPSocket.isClosed()))
                                continue;
                         
                        // 阻塞直到收到數據
                        localUDPSocket.receive(packet);
                         
                        // 解析服務端發過來的數據
                        String pFromServer = new String(packet.getData(), 0 , packet.getLength(), "UTF-8");
                        Log.w(LocalUDPDataReciever.TAG, "【NOTE】>>>>>> 收到服務端的消息:"+pFromServer);
                }
        }
}

服務端准備工作

本文將分別基於MINA2和Netty4實現兩套服務端(你只需要使用其中之一即可),服務端准備工作已在本系列文章的前兩篇詳細記錄了,具體如下:

- Netty4實現服務端的准備工作請見:NIO框架入門(一):服務端基於Netty4的UDP雙向通信Demo演示
- MINA2實現服務端的准備工作請見:NIO框架入門(二):服務端基於MINA2的UDP雙向通信Demo演示

服務端代碼實現

因兩套方案的服務端代碼都不復雜,且已經本系列文章的前兩篇中詳細介紹,本文就不在重復粘貼了。

- Netty4實現的服務端請見:NIO框架入門(一):服務端基於Netty4的UDP雙向通信Demo演示
- MINA2實現的服務端請見:NIO框架入門(二):服務端基於MINA2的UDP雙向通信Demo演示

Demo 運行截圖

[1] Android客戶端運行結果:


[2] 服務端運行結果(MINA2方案):


[3] 服務端運行結果(Netty4方案):

本文小結

Demo中的客戶端代碼是從開源即時通訊框架MobileIMSDK 的Android端中復制出來的(為了方便理解做了大幅簡化),有興趣的可看看 MobileIMSDK Android端Server端,簡化一下可以用作你自已的各種用途。

本文的姊妹篇《NIO框架入門(三):iOS與MINA2、Netty4的跨平台UDP雙向通信實戰》,演示的是iOS端的跨平台UDP雙向通信,需要的話可以看看。

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

更多NIO框架資料整理

[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-388-1-1.html 自行下載。

完整源碼工程截圖如下:

   

截圖說明:左右是Android客戶端源碼、右邊是服務端(MINA2和Netty4兩個方案)。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM