Netty 入门 一 (基本操作)

Netty 入门

1.为什么选择netty?
netty是基于java NIO的实现的框架,准确的所是基于NIO2.0也就是AIO实现的,但由于NIO的使用较为繁琐,就出现了netty,通过netty开发的NIO服务器和客户端十分简单,短短几十行代码,就能完成NIO几百行代码才能实现的功能。

2.回顾NIO的开发步骤
(1) 创建ServerSocketChannel,配置他为非阻塞模式。
(2) 绑定监听, 配置TCP 参数,例如backlog的大小。
(3) 创建一个独立的IO线程,用于轮询多路复用器。
(4) 创建selector , 将之前创建的ServerSocektChannel注册到Selector上,监听SelectionKey.ACCEPT事件。
(5) 启动IO线程,在循环体中执行Selector.select()方法,轮询就绪的Channel。
(6) 当轮询到处于就绪状态的Channel时,需要对其进行判断,如果是OP_ACCEPT状态,说明书是新的客户端接入,则调用ServerChannel.accept()方法接收新的客户端。
(7) 设置新接入客户端的链路SocketChannel为阻塞模式,并且配置一些其他的TCP参数。
(8) 将SocketChannel注册到Selector上,jiantingOP_READ操作位。
(9) 如果轮询的Channel为OP_READ,则说明SocketChannel中有新就绪的数据包需要读取,则构造ByteBuffer对象,读取数据包。
(10) 如果轮序的Chanel为OP_WRITE,则说明还有数据没有发送完成,需要继续发送。

一个简单的NIO服务端程序,如果我们直接使用JDK提供的API进行开发,需要经过繁琐的十几项操作才能完成最基础的消息发送和读取,这也是为什么我们要选择Netty等NIO框架原因了。

下面看看如何用Netty实现服务器和客户端的,以时间服务器为例。

TimeServer

public class TimeServer 
{
    public void bind(int port) throws InterruptedException {
        //配置服务器端的NIO线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup,workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 1024)
             .childHandler(new ChildChannelHander());
            ChannelFuture f = b.bind(port).sync();
            //等待服务监听端口关闭
            f.channel().closeFuture().sync();   //只是用于阻塞主线程
        }finally {
            //优雅退出,释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    //IO事件处理类
    private class ChildChannelHander extends ChannelInitializer<SocketChannel>{
        @Override
        protected void initChannel(SocketChannel arg0) throws Exception {
            // TODO Auto-generated method stub
            arg0.pipeline().addLast(new TimeServerHandler());
        }
    }

    public static void main( String[] args )
    {
        int port = 8080;
        try {
            new TimeServer().bind(port);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class TimeServerHandler extends ChannelHandlerAdapter{
    //有数据准备
    @Override
    public void channelRead(ChannelHandlerContext ctx,Object msg) throws Exception{
        ByteBuf buf = (ByteBuf) msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req,"UTF-8");
        System.out.println("The timeserver receive order"+body);
        String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(
                             System.currentTimeMillis()).toString()    : "BAD ORDER";
        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
        ctx.write(resp);
    }
    //数据读取完成后
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception{
        ctx.flush();
    }
    //数据读取出错
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
        System.out.println(cause.getMessage());
        ctx.close();
    }

}
  1. 我们从bind方法开始讲解,首先创建了两个NioEventLoopGroup实例。NioEventLoopGroup是一个线程组,它包含了一组NIO线程,专门用于对网络事件的处理,实际上他们就是Reactor线程组。创建两个线程组的原因是需要一个用于服务器接收客户端的连接,另一个用于SocketChannel的网络读写 (Reactor设计模型中的 多线程模型)。
  2. ServerBootstrap对象是Netty用于启动NIO服务端的辅助启动类,目的是为了降低开发的复杂度。ServerBootstrap的group()方法用于获取两个NIO线程组传(一个用于服务器接收客户端的连接,另一个用于SocketChannel的网络读写)。
  3. 接着设置创建的Channel为NioServerSocketChannel,他的功能对应于JDK NIO类库中的ServerSocketChannel类。
  4. 然后配置NioServerSocketChanel的TCP参数,此处将他的backlog设置为1024 。
  5. 最后绑定IO事件的处理类ChildChannelHandler,他的作用类似于Reactor模式中的handler类,主要用于处理网络IO事件,例如记录消息日志,对消息进行编解码等等。
  6. 接下来调用bind方法绑定监听端口,绑定端口需要一小段时间,调用他的同步阻塞方法sync等待绑定操作完成。完成之后Netty会返回一个ChannelFuture,用于异步操作的通知回调。使用f.channel().closeFuture().sync()方法阻塞主线程,等待服务器关闭后main线程才退出。

以上就是Netty搭建服务器的基本框架,相对使用JDK开发而言,使用Netty框架的复杂度要低很多。
接下来看看TimeServerHandler类是如何实现的:

  1. TimeServerHandler继承于ChannelHandlerAdapter,它用于进行网络事件读取操作,通常我们要重写他的channelRead()方法和exceptionCaught()方法。
  2. channelRead方法中,将msg转换成Netty的ByteBuf对象。通过ByteBuf的readableBytes方法可以获取缓冲区中可读数据的字节数,根据可读字节数创建byte数组,通过Bytebuf的readBytes方法将缓冲区的字节数据复制到新建的byte数组中。通过ChannelHandlerContext的write方法异步发送应答消息给客户端。
  3. 在channelReadComplete中调用了ChanelHandlerContext的flush方法,在channelRead中调用的ChannelHandlerContext.write()方法并不会把数据发送出去,而是把数据放到缓冲数组找那个,必须继续调用flush方法才能真正的把数据发送给客户端。
  4. exceptionCaught重写异常处理方法,异常发生时,关闭ChannelHandlerContext,释放与ChannelHandlerContext相关联的句柄和资源。

TimeClient

public class TimeClient {
    public void connect(int port,String host)throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
             .option(ChannelOption.TCP_NODELAY, true)
             .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) {
                    ch.pipeline().addLast(new TimeClientHandler());
                }
             });

            //发异步连接操作
            ChannelFuture f = b.connect(host,port).sync();
            //等待客户端链路关闭
            f.channel().closeFuture().sync();
        }finally {
            //优雅退出,释放NIO线程组
            group.shutdownGracefully();
        }
    }
    public static void main(String[] args) throws Exception {
        int port = 8080;
        new TimeClient().connect(port, "127.0.0.1");
    }
}


class TimeClientHandler extends ChannelHandlerAdapter{
    private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName());
    private ByteBuf firstMessage;

    public TimeClientHandler(){
        byte[] req = "QUERY TIME ORDER".getBytes();
        firstMessage = Unpooled.buffer(req.length);
        firstMessage.writeBytes(req);
    }

    //连接成功时
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ctx.writeAndFlush(firstMessage);
    }

    @Override
    //有数据准备好时(有数据可读时)
    public void channelRead(ChannelHandlerContext ctx,Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, "UTF-8");
        System.out.println("Now is : " + body);
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) {
        logger.warning("Unexpected exception from downstream : "+cause.getMessage());
        ctx.close();
    }
}

客户端的行为原理和服务器的基本一致。

小结
本篇文章介绍了Netty的入门应用,与传统的NIO开发进行了比较,可以发现,netty的代码更加简洁,开发难度更低,扩展性也更好,非常适合作为基础通信框架被用户集成和使用。
接下的文章来会讲解用netty提供的编码功能解决读半包问题。

注:Netty用户指南:
http://ifeve.com/netty5-user-guide/

猜你喜欢

转载自blog.csdn.net/huxiny/article/details/80336440