本文演示的是一個Android客戶端程序,經過UDP協議與兩個典型的NIO框架服務端,實現跨平臺雙向通訊的完整Demo。
當前因爲NIO框架的流行,使得開發大併發、高性能的互聯網服務端成爲可能。這其中最流行的無非就是MINA和Netty了,MINA目前的主要版本是MINA2、而Netty的主要版本是Netty3和Netty4(Netty5已經被取消開發了:詳見此文)。
本文中,服務端將分別用MINA2和Netty4進行實現,但在你實際的項目中服務端實現只需選其一就好了。本文中的Demo同時用MINA2和Netty4分別實現服務端的目的,是由於不少人都在糾結究竟是用MINA仍是Netty來實現高併發的Java網絡通訊服務端,在此乾脆兩個都實現了,就看你怎麼選擇。
實際上,MINA2和Netty4的官方代碼裏有UDP通訊的Demo代碼,但卻不存在針對移動端(主要是Android和iOS端)的Demo,本文將演示用Android客戶端來實現這種跨平臺的雙向網絡通訊。Demo中,已經解決跨平臺通訊時的常見的亂碼、數據字節異常等問題,如以爲有用,你可直接使用之。php
- 更多即時通信技術資料:http://www.52im.net/forum.php?mod=collection&op=all
html
- 移動端即時通信交流羣:215891622 推薦java
有關MINA和Netty的入門文章不少,但多數都是複製、粘貼的未經證明的來路不明內容,對於初次接觸的人來講,一個能夠運行且編碼規範的Demo,顯然要比各類「詳解」、「深刻分析」之類的要來的直接和有意義。本系列入門文章正是基於此種考慮而寫,雖無精深內容,但至少但願對初次接觸MINA、Netty的人有所啓發,起到拋磚引玉的做用。
本文是《NIO框架入門》系列文章中的第 4 篇,目錄以下:android
本文中的Demo代碼實現包含兩部分,Android UDP客戶端和NIO框架實現的服務端(包括MINA2和Netty4實現兩個方案),客戶端每隔5秒向服務端發送消息,而服務端在收到消息後立刻回覆一條消息給客戶端。
如上所述,服務端(PC服務器)和客戶端(Android移動端)都要實現消息的發送和接收,即實現跨平臺的雙向通訊。下節將將給出真正的實現代碼。git
這兩年,Google官方已經基本放棄Eclipse+ADT這樣的IDE組合,轉而大力開發Android Studio,但不得不認可,因爲個人OS仍然是XP(Android Studio不支持XP),因此Eclipse+ADT還得繼續用(這個組合雖然一直被吐槽,但又不得不用)。
若是你習慣使用Eclipse+ADT這樣的IDE,能夠下載我打好包的版本,內含Eclipse4.2+ADT+Android SDK:程序員
若是你須要Android Studio,可進入此連接下載。
github
本文以Eclipse+ADT爲開發Android開發工具(如你使用Android Studio道理也是同樣的),按照提示新建工程便可,無需特殊的設置或其它前前置條件。
我建好的工程,以下圖所示(不少都是默認生成的,用不上的東西就別去管它了):
補充說明:由於須要進行網絡通訊,建好的工程裏,請務必在 AndroidManifest.xml 加上網絡權限的許可,以下圖:編程
/* * 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輸出哦。
api
/* * 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通信的相關實例。
android-studio
/* * 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演示》
[1] Android客戶端運行結果:
[2] 服務端運行結果(MINA2方案):
[3] 服務端運行結果(Netty4方案):
Demo中的客戶端代碼是從開源即時通信框架MobileIMSDK 的Android端中複製出來的(爲了方便理解作了大幅簡化),有興趣的可看看 MobileIMSDK Android端、Server端,簡化一下能夠用做你自已的各類用途。
本文的姊妹篇《NIO框架入門(三):iOS與MINA二、Netty4的跨平臺UDP雙向通訊實戰》,演示的是iOS端的跨平臺UDP雙向通訊,須要的話能夠看看。
對於服務端的NIO框架來講,若是你閱讀過本系列的《NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示》和《NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示》,應該能明顯地感受的出來MINA2的UDP服務端API接口使用要是Netty4的繁瑣,並且MINA2還存在獨立客戶端(非依賴於MINA2客戶端)實現時的多餘字節和亂碼問題。但我的認爲MINA2的代碼風格更符合通常程序員的編碼習慣,更好懂一些,而Netty4因歷經多個大版本的進化,雖起來很是簡潔,但實現並非那麼直觀。固然,至於MINA仍是Netty,請客觀一評估和使用,由於兩者並沒有本質區別。
[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)