首页 > 代码库 > Netty实践(一):轻松入门

Netty实践(一):轻松入门

前言

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

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



从代码实例来分析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://zhangfengzhe.blog.51cto.com/8855103/1890017

Netty实践(一):轻松入门