zoukankan      html  css  js  c++  java
  • 【原创】NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示


    申明:本文由作者基于日常实践整理,希望对初次接触MINA、Netty的人有所启发。如需与作者交流,见文签名,互相学习。 


    学习交流

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

      

    前言

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

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

    学习交流

    - 更多即时通讯技术资料:http://www.52im.net/forum.php?mod=collection&op=all

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

    《NIO框架入门》系列文章目录

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

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

    本文亮点

    [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

     1 public 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

     1 public 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

    public class EchoClient
    {
            public static void main(String[] args) throws Exception
            {
                    // 初始化本地UDP的Socket
                    LocalUDPSocketProvider.getInstance().initSocket();
                    // 启动本地UDP监听(接收数据用的)
                    LocalUDPDataReciever.getInstance().startup();
                     
                    // 循环发送数据给服务端
                    while(true)
                    {
                            // 要发送的数据
                            String toServer = "Hi,我是客户端,我的时间戳"+System.currentTimeMillis();
                            byte[] soServerBytes = toServer.getBytes("UTF-8");
                             
                            // 开始发送
                            boolean ok = UDPUtils.send(soServerBytes, soServerBytes.length);
                            if(ok)
                                    Log.d("EchoClient", "发往服务端的信息已送出.");
                            else
                                    Log.e("EchoClient", "发往服务端的信息没有成功发出!!!");
                             
                            // 3000秒后进入下一次循环
                            Thread.sleep(3000);
                    }
            }
    }

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

    [2] Socket操作类 LocalUDPSocketProvider.java

     1 public 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

     1 public 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)

  • 相关阅读:
    getParameter和getAttribute的区别
    forward和sendRedirect的区别
    关于html/css的路径问题
    手写ORM
    数据库其他使用方法介绍
    Navicat使用与python操作数据库
    表查询
    表与表之间的三种关系
    SQL语法
    MySQL数据库的安装与使用
  • 原文地址:https://www.cnblogs.com/imstudy/p/5600369.html
Copyright © 2011-2022 走看看