NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战

Stella981
• 阅读 719

概述

=====

本文演示的是一个Android客户端程序,通过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通信的完整Demo。

当前由于NIO框架的流行,使得开发大并发、高性能的互联网服务端成为可能。这其中最流行的无非就是MINA和Netty了,MINA目前的主要版本是MINA2、而Netty的主要版本是Netty3Netty4(Netty5已经被取消开发了:详见此文)。

本文中,服务端将分别用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框架入门》系列文章中的第4篇,目录如下:

NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示

NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示

NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战

NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战》(本文)

本篇亮点

- 客户端基于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:

Eclipse+ADT 网盘下载

如果你需要Android Studio,可进入此链接下载

[Step 2]:新建一个普通的Android工程,�准备开撸

本文以Eclipse+ADT为开发Android开发工具(如你使用Android Studio道理也是一样的),按照提示新建工程即可,无需特殊的设置或其它前前置条件。

我建好的工程,如下图所示(很多都是默认生成的,用不上的东西就别去管它了):

新建的Android工程

补充说明:因为需要进行网络通信,建好的工程里,请务必在 AndroidManifest.xml 加上网络权限的许可,如下图:

记得加入网络权限申明

Android客户端代码实现

[1] 客户端主类 MainActivity.java:

/*

* Copyright (C) 2016 即时通讯网(52im.net) - 即时通讯开发者社区.

* All rights reserved.

*/

packagenet.x52im.example.android.udp;

importnet.x52im.example.android.udp.utils.UDPUtils;

importandroid.os.Bundle;

importandroid.os.Handler;

importandroid.support.v7.app.ActionBarActivity;

importandroid.util.Log;

/**

* Demo主类。

*

* @author jack.jiang@52im.net, 2016-06-27

* @version 1.0

*/

publicclassMainActivityextendsActionBarActivity

{

privatefinalstaticString TAG = MainActivity.class.getSimpleName();

// 重复发送的时间间隔(单位:毫秒)

publicstaticintINTERVAL =5000;

privateHandler handler =null;

privateRunnable runnable =null;

@Override

protectedvoidonCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

// 初始化本地UDP的Socket

LocalUDPSocketProvider.getInstance().initSocket();

// 启动本地UDP监听(接收数据用的)

LocalUDPDataReciever.getInstance(this).startup();

// 自动循环发送

handler =newHandler();

runnable =newRunnable(){

@Override

publicvoidrun()

{

sendMessageToServer();

// 开始下一次循环

handler.postDelayed(runnable, INTERVAL);

}

};

// 立即开始发送

handler.postDelayed(runnable,0);

}

privatevoidsendMessageToServer()

{

try

{

// 要发送的数据

String toServer ="Hi,我是客户端,我的时间戳"+System.currentTimeMillis();

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

// 开始发送

booleanok = 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.

*/

packagenet.x52im.example.android.udp;

importnet.x52im.example.android.udp.utils.UDPUtils;

importandroid.os.Bundle;

importandroid.os.Handler;

importandroid.support.v7.app.ActionBarActivity;

importandroid.util.Log;

/**

* Demo主类。

*

* @author jack.jiang@52im.net, 2016-06-27

* @version 1.0

*/

publicclassMainActivityextendsActionBarActivity

{

privatefinalstaticString TAG = MainActivity.class.getSimpleName();

// 重复发送的时间间隔(单位:毫秒)

publicstaticintINTERVAL =5000;

privateHandler handler =null;

privateRunnable runnable =null;

@Override

protectedvoidonCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

// 初始化本地UDP的Socket

LocalUDPSocketProvider.getInstance().initSocket();

// 启动本地UDP监听(接收数据用的)

LocalUDPDataReciever.getInstance(this).startup();

// 自动循环发送

handler =newHandler();

runnable =newRunnable(){

@Override

publicvoidrun()

{

sendMessageToServer();

// 开始下一次循环

handler.postDelayed(runnable, INTERVAL);

}

};

// 立即开始发送

handler.postDelayed(runnable,0);

}

privatevoidsendMessageToServer()

{

try

{

// 要发送的数据

String toServer ="Hi,我是客户端,我的时间戳"+System.currentTimeMillis();

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

// 开始发送

booleanok = 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.

*/

packagenet.x52im.example.android.udp;

importjava.net.DatagramPacket;

importjava.net.DatagramSocket;

importnet.x52im.example.android.udp.utils.ConfigEntity;

importandroid.content.Context;

importandroid.util.Log;

/**

* 本地UDP端口监听和数据接收类。

*

* @author jack.jiang@52im.net, 2016-06-27

* @version 1.0

*/

publicclassLocalUDPDataReciever

{

privatestaticfinalString TAG = LocalUDPDataReciever.class.getSimpleName();

privatestaticLocalUDPDataReciever instance =null;

privateThread thread =null;

privateContext context =null;

publicstaticLocalUDPDataReciever getInstance(Context context)

{

if(instance ==null)

instance =newLocalUDPDataReciever(context);

returninstance;

}

privateLocalUDPDataReciever(Context context)

{

this.context = context;

}

publicvoidstartup()

{

this.thread =newThread(newRunnable()

{

publicvoidrun()

{

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();

}

privatevoidudpListeningImpl()throwsException

{

while(true)

{

byte[] data =newbyte[1024];

// 接收数据报的包

DatagramPacket packet =newDatagramPacket(data, data.length);

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

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

continue;

// 阻塞直到收到数据

localUDPSocket.receive(packet);

// 解析服务端发过来的数据

String pFromServer =newString(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客户端运行结果:

Android客户端运行结果

[2] 服务端运行结果(MINA2方案):

服务端运行结果(MINA2方案)

[3] 服务端运行结果(Netty4方案):

服务端运行结果(Netty4方案)

本文小结

Demo中的客户端代码是从开源即时通讯框架MobileIMSDK的Android端中复制出来的(为了方便理解做了大幅简化),有兴趣的可看看 MobileIMSDKAndroid端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端源码

服务端源码

截图说明:左右是Android客户端源码、右边是服务端(MINA2和Netty4两个方案)。

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

作者:Jack Jiang(点击作者姓名进入Github)

出处:http://www.52im.net/space-uid-1.html

联系方式:QQ: 413980957, 微信: hellojackjiang,Email: jb2011@163.com

Jack Jiang同时是【原创Java Swing外观工程BeautyEye】【轻量级移动端即时通讯框架MobileIMSDK】的作者,可前往下载交流。

本博文 欢迎转载,转载请注明出处(也可前往我的52im.net找到我)。

本文同步分享在 博客“JackJiang2011”(JianShu)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示
前言NIO框架的流行,使得开发大并发、高性能的互联网服务端成为可能。这其中最流行的无非就是MINA和Netty了,MINA目前的主要版本是MINA2(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fdocs.52im.net%2Fextend%2Fdocs%2Fs
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战
前言本文将演示一个iOS客户端程序,通过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通信的完整Demo。服务端将分别用MINA2和Netty4进行实现,而通信时服务端你只需选其一就行了。同时用MINA2和Netty4分别实现服务端的目的,是因为很多人都在纠结到底是用MINA还是Netty来实现高并发的Java网络通信服务端
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这