netty学习一(netty入门程序)

 一、什么是netty

Netty是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。

由JBOSS提供的基于Java NIO的开源框架,Netty提供异步非阻塞、事件驱动、高性能、高可靠、高可定制性的网络应用程序和工具,可用于开发服务端和客户端。

二、为什么使用netty

JAVA原先是采用的是传统的BIO,为什么后来又研发出了NIO呢?
首先看看传统的基于同步阻塞IO(BIO)的线程模型图

 

BIO主要存在以下缺点:
1.从线程模型图中可以看到,一连接一线程,由于线程数是有限的,所以这样的模型是非常消耗资源的,
最终也导致它不能承受高并发连接的需求
2.性能低,因为频繁的进行上下文切换,导致CUP利用率低
3.可靠性差,由于所有的IO操作都是同步的,即使是业务线程也如此,所以业务线程的IO操作也有可能被阻塞,
这将导致系统过分依赖网络的实时情况和外部组件的处理能力,可靠性大大降低
上面的原因就是导致早期的高性能服务器为什么不选用JAVA开发,而是选用C/C++的重要原因。
为了解决上面的问题,NIO横空出世,下面是NIO的线程模型图

1.NIO采用了Reactor线程模型,一个Reactor聚合了一个多路复用器Selector,它可以同时注册、监听和轮询
成百上千个Channel,这样一个IO线程可以同时处理很多个客户端连接,线程模型优化为1:N(N<最大句柄、数),
或M:N(M通常为CUP核数+1)
2.避免了IO线程频繁的上下文切换,提升了CUP的效率
3.所有的IO操作都是异步的,所以业务线程的IO操作就不用担心阻塞,系统降低了对网络的实时情况和外部组件
的处理能力的依赖

为什么不直接用JDK原生的NIO而选用Netty框架?
先看看JDK的NIO中服务端和客户端的时序图
服务端:

客户端:

从图中我们可以看到,使用JDK原生NIO的不足之处
1.NIO的类库和API相当复杂,使用它来开发,需要非常熟练地掌握Selector、ByteBuffer、ServerSocketChannel、SocketChannel等
2.需要很多额外的编程技能来辅助使用NIO,例如,因为NIO涉及了Reactor线程模型,所以必须必须对多线程和网络编程非常熟悉才能写出高质量的NIO程序
3.想要有高可靠性,工作量和难度都非常的大,因为服务端需要面临客户端频繁的接入和断开、网络闪断、半包读写、失败缓存、网络阻塞的问题,这些将严重影响我们的可靠性,而使用原生NIO解决它们的难度相当大。
4.JDK NIO中著名的BUG--epoll空轮询,当select返回0时,会导致Selector空轮询而导致CUP100%,官方表示JDK1.6之后修复了这个问题,其实只是发生的概率降低了,没有根本上解决。
那么为什么要用Netty呢?
1.API使用简单,更容易上手,开发门槛低
2.功能强大,预置了多种编解码功能,支持多种主流协议
3.定制能力高,可以通过ChannelHandler对通信框架进行灵活地拓展
4.高性能,与目前多种NIO主流框架相比,Netty综合性能最高
5.高稳定性,解决了JDK NIO的BUG
6.经历了大规模的商业应用考验,质量和可靠性都有很好的验证。

三、Netty能提供什么服务?
1.开发异步非阻塞的TCP网络应用程序
2.开发异步非阻塞的UDP网络应用程序
3.开发异步文件传输程序
4.开发异步HTTP程序的服务端和客户端
5.提供多种编解码的集成框架,包括谷歌Protobuf、JBossMarshalling、Java序列化、压缩编解码、XML解码、
字符串编解码等都可以由用户直接使用
6.提供形式多样的编解码基础类库,可以方便地进行私有协议栈编解码框架的二次开发
7.基于职责链的Pipeline-Handler机制,可以方便地对网络事件进行拦截和定制
8.所有的IO操作都是异步的,用户可以通过Future-Listeren机制主动get结果或者等IO线程完成操作之后主动Notify来通知,
用户业务线程不需要同步等待
9.基于链路空闲事件监测的心跳机制
10.流量控制和整形
 

好的,介绍完了netty,我们来查看一下netty的入门程序,话不多说,直接上代码:

TimeServer类:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;


/**
 * @ProjectName: nettyTes
 * @Package: PACKAGE_NAME
 * @ClassName: TimeServer
 * @Author: Administrator
 * @Description: ${description}
 * @Date: 2019/10/12 11:52
 * @Version: 1.0
 */
public class TimeServer {

    public void bind(int port) throws Exception {
        // 配置服务端的NIO线程组,NioEventLoopGroup是个线程组,它包含了一组N10线程,专门用于网络事件的处理,实际上它们就是Reactor线程组。
        // 这里创建两个的原因是一个用于服务端接受客户端的连接,另一个用于进行SocketChannel的网络读写。
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //ServerBootstrap对象,它是Netty用于启动NlO服务端的辅助启动类,目的是降低服务端的开发复杂度。
            ServerBootstrap b = new ServerBootstrap();
            //将两个NIO线程组当作入参传递到ServerBootstrap中。
            b.group(bossGroup, workerGroup);
            //设置创建的Channel 为NioServerSocketChannel,它的功能对应于JDK的NIO 类库中的ServerSocketChannel类
            b.channel(NioServerSocketChannel.class);
            //NioServerSocketChannel的TCP参数,此处将它的backlog设置为1024,
            b.option(ChannelOption.SO_BACKLOG, 1024);
            //绑定1/0事件的处理类ChildChannelHandler,它的作用类似于Reactor模式中的handler类,主要用于处理网络I/0事件,例如记录日志、对消息进行编解码等。
            b.childHandler(new ChildChannelHandler());
            //调用它的bind方法绑定监听端口,随后,调用它的同步阻塞方法sync等待绑定操作完成。
            // 完成之后Netty会返回一个ChannelFuture,它的功能类似于JDK的java.util.concurrent.Future,主要用于异步操作的通知回调。
            ChannelFuture f = b.bind(port).sync();
            //使用f.channel().closeFut1ue().sync()方法进行阻塞,等待服务端链路关闭之后main函数才退出。
            f.channel().closeFuture().sync();
        } finally {
            // 优雅退出,释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel arg0) throws Exception {
            arg0.pipeline().addLast(new TimeServerHandler());
        }

    }

    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                // 采用默认值
            }
        }
        new TimeServer().bind(port);
    }

}
TimeServerHandler类:
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

/**
 * @ProjectName: nettyTes
 * @Package: PACKAGE_NAME
 * @ClassName: TimeServerHandler
 * @Author: Administrator
 * @Description: TimeServerHandler继承自ChannelHandlerAdapter,它用于对网络事件进行读写操作
 * @Date: 2019/10/12 11:57
 * @Version: 1.0
 */
public class TimeServerHandler extends ChannelHandlerAdapter {


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        //将msg转换成Netty的ByteBuf对象
        ByteBuf buf = (ByteBuf) msg;
        //ByteBuf的readableBytes方法可以获取缓冲区可读的字节数,根据可读的字节数创建byte数组
        byte[] req = new byte[buf.readableBytes()];
        //通过ByteBuf的readBytes方法将缓冲区中的字节数组复制到新建的byte数组中
        buf.readBytes(req);
        String body = new String(req, "UTF-8");
        System.out.println("The time server 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());
        //write方法异步发送应答消息给客户端。
        ctx.write(resp);
    }


    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //flush方法,它的作用是将消息发送队列中的消息写入到SocketChannel  中发送给对方。从性能角度考虑,为了防止频繁地唤醒Selector进行消息发送,
        // Netty的write方法并不直接将消息写入SocketChannel中,
        // 调用write方法只是把待发送的消息放到发送缓冲数组中,再通过调用flush方法,将发送缓冲区中的消息全部写到SocketChannel中。
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        ctx.close();
    }
}
TimeClient类
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * @ProjectName: nettyTes
 * @Package: PACKAGE_NAME
 * @ClassName: TimeClient
 * @Author: Administrator
 * @Description: ${description}
 * @Date: 2019/10/12 12:00
 * @Version: 1.0
 */
public class TimeClient {
    public void connect(int port, String host) throws Exception {
        // 配置客户端NIO线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.TCP_NODELAY, true);
            b.handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch)
                                throws Exception {
                            ch.pipeline().addLast(new TimeClientHandler());
                        }
                    });

            // 发起异步连接操作
            ChannelFuture f = b.connect(host, port).sync();
            // 当代客户端链路关闭
            f.channel().closeFuture().sync();
        } finally {
            // 优雅退出,释放NIO线程组
            group.shutdownGracefully();
        }
    }

    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                // 采用默认值
            }
        }
        new TimeClient().connect(port, "127.0.0.1");
    }
}
ClientHandler类
package stickorApartPackageResolveOne.one;

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

import java.util.logging.Logger;

/**
 * @ProjectName: nettyTes
 * @Package: stickorApartPackageResolveOne.one
 * @ClassName: ClientHandler
 * @Author: Administrator
 * @Description: ${description}
 * @Date: 2019/10/12 17:03
 * @Version: 1.0
 */
public class ClientHandler extends ChannelHandlerAdapter {

    private static final Logger logger = Logger
            .getLogger(ClientHandler.class.getName());

    private int counter;
    private byte[] req;

    public ClientHandler() {
        req = ("QUERY TIME ORDER" + System.getProperty("line.separator") ).getBytes();

        System.out.print(req.length);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ByteBuf message = null;
        for (int i = 0; i < 30; i++) {
            message = Unpooled.buffer(req.length);
            message.writeBytes(req);
            ctx.writeAndFlush(message);
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        String body = (String) msg;
        System.out.println("Now is : " + body + " ; the counter is : " + ++counter);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 释放资源
        logger.warning("Unexpected exception from downstream : " + cause.getMessage());
        ctx.close();
    }
}

 

使用netty框架,基本上都是按照这个模板来编写,下面展示运行结果:

服务端:

客户端:

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值