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

学习交流

  • 更多学习资料:点此进入 推荐
  • 移动端即时通讯交流: 215891622 推荐

前言

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

本文将演示的是一个基于Netty4的UDP服务端和一个标准UDP客户端(Java实现)双向通信的完整例子。实际上,Netty4的UDP例子非常难找(恕我愚钝,找遍全网也没有有价值的代码,有也是Netty3,而Netty3和Netty4的风格差的不是一点点,参考意义不大),官方的代码演示里只有一个简单的UDP广播例子,不足以用于演示Netty4的UDP通信最佳实践。

本文亮点

[1] Netty4的UDP例子太难找:

Netty4的完整双向UDP通信例子很难找(官方没有),本文就是要用代码来演示这个;

[2] 本例中客户端UDP实现无需第3方依赖:

通常MINA或Netty的例子里,客户端也都是用的MINA或Netty的客户端lib,本文将直接使用标准UDP代码(便于跨平台实现,比如iOS平台),不依赖于第3方包;

[3] 完整可执行源码、方便学习:

完整的Demo源码,适合新手直接运行,便于学习和研究。

题外话

社区里经常在争论到底该用MINA还是Netty,比如这几篇文章:《开源NIO框架:到底是先有MINA还是先有Netty?》、《有关“为何选择Netty”的11个疑问及解释》、《选Netty还是Mina:深入研究与对比(一)》,哈哈这确实挺难抉择的。不过,个人观点是,对那个熟悉就用哪个吧,没什么好纠结的,必竟二者并比本质区别,也都是出自同一作者之手。

写个什么样的Demo?

言归正传,本文要演示的Demo包含两部分,Java UDP客户端和Netty4 UDP服务端,客户端将每隔3秒向服务端发送一条消息,而服务端在收到消息后马上回复一条消息给客户端。

也就是说,服务端和客户端都要实现消息的发送和接收,这也就实现了双向通信。如果有心的话,稍加改造,也就很容易实现一个简陋的聊天程序了。下节将将给出真正的实现代码。

Netty4 服务端准备工作

[1] 第一步:下载Netty4

Netty4的官方网站是:http://netty.io/,直接下最新的Netty 4.1吧,如下图:

[2] 第二步:找到Netty4的核心库文件

直接用这个all in one的jar包吧,反正用在服务端,2M大小的东西无所谓,方便管理,见下图:

[3] 第三步:建好Java工程准备准备开撸

个人习惯用Eclipse,你用NetBeans或InteliJ也都没问题,具体建立过程不熟练的衣自行百度吧,唯一注意的是把Netty4的lib包引用进来就行了,我的见下图:

服务端代码

[1] 服务端主类 EchoServer.java

1public class EchoServer 2{ 3        public static void main(String[] args) throws InterruptedException 4        { 5                Bootstrap b = new Bootstrap(); 6                EventLoopGroup group = new NioEventLoopGroup(); 7                b.group(group) 8                        .channel(NioDatagramChannel.class) 9                        .handler(new EchoSeverHandler()); 10                  11                // 服务端监听在9999端口 12                b.bind(9999).sync().channel().closeFuture().await(); 13        } 14}

如上述代码所示:不得不说,Netty4的UDP服务端代码实现起来确实很简单,一个 Bootstrap、一个 EventLoopGroup、外加一个SimpleChannelInboundHandler,就这么被Netty4轻松搞定(准确地说Netty4是在Java NIO上的封装而已,但最终API对开发者来说确实很友好)。

吐个槽:话说Netty4的代码跟MINA相比,风格确实大不相同,虽说两者有很深的源源,但经过Netty3、Netty4的进化,两者的差异(至少代码看起来是这样)还是很明显的。另外,b.bind(9999).sync().channel().closeFuture().await() 这一大串的连续方法调用,看起来很诡异,不小心调错的话,服务器会不会爆炸?

[2] 服务端Handler类 EchoSeverHandler.java

1public class EchoSeverHandler extends SimpleChannelInboundHandler<DatagramPacket> 2{ 3        @Override 4        protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) 5                        throws Exception 6        { 7                // 读取收到的数据 8                ByteBuf buf = (ByteBuf) packet.copy().content(); 9                byte[] req = new byte[buf.readableBytes()]; 10                buf.readBytes(req); 11                String body = new String(req, CharsetUtil.UTF_8); 12                System.out.println("【NOTE】>>>>>> 收到客户端的数据:"+body); 13                  14                // 回复一条信息给客户端 15                ctx.writeAndFlush(new DatagramPacket( 16                Unpooled.copiedBuffer("Hello,我是Server,我的时间戳是"+System.currentTimeMillis() 17                                , CharsetUtil.UTF_8) 18                                , packet.sender())).sync(); 19        } 20}

如上所示,这个Handler看起来比MINA的类似实现,要简洁不少。

客户端代码

为了让客户端代码看起来更简洁,我把Socket管理代码提炼到 LocalUDPSocketProvider类、把UDP数据监听和接收提炼到了 LocalUDPDataReciever类(实际上这两个同名类是简化自MobileIMSDK工程哦)。

[1] 客户端主类 EchoClient.java

1public class EchoClient 2{ 3        public static void main(String[] args) throws Exception 4        { 5                // 初始化本地UDP的Socket 6                LocalUDPSocketProvider.getInstance().initSocket(); 7                // 启动本地UDP监听(接收数据用的) 8                LocalUDPDataReciever.getInstance().startup(); 9                  10                // 循环发送数据给服务端 11                while(true) 12                { 13                        // 要发送的数据 14                        String toServer = "Hi,我是客户端,我的时间戳"+System.currentTimeMillis(); 15                        byte[] soServerBytes = toServer.getBytes("UTF-8"); 16                          17                        // 开始发送 18                        boolean ok = UDPUtils.send(soServerBytes, soServerBytes.length); 19                        if(ok) 20                                Log.d("EchoClient", "发往服务端的信息已送出."); 21                        else 22                                Log.e("EchoClient", "发往服务端的信息没有成功发出!!!"); 23                          24                        // 3000秒后进入下一次循环 25                        Thread.sleep(3000); 26                } 27        } 28}

**补充说明:**客户端代码没有使用任何依赖,纯Java UDP代码实现(如果是Andriod平台,代码也几乎不用改就能用),部分代码修改自 开源即时通讯框架 MobileIMSDK(去掉了很多保证健壮性代码,现在看起来要简洁的多,便于初学者学习)。

[2] Socket操作类 LocalUDPSocketProvider.java

1public 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本地监听端口(如果为0将表示由系统分配,否则使用指定端口) 19                        this.localUDPSocket = new DatagramSocket(ConfigEntity.localUDPPort); 20                        // 调用connect之后,每次send时DatagramPacket就不需要设计目标主机的ip和port了 21                        // * 注意:connect方法一定要在DatagramSocket.receive()方法之前调用, 22                        // * 不然整send数据将会被错误地阻塞。这或许是官方API的bug,也或许是调 23                        // * 用规范就应该这样,但没有找到官方明确的说明 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创建时出错,原因是:" + e.getMessage(), e); 32                } 33        } 34  35        public DatagramSocket getLocalUDPSocket() 36        { 37                return this.localUDPSocket; 38        } 39}

[3] 数据接收类 LocalUDPDataReciever.java

1public 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监听停止了(socket被关闭了?)," + 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                        // 接收数据报的包 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                        // 阻塞直到收到数据 49                        localUDPSocket.receive(packet); 50                          51                        // 解析服务端发过来的数据 52                        String pFromServer = new String(packet.getData(), 0 , packet.getLength(), "UTF-8"); 53                        Log.w(LocalUDPDataReciever.TAG, "【NOTE】>>>>>> 收到服务端的消息:"+pFromServer); 54                } 55        } 56}

运行效果

客户端运行结果:

服务端运行结果:

补充说明

客户端代码没什么特别,就是标准的Java UDP代码,看起来跟其它例子不一样的原因只是我把它提炼了一下,没本质区别。同样的代码改改也可以很好的用在Android端。实际上,上面的客户端代码就是从开源 MobileIMSDK 的Java端扒出来的,有兴趣的也可以看看它的Android端Server端iOS端,简化一下可以用作你自已的各种用途。

更多学习资源

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

完整源码工程截图如下:

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

点赞
收藏

评论区

加载中...

相关推荐

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

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )