Netty学习之Netty介绍

Netty学习之Netty介绍

前言

本周开始学习Netty,主要的参考资料是《Netty In Action》原版书,这本书写得真好,一开始学习Netty的时候,看得有些云里雾里,后面弄懂之后,再回头看一下这本书,就发现这本书真的言简意赅,精炼地将Netty的各个组件展现出来。

传统的Java网络编程

在传统的Java网络编程中,是基于阻塞形式的IO,在这种形式的IO模型中,由于当数据没有到来的时候,对应的线程会阻塞,所以,需要为每一个请求都分配一个线程(当然,可以通过线程池来复用线程,从而减少线程创建以及销毁的代价),然而,实际上需要消耗的资源还是非常巨大的,比如,如果同一时刻有1k的并发量,那么至少就需要有1k的左右的线程来处理(数据不严谨,但是足够表达意思了)。

如下代码所示

public class OIo {

    private static boolean isStop = false;
    private static ExecutorService executorService;

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(8080);
        // 通过线程池的方式来处理
        executorService = Executors.newCachedThreadPool();
        // 监听多个请求
        while (!isStop) {
            Socket clientSocket = server.accept();
            // 每个请求分配对应的线程来处理
            // 由于是阻塞IO,所以如果不分配线程来处理,会导致同一时间只能处理
            // 一个请求
            executorService.submit(new NetworkHandler(clientSocket));
        }
    }

    static class NetworkHandler implements Runnable {

        private Socket clientSocket;

        public NetworkHandler(Socket socket) {
            this.clientSocket = socket;
        }

        public void run() {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                PrintWriter writer = new PrintWriter(clientSocket.getOutputStream());
                String requestContent, responseContent;
                // 建立连接之后不停读取数据
                while((requestContent = reader.readLine()) != null) {
                    if("DONE".equals(requestContent)) {
                        break;
                    }
                    responseContent = handleRequest(requestContent);
                    writer.write(responseContent);
                }
                writer.flush();
                reader.close();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private String handleRequest(String request) {
            System.out.println("request: " + request);
            return "handed";
        }
    }
}

显然,上面的这种模式在并发比较大的时候是不太适合使用的,于是,在Java4之后,引入了Nio模式,通过选择器(selector)来监视多个channel(多个连接),当数据到达时负责与对应的channel相互,这样,在建立连接的时候,就不需要为每个连接都分配一个线程了,Nio是基于非阻塞形式的IO,所以,相对于传统的阻塞IO,有了很大的性能提升,然而,由于Java中提供的Nio过于底层,而且重复的操作比较多,所以,很多的大牛们就开始对其进行抽象,封装,Netty就是其中的一个了。

Netty介绍

Netty是一个异步的,事件驱动的网络应用框架。

具有众多优秀的特性

  • 通用的API(支持阻塞跟非阻塞socket)
  • 支持TCP、UDP
  • 逻辑以及网络底层处理分隔
  • 灵活及可扩展的事件模型
  • 高度自定义线程模型
  • 高吞吐量,低延迟
  • 消耗资源低
  • 支持SSL/TSL
  • 社区支持

Netty官网:Netty官网

通过上面的介绍,我们应该明确的是Netty是一个网络框架,所以,它所做的事情其实就是网络的处理(建立连接、发送数据等),这一点需要搞清楚(一开始就是没有弄清楚Netty是什么,所以在学习的时候出现了混乱)。而我们使用Netty,一般是在它上面进行我们的业务操作(比如实现IM逻辑、实现RPC等等),当然,如果有精力研究其源码,也是非常不错的,据说Netty的源码也是非常值得学习的(先挖个坑,后面有机会一定学习、分析一波)

在上面Netty的介绍的时候,我们看到了异步事件驱动这两个词,先来谈谈异步,与异步对应的还有一个词,同步

同步指的是当请求发出之后,必须等到请求回来,才完成操作,异步则不是,异步是在请求发出之后,立即结束,不用等到请求返回才结束操作。

与这两个词关联的还有阻塞非阻塞,阻塞是指请求发出之后,如果没有数据,则对应的操作会被阻塞(具体的就是对应的线程会进入阻塞态,等到数据能获取,再变为可运行态),非阻塞则是指即使没有数据到来,此时对应的线程可以进行其他的操作,而不会被动进入阻塞态,即不会被阻塞。

关于这四个词语的更多的解析及举例,可以参考知乎的这篇问答:怎样理解阻塞非阻塞与同步异步的区别

对于事件驱动,一般是与异步相结合的,对于同步而言,由于操作会等到出结果了才返回,所以直接调用就能获取结果了(成功/失败),然而,对于异步而言,由于调用后立即返回,所以根据返回内容是没法知道结果的,那怎么知道结果呢,一般是通过注册回调函数来实现,当有结果的时候,事件发出者/监管者会调用对应的回调函数来通知我们操作已经完成了,也就是说,整个操作的过程中,是通过事件来进行沟通,而不是通过直接调用来沟通。理清楚这一点对于学习Netty非常重要,因为Netty中是通过事件来进行消息的传递的。

Netty核心组件

Netty是由几个核心的组件构成的

  • Channels,NIO的基本组件,表示一个打开的连接(网络,文件),可以用于处理一个或者多个不同的I/O操作(读/写)
  • Callbacks,一个方法,提供给另一个方法的引用,用于后者在合适的时候调用,Netty使用回调函数处理事件,当回调函数触发时,事件能够被实现了ChannelHandler接口的处理器处理(其实就是事件发生时,回调函数被调用)(被动)
  • Futures,提供了另外一种通知应用操作已经完成的方法(主动),异步操作的结果占位符,通过该方式,可以在后面访问到异步操作的结果,由于JDK中的Future比较笨重,Netty提供了自己的实现ChannelFuture,用于当异步操作执行的时候。并且允许我们向ChannelFuture注册一个或者多个ChannelFutureListener对象,当操作完成时,这些对象的回调方法operationComplete()会被调用,Netty的每个outBoundI/O均返回一个ChannelFuture,也即不阻塞
  • Events and Handlers,Netty使用不同的事件通知我们状态的改变或者操作的结果,然后我们可以采用不同的机制来处理不同的事件,如:日志记录、数据传输、流控制、应用逻辑等,由于Netty是网络框架,所以事件是根据inbound或者outbound数据流来区分的,如:连接建立或者断开、数据读取、用户事件、错误事件;远程端口打开或者关闭、写或者刷新数据
  • EventLoop,连接到每个Channel用于处理事件,包括:注册事件、分发事件到ChannelHandler、调度行动,每个EventLoop本身由一个线程来驱动,用于处理一个Channel中所有的I/O事件,并且在其生命周期中不会改变

基本上理清楚上面几个组件的作用以及相互的关系,就能掌握Netty的使用了。

第一个Netty应用

为了形象地认识Netty,这里通过一个简单的小例子来理解,顺便配置下环境

首先导入Netty依赖


    io.netty
    netty-all
    4.1.12.Final

对应的小程序

public class EchoServer {

    private final int PORT = 8080;

    public static void main(String[] args) {
        new EchoServer().start();
    }

    public void start() {
        // 创建事件处理器
        EventLoopGroup group = new NioEventLoopGroup();
        // 创建启动器
        ServerBootstrap bootstrap = new ServerBootstrap();
        // 配置启动器
        bootstrap.group(group)
                // 配置建立连接的处理器
                .channel(NioServerSocketChannel.class)
                // 配置连接建立后的处理器
                // 当一个新连接建立后,就会新建一个childHandler
                .childHandler(new ChannelInitializer() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        // 将逻辑处理器添加到channel对应的pipeline中
                        ch.pipeline().addLast(new EchoServerHandler());
                    }
                });
        try {
            // 绑定地址并且建立连接,sync()表示等到连接绑定完成
            ChannelFuture future = bootstrap.bind(PORT).sync();
            // 阻塞直至连接关闭
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            try {
                // 关闭连接,释放资源
                group.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 逻辑处理器
     * 这里是直接将收到的数据发送回去
     */
    class EchoServerHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf data = (ByteBuf) msg;
            System.out.println("request: " + data.toString(CharsetUtil.UTF_8));
            // 将收到的数据发送回去给客户端
            ctx.writeAndFlush(data);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            // 关闭连接
            ctx.close();
        }
    }
}

启动服务之后,直接通过telnet程序进行测试即可。

总结

本小节主要是进行Netty的介绍,包括传统阻塞IO的缺点以及Netty的几个特性,最后通过一个简单的小例子展示了Netty的基础用法,后面将详细学习Netty的几个组件及具体作用。

你可能感兴趣的:(Netty学习之Netty介绍)