你好,游客 登录 注册 搜索
背景:
阅读新闻

Netty实践教程

轻松入门

[日期:2017-03-11] 来源:Linux社区  作者:zhangfengzhe [字体: ]

前言

Netty作为目前世界上最流行的NIO框架之一,在功能、性能、健壮性方面首屈一指,而且在很多项目中得到验证,比如消息中间件RocketMQ、分布式通信框架Dubbox。Netty内部实现复杂,但是提供给外界的API却十分简单,轻松的让我们的网络处理代码和业务逻辑处理代码分离开,从而快速的开发网络应用。

如果你还不了解JAVA NIO,JAVA SOCKET,可以先参考博主以前关于这方面的博客:《走进Java NIO的世界》、《Java NIO 服务器与客户端实现文件下载》、《Java通信实战:编写自定义通信协议实现FTP服务》。

Netty权威指南 PDF完整版带目录书签+源码  http://www.linuxidc.com/Linux/2016-07/133575.htm

运用Spring注解实现Netty服务器端UDP应用程序  http://www.linuxidc.com/Linux/2013-09/89780.htm

Netty源码学习笔记 http://www.linuxidc.com/Linux/2013-09/89778.htm

Netty使用实例 http://www.linuxidc.com/Linux/2013-09/89779.htm

Java NIO框架--Netty4的简单示例  http://www.linuxidc.com/Linux/2015-01/111335.htm

从代码实例来分析Netty

服务器端启动代码

public class Main {
 
    public static void main(String[] args) {
 
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup(); // (2)
        int port = 8867;
        try {
            ServerBootstrap b = new ServerBootstrap(); // (3)
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class) // (4)
                    .childHandler(new ChannelInitializer<SocketChannel>() { // (5)
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ServerHandler());
                }
            })
            .option(ChannelOption.SO_BACKLOG, 128)          // (6)
            .childOption(ChannelOption.SO_KEEPALIVE, true); // (7)
 
            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(port).sync(); // (8)
 
            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            System.out.println("start server....");
            f.channel().closeFuture().sync();
            System.out.println("stop server....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            System.out.println("exit server....");
        }
 
    }
}

上面大致揭示了Netty服务端启动编写的代码步骤,下面我们来具体分析下:

在原始的JAVA SOCKET通信中,不论是基于IO/NIO,实质上服务端有2个工作需要处理:第一,接受客户端的连接请求;第二,处理客户端的请求进行通信。在上面的代码里,Netty已经为我们抽象出来2个EventLoopGroup线程组(bossGroup/workerGroup)来完成这2个任务。

服务端启动前,显然需要进行一些配置(Channel的类型、服务端进行业务处理的Handler、一些TCP/IP协议的配置等),Netty可以利用ServerBootstrap/Bootstrap分别对Server/Client进行配置。

绑定端口、启动服务,注意返回的对象ChannelFuture,既然叫Future,那么猜测就是一个异步的行为。需要注意的是通过ChannelFuture可以获取到Channel,从而利用Channel在通道上进行读、写、关闭等操作。

通过bind方法,可以绑定多个端口,实现N个Clients在Server端的M个端口上进行数据通信。

下面,我们来看一下服务端进行业务处理的Handler

public class ServerHandler  extends ChannelHandlerAdapter {
 
    //每当从客户端收到新的数据时,这个方法会在收到消息时被调用
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
 
        ByteBuf in = (ByteBuf) msg;
        try {
            // Do something with msg
            System.out.println("server get :" + in.toString(CharsetUtil.UTF_8));
 
            ChannelFuture channelFuture = ctx.writeAndFlush(Unpooled.copiedBuffer(("server send time: " + new Date()).getBytes()));
 
            //服务端发送数据完毕后,关闭通道
            channelFuture.addListener(ChannelFutureListener.CLOSE);
 
        } finally {
            //ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放
            //or ((ByteBuf)msg).release();
            ReferenceCountUtil.release(msg);
        }
 
    }
 
    //exceptionCaught()事件处理方法是当出现Throwable对象才会被调用
    //当Netty由于IO错误或者处理器在处理事件时抛出的异常时
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
 
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
 
    }
 
}

首先来看服务端的Handler继承了ChannelHandlerAdapter,实际上这里体现到了适配器设计模式。ChannelHandlerAdapter implements ChannelHandler,如果我们的服务端Handler直接implements ChannelHandler的话,将需要override非常多的API,而先通过ChannelHandlerAdapter实现通用的ChannelHandler,然后让服务端的Handler去复写特定的API即可。

当通道上特定的事件发生时,就会调用特定的方法进行处理,看起来清晰明了。

Netty进行网络通信的数据类型是缓冲数据类型,如ByteBuf。以前在NIO中我们利用ByteBuffer进行通信时,需要额外注意position的位置变化,而现在Netty中,我们不在需要关心这些。

Client/Server端都存在缓冲区,所以我们需要注意,缓冲区的消息释放和刷新。如果读,那么需要release,如果写,只需要flush(flush的时候已经做了release)进行发送到对方。

由于Netty是一个NIO框架,即操作都是异步的,所以上面writeAndFlush操作返回了Future对象,我们可以在这个Future上进行监听,比如操作完毕关闭通道。

客户端启动代码

public class Client {
 
    public static void main(String[] args) {
 
        EventLoopGroup group = new NioEventLoopGroup();
 
        try {
              Bootstrap b = new Bootstrap();
              b.group(group)
              .channel(NioSocketChannel.class)
              .handler(new ChannelInitializer<SocketChannel>() {
                  @Override
                  public void initChannel(SocketChannel ch) throws Exception {
                      ChannelPipeline p = ch.pipeline();
                      p.addLast(new ClientHandler());
                  }
              });
 
              // Start the client.
              ChannelFuture f = b.connect("127.0.0.1", 8867).sync();
 
              // Wait until the connection is closed.
              f.channel().closeFuture().sync();
 
          } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
              // Shut down the event loop to terminate all threads.
              group.shutdownGracefully();
          }
 
    }
 
}

这里,我们主要看一下客户端和服务端启动的区别点:

第一,客户端仅仅需要一个线程组,而服务端需要2个

第二,服务启动辅助类,客户端是Bootstrap,服务端是ServerBootstrap

第三,服务端通道配置是NioServerSocketChannel,客户端是NioSocketChannel

客户端业务处理Handler

public class ClientHandler extends ChannelHandlerAdapter {
 
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
 
        ctx.writeAndFlush(Unpooled.copiedBuffer(("client send hello ").getBytes()));
 
    }
 
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
 
        ByteBuf in = (ByteBuf) msg;
        try {
            // Do something with msg
            System.out.println("client get :" + in.toString(CharsetUtil.UTF_8));
 
            ctx.close();
        } finally {
            //ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放
            //or ((ByteBuf)msg).release();
            ReferenceCountUtil.release(msg);
        }
    }

运行起来

到这里,你体会到Netty的简单、强大了吗?

更多详情见请继续阅读下一页的精彩内容http://www.linuxidc.com/Linux/2017-03/141689p2.htm

linux
【内容导航】
第1页:轻松入门 第2页:TCP拆包、粘包问题
第3页:实际场景下的数据通信 第4页:心跳检测实现
相关资讯       Netty  Netty教程 
本文评论   查看全部评论 (0)
表情: 表情 姓名: 字数

       

评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款