【原创】nio框架入门(四):android与mina2、netty4的跨平台udp双向通信实战 -凯发k8网页登录

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

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

当前由于nio框架的流行,使得开发大并发、高性能的互联网服务端成为可能。这其中最流行的无非就是mina和netty了,mina目前的主要版本是、而netty的主要版本是和(netty5已经被取消开发了:)。

本文中,服务端将分别用mina2和netty4进行实现,但在你实际的项目中服务端实现只需选其一就行了。本文中的demo同时用mina2和netty4分别实现服务端的目的,是因为很多人都在纠结到底是用mina还是netty来实现高并发的java网络通信服务端,在此干脆两个都实现了,就看你怎么选择。

实际上,mina2和netty4的官方代码里有udp通信的demo代码,但却不存在针对移动端(主要是android和ios端)的demo,本文将演示用android客户端来实现这种跨平台的双向网络通信。demo中,已经解决跨平台通信时的常见的乱码、数据字节异常等问题,如觉得有用,你可直接使用之。

- 更多即时通讯技术资料:

- 移动端即时通讯交流群: 推荐

有关mina和netty的入门文章很多,但多数都是复制、粘贴的未经证实的来路不明内容,对于初次接触的人来说,一个可以运行且编码规范的demo,显然要比各种“详解”、“深入分析”之类的要来的直接和有意义。本系列入门文章正是基于此种考虑而写,虽无精深内容,但至少希望对初次接触mina、netty的人有所启发,起到抛砖引玉的作用。

本文是《nio框架入门》系列文章中的第 篇,目录如下:

  • 《》
  • 《》
  • 《》
  • 《》(本文
  • 客户端基于android移动端平台:
    直接使用android的标准udp代码,不依赖第3方包,且已解决与java nio服务端的跨平台通信问题,是个难得的android端实践入门示例;
  • 完整可执行源码、方便学习:
    完整的demo源码,适合新手直接运行,便于学习和研究。
  • demo中的代码源自作者的开源工程,有实用价值:
    源码均修改自作者的即时通讯开源工程 ,只是为了方便学习理解而作了简化,有一定的实用价值;

本文中的demo代码实现包含两部分,android udp客户端和nio框架实现的服务端(包括mina2和netty4实现两个方案),客户端每隔5秒向服务端发送消息,而服务端在收到消息后马上回复一条消息给客户端。

如上所述,服务端(pc服务器)和客户端(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 加上网络权限的许可,如下图:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/*
 * 凯发天生赢家一触即发官网 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/*
 * 凯发天生赢家一触即发官网 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/*
 * 凯发天生赢家一触即发官网 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实现服务端的准备工作请见:《》
- mina2实现服务端的准备工作请见:《》

因两套方案的服务端代码都不复杂,且已经本系列文章的前两篇中详细介绍,本文就不在重复粘贴了。

- netty4实现的服务端请见:《》
- mina2实现的服务端请见:《》

[1] android客户端运行结果:


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


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

demo中的客户端代码是从 的android端中复制出来的(为了方便理解做了大幅简化),有兴趣的可看看 mobileimsdk 、,简化一下可以用作你自已的各种用途。

本文的姊妹篇《》,演示的是ios端的跨平台udp双向通信,需要的话可以看看。

对于服务端的nio框架来说,如果你阅读过本系列的《》和《》,应该能明显地感觉的出来mina2的udp服务端api接口使用要是netty4的繁琐,而且mina2还存在独立客户端(非依赖于mina2客户端)实现时的多余字节和乱码问题。但个人认为mina2的代码风格更符合一般程序员的编码习惯,更好懂一些,而netty4因历经多个大版本的进化,虽起来非常简洁,但实现并不是那么直观。当然,至于mina还是netty,请客观一评估和使用,因为二者并无本质区别。

[1] mina和netty的源码在线学习和查阅:
mina-2.x地址是:
mina-1.x地址是:
netty-4.x地址是:
netty-3.x地址是:

[2] mina和netty的api文档在线查阅:
mina-2.x api文档(在线版):
mina-1.x api文档(在线版):
netty-4.x api文档(在线版):
netty-3.x api文档(在线版):

[3] 更多有关nio编程的资料:
请进入精华资料专辑:

[4] 有关im聊天应用、消息推送技术的资料:
请进入精华资料专辑:

[5] 技术交流和学习:
可直接进入  讨论和学习网络编程、im聊天应用、消息推送应用的开发。

博客园貌似上传不了附件,如需完整eclipse源码工程请联系作者,或者进入链接  自行下载。

完整源码工程截图如下:

   

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

(本文同步发布于:)

作者: (点击作者姓名进入github) 
出处:
凯发天生赢家一触即发官网的联系方式:qq: 413980957, 微信: hellojackjiang,email: jb2011@163.com 
jack jiang同时是和的作者,可前往下载交流。
本博文 欢迎转载,转载请注明出处(也可前往  找到我)。 



作者: (点击作者姓名进入github)
出处:
交流:欢迎加入即时通讯开发交流群
讨论:
jack jiang同时是和的作者,可前往下载交流。
本博文 欢迎转载,转载请注明出处(也可前往 找到我)。


只有注册用户后才能发表评论。


网站导航:
              
 
jack jiang的 mail: jb2011@163.com, 联系qq: 413980957, 微信: hellojackjiang
网站地图