【RPC高性能框架总结】4.高性能nio框架netty(上)

13 篇文章 21 订阅
7 篇文章 0 订阅

接上一篇《NIO示例代码编写和简析》
上一篇我们使用java.nio包下的相关API完成了一个NIO的网络处理过程,实现了一个非阻塞的网络请求处理机制。那么按照之前的写法,无疑代码量和复杂度都是不低的,所以使用一个合适的、封装优雅的第三方开发库来帮我们简化开发,提高代码质量和运行效率,是最好不过的,而Netty就是我们的不二之选。

一、Netty简介

看之前,还是简单介绍一下Netty(摘自度娘):
Netty是由JBOSS提供的一个基于NIO的客户、服务器端编程的java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。

上面提到的“简化”,就是把我们之前直接使用Socket进行通信逻辑的编写过程,封装了起来,我们只需要传入关键参数,简单的调用,即可实现一个非阻塞的异步通信的服务端或客户端Java程序。

还记得之前的总结中出现的这个Linux内核进行网络交互的图吗?里面是一个典型的NIO网络交互模型:

我们在上一篇使用JDK自带的NIO的API模拟了上图的交互过程,而我们如果使用Netty的话,这个交互过程的编写会十分简洁,并且执行效率要高很多(内部使用了线程池)。
使用Netty的话,上面的整个过程将被封装,我们只需要关心关键参数的设置,以及处理请求和反馈信息这些事情上,使我们更加专注于处理程序的业务问题:

上图中,A部分就是Netty帮我们封装起来的逻辑,B部分是Netty的请求和反馈信息的处理机制,该机制以handler处理器为单位,将每个管道的请求信息传送至handler请求处理器(In Hander)中,将需要反馈给服务端的信息传送至handler反馈处理器(Out Handler)中,不同管道的请求handler汇集在一起,形成一个数组,自前向后的去处理信息。

二、Netty样例编写

我们下面通过编写一个简单的Netty小程序,来进一步理解Netty。
首先打开MyEclipse,创建一个名为“Netty_test”的Java工程:

然后在src下面创建一个“cn.com.netty.test.client”包,在下面创建“NettyTestClient”、“NettyTestClientHandler”两个Java文件,然后再创建一个“cn.com.netty.test.server”包,在下面创建“NettyTestServer”、“NettyTestServerHandler”两个Java文件:

其中“NettyTestClient”和“NettyTestClientHandler”分别为客户端的启动类以及业务处理类;“NettyTestServer”、“NettyTestServerHandler”分别为服务端的启动类和业务处理类。
然后创建lib文件夹,将Netty的相关依赖jar包(依赖和源码,正式开发可以去除sources包)放入其中,并右键“Build Path”->“Add to Build  Path”添加至类加载路径:

这里我们使用了Netty-4.1.33(截止本文发布前最新版本)的版本进行开发。
依赖下载地址:https://dl.bintray.com/netty/downloads/netty-4.1.33.Final.tar.bz2

1、客户端启动类
首先编写“NettyTestClient”客户端启动类:

package cn.com.netty.test.client;

import java.net.InetSocketAddress;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import sun.applet.Main;

public class NettyTestClient {
    
    private final String host;//服务端连接ip
    private final int port;//服务端连接端口
    
    public NettyTestClient(String host, int port) {
        this.host = host;
        this.port = port;
    }
    
    public static void main(String[] args) throws Exception {
        new NettyTestClient("localhost",8888).start();
    }
    
    public void start() throws Exception{
        EventLoopGroup nioEventLoopGroup = null;
        try {
            Bootstrap bootstrap = new Bootstrap();//客户端引导类
            //EventLoopGroup可以理解为是一个线程池,这个线程池用来处理连接、接受数据、发送数据
            nioEventLoopGroup = new NioEventLoopGroup();
            bootstrap.group(nioEventLoopGroup)//多线程处理
                     .channel(NioSocketChannel.class)//制定通道类型为NioSocketChannel
                     .remoteAddress(new InetSocketAddress(host,port))//地址
                     .handler(new ChannelInitializer<SocketChannel>(){//业务处理类
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyTestClientHandler());//注册handler
                        }
                     });
            //连接服务器
            ChannelFuture channelFuture = bootstrap.connect().sync();
            channelFuture.channel().closeFuture().sync();
        }finally{
            nioEventLoopGroup.shutdownGracefully().sync();
        }
    }
}

可以看到,我们在main方法中直接new这个自定义客户端启动类,传入一个服务器ip和服务器端口,调用start()方法,就可以直接获取服务端连接并通信了,十分简洁。要注意的是,我们自定义的这个NettyTestClient类,本身不是一个线程启动类,所以我们编写的start()方法,也是一个普通的方法。
在start()方法中,首先创建了一个客户端的引导类“Bootstrap”,该引导类是用来启动和通过配置引导服务连接的类。接着创建了一个EventLoopGroup类的对象,该EventLoopGroup类就是一个“事件循环组”,它的底层是一个线程池。它有多重实现,这里我们选择其中一种常用的实现类“NioEventLoopGroup”。
然后引导类“Bootstrap”开始进行引导配置,这里分四步:
(1)首先将NioEventLoopGroup线程组注册到引导类的组配置中
(2)然后再去注册一个Channel管道,指定注册的管道类型为“NioSocketChannel.class”,即为NIO的API的Channel类。
(3)然后再注册远程连接服务器的地址和端口。
(4)最后一步是最重要的,也是我们业务逻辑主要的开发部分,即注册的Handler处理类。

这里我们着重说一下bootstrap如何初始化Handler业务处理类,handler里面传入了ChannelInitializer通道初始化类,该类会重写在对通道进行初始化时的逻辑,在initChannel方法中就对建立连接的通道SocketChannel进行初始化操作。在该方法中,我们拿到了连接的SocketChannel通道对象,然后获取该通道的“流水线”-------pipeline,然后将我们处理业务逻辑的Handler注册进该流水线,也可以继续在后面调用“addLast”方法,来继续注册多个Handler处理类到“流水线”,以实现不同管道的请求handler汇集在一起,形成一个数组,自前向后的去处理信息。

当客户端引导类Bootstrap完成上面四步的引导配置后,就可以进行服务器的连接动作了。其中的“connect()”就是进行连接操作,而“sync()”为同步方法,就是一直等待它连接成功为止。连接成功之后,返回线程的执行结果对象channelFuture,该channelFuture执行channel()方法获取结果,然后执行“closeFuture()”关闭结果对象。
最后会执行nioEventLoopGroup.shutdownGracefully().sync()将整个线程组关闭。

2、客户端业务处理类
我们上面传入的业务处理Handler为NettyTestClientHandler,编写的样例代码如下:

package cn.com.netty.test.client;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class NettyTestClientHandler extends SimpleChannelInboundHandler<ByteBuf>{

    //客户端连接服务器后被调用
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端连接服务器,开始发送数据......");
        byte[] req = "QUERY TIME ORDER".getBytes();//请求消息
        ByteBuf firstMessage = Unpooled.buffer(req.length);//发送类
        firstMessage.writeBytes(req);//发送
        ctx.writeAndFlush(firstMessage);//flush
    }

    //从服务器收到数据后调用
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
        System.out.println("client 读取server数据..");
        //服务器返回消息后
        byte[] req = new byte[buf.readableBytes()];//创建一个存储信息的byte数组
        buf.readBytes(req);//将buffer中的数据读到byte数组中
        String body = new String(req,"UTF-8");//将byte数组转换为String(并转码)
        System.out.println("服务端数据为:"+body);//打印服务端反馈的信息
    }

    //发生异常时调用
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("client exceptionCaught...");
        //释放资源
        ctx.close();
    }
}

首先该类继承了SimpleChannelInboundHandler类,即是一个“Inbound”类型的处理器,Netty框架拿到的数据全部会丢给InboundHandler来处理,其中泛型中的“ByteBuf”就是要处理的数据对象。

继承SimpleChannelInboundHandler类,可以重写很多方法,这里重写了以下三个方法:
①channelActive(ChannelHandlerContext ctx)
②channelRead0(ChannelHandlerContext ctx, ByteBuf buf)
③exceptionCaught(ChannelHandlerContext ctx, Throwable cause)

其中channelActive方法,即当channel通道被打通(激活),即连接成功之后,要做的事;channelRead0方法,为从服务端收到数据后需要做的事;exceptionCaught方法及发生异常的时候需要做的事。

首先我们在channelActive方法中编写连接成功之后,需要做的事。连接成功后,我们自然要向服务端发送信息,所以这里我们构造一个请求信息(类型为byte),然后使用Unpooled.buffer方法,创建一个拥有请求数据大小缓存的一个发送ByteBuf,最后执行writeBytes将请求信息写到缓存中去,然后执行上下文对象ChannelHandlerContext的writeAndFlush方法,进行flush,此时可能会直接将信息直接发出去,亦或是在有多个Handler的情形下。交给下一个Handler进行处理。

其次我们在channelRead0方法中编写接收到服务端反馈回来的信息后,需要做的事。接收到信息后,首先创建一个存储信息的byte数组,然后将ByteBuf缓存对象中的信息读取到新建的byte数组中,之后按照需要去处理这个byte反馈信息。这里我们将其转换为String字符串打印出来了。

最后我们在exceptionCaught方法中编写了当出现异常时候,需要做的事。此时我们获取上下文对象ChannelHandlerContext,执行其close方法进行资源的释放。

至此,客户端的样例代码编写完毕。

下面编写服务端的样例代码。紧跟《高性能nio框架netty(下)》。

参考:
传智播客《2017零基础大数据》教学视频

转载请注明出处:https://blog.csdn.net/acmman/article/details/87448666

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光仔December

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值