《Netty实战》读书笔记(一)

166 阅读6分钟

Netty——异步和事件驱动

  • JAVA网络编程
  • Netty简介
  • Netty核心组件

Netty是什么?

基于java NIO的进一步升级,也是真正意义上的异步非阻塞的模型,使用最小的资源开销来达到最大的性能

  • 支持多种通讯协议

  • 高度的抽象,隐藏底层实现细节

  • 简单易用

  • IO性能良好

核心思想:使用最小的开销来实现最高效率的能力

核心组件:

  • Channel
  • 回调
  • Future 设计模式
  • 事件和ChannelHandle

Channel

可以看做一个黑盒,载体,可以输入和输出

回调

一个指向被提供给另一个方法的引用

Future

核心组件之一

用于实现异步通知

interface java.util.concurrent.Future是JDK预置的接口的完成通知形式

由 ChannelFutureListener 提供的通知机制

每次的IO都会产生一个ChannelFutrue

事件和ChannelHandler

由于Netty是事件驱动的,将整个IO拆分为下面的事件

  • 连接事件

  • 数据读取

  • 用户事件

  • 错误事件

  • 关闭连接事件

  • 数据写入

谁在使用它?

  • Elastic Search
  • Kibana

为什么要学netty?

  • 高并发框架
  • 极高的IO性能
  • 对于NIO有进一步的扩展
  • 将关注点由底层转向业务实现
  • 大大的降低了网络编程,IO,多线程复杂结构

Netty的同类框架

如何学习?

  • 看书
  • 写案例
  • netty的历史
代理设置:
127.0.0.1
端口:
1080
具体设置:
windows10.microdone.cn

JAVA 网络编程

阻塞I/O样板代码

大致流程:

  • 创建一个ServerSocket,用来监听客户端请求
  • 调用**accept()**核心方法,该方法会产生阻塞,等待请求创建
  • 使用socket获取输出和输出流对象,一般使用包装buffer对应提高读写效率
  • 创建流对象
  • 读取数据,处理请求数据,得到处理后的响应信息,最后在进行处理

accept()方法做了什么事情:

  1. 初始化,创建socketImpl()实现类
  2. 校验套接字
  3. 检测请求是否合法
  4. 异常处理

阻塞I/O的优点:

  • 简单易懂

  • 对于并发量不是很大情况可以很好的处理

阻塞I/O的缺点:

  • 每一个请求就要建立一个线程,并且要一直等待,资源浪费
  • 开启的线程数取决于操作系统,
  • 上下文切换的代价昂贵

示范代码:

public static void main(String[] args) throws IOException {
        // 1. 创建一个套接字
        ServerSocket socket = new ServerSocket(8080);
        // 创建一个请求对象
        Socket accept = socket.accept();
        // 获取输入流和输出流
        BufferedReader in = new BufferedReader(new InputStreamReader(accept.getInputStream()));

        PrintWriter out = new PrintWriter(accept.getOutputStream());

        // 创建流对象
        String request, response;
        while ((request =in.readLine())!=null){
            // 读取字节流数据
            if(request.equals("Done")){
                break;
            }
            // 调用服务器处理流数据的方法,处理请求流
            response = processRequest(request);
            out.println(response);
        }
    }

JAVA NIO

原理:调用本地套接字的非阻塞套接字

  • 使用setsockept()配置套接字,读写的时候没有数据可以返回
  • 使用事件通知的API注册非阻塞的套接字

非阻塞I/O是 jdk1.4引入的

这里有个很大的误区:

NIO是不是非阻塞的?

结论:错误

原因:

NIO是 1.4引入的,它不再新了,而且该API其实内部还是阻塞的,后面会讲到

class java.nio.channels.Selector 非阻塞IO的实现类

优点:

  • 较少的线程处理很多链接
  • 没有IO的时候可以执行其他操作

编写第一个Netty应用程序

环境准备

  • Apach maven
  • JDK1.7以上

开发工具

  • IntelinJ IDEA 2019版本

编写服务端

ChannelHandler - 接受客户端数据

  • channelRead()
    • 对每个传入的消息都要调用;
  • channelReadComplete()
    • 通过ChannelInboundHandler 最后一个对 channel-Read() 的调用是当前批量读取中的最后一条消息;
  • exceptionCaught()
    • 在读取操作期间,有异常抛出时会调用j

方式:

继承CahnelInboundHandlerAdapter的方式

不捕获异常,会发生什么情况

每个Channel 拥有一个关联的ChannelPipeline,如果没有实现,会放到Piepeline的尾端,但是,必须有一个实现了捕获异常的Caught()方法

  • 针对不同事件,调用handler
  • 使用责任链加上事件响应的方式处理

引导服务器启动

  • 业务逻辑增加了之后,需要启动这个Channel监听请求
  • 配置Channel,将入站信息通知给handler实例

创建传输的server

  • 建立我们自己实现的channelHandler EchoServer对象

  • 创建EventLoopGroup,用来处理channel

  • 创建boostrapServer,用于建立套接字连接

  • 设置group

    • 使用NIOSocket 套接字
    • 设置请求接受端口
    • 设置子channel的channelHandler
    • 初始化调用,设置新的channel -> 就是我们自我实现的EchoServer
  • 设置future, 使用异步开启服务器并且阻塞线程等待结果

  • 获取futurecloseFuture,同样阻塞等待所有任务结束之后发送通知

  • 最后,finally 方法关闭group,并且释放所有的资源

如果想要使用阻塞的IO要如何处理:

OioServerSocketChannel OioEventLoopGroup

示范代码:

EchoServer:我们实现的channelHandler

public class EchoServer extends ChannelInboundHandlerAdapter {

    /**
     * 读取事件
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in = (ByteBuf) msg;
        System.err.println("Server receive" + in.toString(CharsetUtil.UTF_8));

    }

    /**
     * 最后一次读取完成之后,发送通知到channelHandler
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * 读取的过程
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

EchoServerClinet:服务器客户端开启

public class EchoServerClient {

    private static final int PORT = 8080;

    public static void main(String[] args) throws InterruptedException {
        if(args.length > 1){
            System.err.println("server ");
        }
        final EchoServer echoServer = new EchoServer();
        // 创建事件驱动器 EventLoopGroup
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try{
            // 创建server bootstrap
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            // 1. 指定使用NIO传输的渠道
            // 2. 指定端口
            // 3. 添加一个echoseverhandler 到子channel的chanelPipeline
            serverBootstrap.group(eventLoopGroup)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(PORT))
                    .childHandler(new ChannelInitializer() {
                        // 很关键,需要使用pipeline加入事件来帮我们处理channel
                        protected void initChannel(Channel ch) throws Exception {
                            ch.pipeline().addLast(echoServer);
                        }
                    });
            // 阻塞当前线程,异步绑定服务器
            ChannelFuture channelFuture = serverBootstrap.bind().sync();
            // 获取当前channel的closeFuture,阻塞直到线程完成
            channelFuture.channel().closeFuture().sync();

        }finally {
            // 关闭group 释放资源
            eventLoopGroup.shutdownGracefully().sync();
        }
    }

}

编写客户端

客户端需要做的事情比较简单,只需要如下步骤:

  • 建立一个与socket的连接
  • 发送一条或者多条消息
  • 对于每一条消息,等待服务器返回处理的数据
  • 关闭连接

chanelHandler实现客户端

  • 继承SimpleChannelInboundHandler
  • 实现方法channelRead0()从服务器收到消息之后,会调用这个方法
  • 重写channelActive(),被通知channel活跃的时候发送通知
  • 重写exceptionCaught(),处理异常

为什么客户端要使用SimpleChannelInboundHandler而不是服务端的ChannelInboundHandlerAdapter

SimpleChannelInboundHandler 在处理完传入消息之后,理论上一个请求就已经被处理完成,此时channel会该类默认会释放这条传入消息的内存引用

ChannelInboundHandlerAdapter处理完消息之后,此时channelRead()结果不一定返回,因为整个过程都是异步的

问题又来了,什么时候会释放channelRead()处理之后的消息资源呢

答:当channelComplete() writeAndFlush()之后,该消息资源才会被真正释放

运行案例

  • 启动EchoServerClient
  • 启动EchoClientServer

总结

以上就是一个最为简单的netty Demo

后续的笔记将会深入到netty的几个关键点

  • Channel
  • CahnnelHandler
  • Futrue
  • BootstrapServer
  • EventLoop
  • EventLoopGroup