为什么要用Netty以及Netty编程入门

1.原生NIO存在的问题

原生NIO有一个最大的问题就是epoll bug,它会导致Selector空轮询,最终导致CPU 占用率100%,直到JDK1.7该问题都没有完全解决,其次NIO模型开发的工作量比较大,需要考虑很多问题如客户端断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理

2.Netty的优点

netty是JBoss公司出品的一个开源的java网络应用框架,它是基于NIO的主从Reactor模型的同步非阻塞IO
1.更安全可靠:支持SSL/TLS协议
2.更高效:支持NIO的零拷贝,更加高效的socket底层
3.更灵活:支持多种decoder encoder,可以对TCP粘包分包自动化,可以接受处理线程池,支持重连心跳检测,可配置化IO线程、TCP的参数、TCP接受发送缓冲区的参数等

3.Netty原理详解

在这里插入图片描述
1.Netty抽象出两组线程池 BossGroup 专门负责接收客户端的连接, WorkerGroup 专门负责网络的读写且BossGroup 和 WorkerGroup 类型都是 NioEventLoopGroup,相当于一个事件循环组, 这个组中含有多个NioEventLoop事件循环,它是一个可以不断去循环处理任务的线程,每个NioEventLoop 都有一个selector , 用于监听绑定在其上的socket的网络通讯
2.Boss的NioEventLoop 循环执行的步骤有3步
(1)轮询accept 事件
(2)处理accept 事件 , 与client建立连接 , 生成NioScocketChannel , 并将其注册到某个worker NIOEventLoop 上的 selector
(3)处理任务队列的任务 , 即 runAllTasks
3.每个 Worker NIOEventLoop 循环执行的步骤
(1)轮询read, write 事件
(2)处理i/o事件, 即read , write 事件,在对应NioScocketChannel 处理
(3)处理任务队列的任务 , 即 runAllTasks
4.每个Worker NIOEventLoop 处理业务时,会使用pipeline(管道), pipeline 中包含了 channel , 即通过pipeline 可以获取到对应通道, 管道中维护了很多的 处理器

4.Netty入门编程

  • 客户端
public class NettyClient {
    public static void main(String[] args) throws Exception {

        //客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();


        try {
            //创建客户端启动对象
            //注意客户端使用的不是 ServerBootstrap 而是 Bootstrap
            Bootstrap bootstrap = new Bootstrap();

            //设置相关参数
            bootstrap.group(group) //设置线程组
                    .channel(NioSocketChannel.class) // 设置客户端通道的实现类(反射)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyClientHandler()); //加入自己的处理器
                        }
                    });

            System.out.println("客户端 ok..");

            //启动客户端去连接服务器端
            //关于 ChannelFuture 要分析,涉及到netty的异步模型
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
            //给关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        }finally {

            group.shutdownGracefully();

        }
    }
}
public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    //当通道就绪就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client " + ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, server: (>^ω^<)喵", CharsetUtil.UTF_8));
    }

    //当通道有读取事件时,会触发
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ByteBuf buf = (ByteBuf) msg;
        System.out.println("服务器回复的消息:" + buf.toString(CharsetUtil.UTF_8));
        System.out.println("服务器的地址: "+ ctx.channel().remoteAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
  • 服务端
public class NettyServer {
public static void main(String[] args) throws Exception {

        //1.创建两个线程组 bossGroup 和 workerGroup  其中bossGroup 只是处理连接请求 , 真正的和客户端业务处理,会交给 workerGroup完成两个都是无限循环   bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数默认实际 cpu核数 * 2
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(); //8

        try {
            //2.创建服务器端的启动对象,配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();

            //3.使用链式编程来进行设置
            bootstrap.group(bossGroup, workerGroup) //设置两个线程组
                    .channel(NioServerSocketChannel.class) //使用NioSocketChannel 作服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
                    .handler(null) // 该 handler对应 bossGroup , childHandler 对应 workerGroup
                    .childHandler(new ChannelInitializer<SocketChannel>() {//创建一个道初始化对象(匿名对象)
                        //给work关联的pipeline 设置处理器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                     System.out.println("客户socketchannel hashcode=" + ch.hashCode()); //可以使用一个集合管理 SocketChannel, 再推送消息时,可以将业务加入到各个channel 对应的 NIOEventLoop 的 taskQueue 或者 scheduleTaskQueue
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    }); // 给我们的workerGroup 的 EventLoop 对应的管道设置处理器

            System.out.println(".....服务器 is ready...");

            //绑定一个端口并且同步, 生成了一个 ChannelFuture 对象
            //启动服务器(并绑定端口)
            ChannelFuture cf = bootstrap.bind(6668).sync();

            //给cf 注册监听器,监控我们关心的事件

            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (cf.isSuccess()) {
                        System.out.println("监听端口 6668 成功");
                    } else {
                        System.out.println("监听端口 6668 失败");
                    }
                }
            });


            //对关闭通道进行监听,当监听到关闭事件时才会关闭
            cf.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

}
public class ServerHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {

    }

    //接收数据
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println(ctx.channel().remoteAddress()+":消息:"+msg);
        ctx.writeAndFlush(msg);
    }
}

5.代码描述

客户端与服务端均由各自的启动辅助类BootStrap开始,开始执行链式编程,其实就是类似于get与set方法,设置一些参数而已,重要的就是.group .channel .childHandler这三个必须设置好,其中前两个是在给BossGroup做配置,后面的childHandler根据名字就知道是给子事件循环组即WorkGroup做配置,Netty模型中也是work做真实的事件处理,除此之外你还可以.option或者childoption(依然父子关系)指定超时或者tcp层缓冲区大小等等的设置,详细说几个配置:
.channel(NioServerSocketChannel.class) //代表服务端以NioServerSocketChannel作为通道类型
.childHandler(new ChannelInitializer() //ChannelInitializer是用于channel注册到eventLoop后对其实例化的一个类,其继承于ChannelInboundHandler接口,是一个抽象类不能直接使用,要实现其中的 initChannel抽象方法,指定其类型为NioSocketChannel,因为workGroup最终是要与Client连接的,所以你会发现NettyClient中的也是NioSocketChannel类型。
而initChannel方法就是去对Handler的设置,首先需要去创建ChannelPipeline容器(源码中也是这样),这个容器是一个双向链表,用addLast方法添加各种我们需要的处理器,编码器解码器以及我们自定义的Handler等等
其中详细说一下ChannelPipeline:
Channelpipeline是一个双向链表,ChannelHandlerContext看作链表的节点,ChannelHandler则为每个节点中保存的一个属性对象
在这里插入图片描述
那其实这里的ChannelHandler其实就是节点中具体放的东西,像编码器解码器这些,以及这里最重要的我们自己编写的SeverHandler,这个接口本身没有太多方法,是以继承重写的形式,因而我们自己编写SeverHandler也是一样,去继承重写相关方法即可,那么继承什么呢?这里得先说一下,既然ChannelPipeline是一个双向链表,那么就可以来回前后两个方向去运行,我们把从head到tail即
ChannelInboundHandlerAdapter或者SimpleChannelInboundHandler去重写相关方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值