zoukankan      html  css  js  c++  java
  • Netty 入门初体验

    Netty简介

    Netty是一款异步的事件驱动的网络应用程序框架,支持快速开发可维护的高性能的面向协议的服务器和客户端。Netty主要是对java 的 nio包进行的封装

    为什么要使用 Netty

    上面介绍到 Netty是一款 高性能的网络通讯框架,那么我们为什么要使用Netty,换句话说,Netty有哪些优点让我们值得使用它,为什么不使用原生的 Java Socket编程,或者使用 Java 1.4引入的 Java NIO。接下来分析分析 Java Socket编程和 Java NIO。

    Java 网络编程

    首先来看一个Java 网络编程的例子:

    public static void main(String[] args) {
            try {
                ServerSocket serverSocket = new ServerSocket(8888);
                Socket socket = serverSocket.accept();
                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String line = "";
                while ((line = reader.readLine()) != null) {
                    System.out.println("received: " + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
    }
    复制代码

    上面展示了一个简单的Socket服务端例子,该代码只能同时处理一个连接,要管理多个并发客户端,需要为每个新的客户端Socket创建一个 新的Thread。这种并发方案对于中小数量的客户端来说还可以接受,如果是针对高并发,超过100000的并发连接来说该方案并不可取,它所需要的线程资源太多,而且任何时候都可能存在大量线程处于阻塞状态,等待输入或者输出数据就绪,整个方案性能太差。所以,高并发的场景,一般的Java 网络编程方案是不可取的。

    Java NIO

    还是先来看一个 Java NIO的例子:

    public class ServerSocketChannelDemo {
        private ServerSocketChannel serverSocketChannel;
        private Selector selector;
    
        public ServerSocketChannelDemo(int port) throws IOException {
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        }
    
        public void listener() throws IOException {
            while (true) {
                int n = selector.select();
                if (n == 0) {
                    continue;
                }
                Iterator iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = (SelectionKey) iterator.next();
                    if (selectionKey.isAcceptable()) {
                        ServerSocketChannel server_channel = (ServerSocketChannel) selectionKey.channel();
                        SocketChannel socketChannel = server_channel.accept();
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector, SelectionKey.OP_READ);
                    }
                    if (selectionKey.isReadable()) {
                        //如果通道处于读就绪的状态
                        //读操作
                        //TODO
                    }
                }
            }
        }
    }
    复制代码

    NIO的核心部分主要有:

    • 通道 Channel
    • 缓冲区 Buffer
    • 多路复用器 Selector

    选择器 Selector 是 Java 非阻塞 I/O实现的关键,将通道Channel注册在 Selector上,如果某个通道 Channel发送 读或写事件,这个Channel处于就绪状态,会被Selector轮询出来,进而进行后续I/O操作。这种I/O多路复用的方式相比上面的阻塞 I/O模型,提供了更好的资源管理:

    • 使用较少的线程便可以处理很多连接,因此也减少了内存管理和上下文切换所带来的开销
    • 当没有I/O操作需要处理的时候,线程也可以被用于其他任务。

    尽管使用 Java NIO可以让我们使用较少的线程处理很多连接,但是在高负载下可靠和高效地处理和调度I/O操作是一项繁琐而且容易出错的任务,所以才引出了高性能网络编程专家——Netty

    Netty特性

    Netty有很多优秀的特性值得让我们去使用它(摘自《Netty实战》):

    设计

    • 统一的API,适用于不同的协议(阻塞和非阻塞)
    • 基于灵活、可扩展的事件驱动模型
    • 高度可定制的线程模型
    • 可靠的无连接数据Socket支持(UDP)

    性能

    • 更好的吞吐量,低延迟
    • 更低的资源消耗
    • 最少的内存复制

    健壮性

    • 不再因过快、过慢或超负载连接导致OutOfMemoryError
    • 不再有在高速网络环境下NIO读写频率不一致的问题

    安全性

    • 完整的SSL/TLS和STARTTLS的支持
    • 可用于受限环境下,如 Applet 和OSGI

    易用

    • 详实的Javadoc和大量的示例集
    • 不需要超过 JDK 1.6+的依赖

    Netty示例代码

    下面是server 和client的示例代码,先来看看Netty代码是怎么样的,后续文章会详细分析各个模块。

    Server代码

    public class EchoServer {
        private final int port;
    
        public EchoServer(int port) {
            this.port = port;
        }
    
        public static void main(String[] args) throws InterruptedException {
            new EchoServer(8888).start();
        }
    
        public void start() throws InterruptedException {
            final EchoServerHandler serverHandler = new EchoServerHandler();
            //创建EventLoopGroup,处理事件
            EventLoopGroup boss = new NioEventLoopGroup();
            EventLoopGroup worker = new NioEventLoopGroup();
            try {
                ServerBootstrap b = new ServerBootstrap();
                b.group(boss,worker)
                        //指定所使用的NIO传输 Channel
                        .channel(NioServerSocketChannel.class)
                        //使用指定的端口设置套接字地址
                        .localAddress(new InetSocketAddress(port))
                        //添加一个EchoServerHandler到子Channel的ChannelPipeline
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel socketChannel) throws Exception {
                                //EchoServerHandler标志为@Shareable,所以我们可以总是使用同样的实例
                                socketChannel.pipeline().addLast(serverHandler);
                            }
                        });
                //异步的绑定服务器,调用sync()方法阻塞等待直到绑定完成
                ChannelFuture future = b.bind().sync();
                future.channel().closeFuture().sync();
            } finally {
                //关闭EventLoopGroup,释放所有的资源
                group.shutdownGracefully().sync();
                worker.shutdownGracefully().sync();
            }
        }
    }
    复制代码

    EchoServerHandler

    @ChannelHandler.Sharable //标识一个 ChannelHandler可以被多个Channel安全地共享
    public class EchoServerHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf buffer = (ByteBuf) msg;
            //将消息记录到控制台
            System.out.println("Server received: " + buffer.toString(CharsetUtil.UTF_8));
            //将接受到消息回写给发送者
            ctx.write(buffer);
        }
    
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            //将未消息冲刷到远程节点,并且关闭该 Channel
            ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                    .addListener(ChannelFutureListener.CLOSE);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            //打印异常栈跟踪
            cause.printStackTrace();
            //关闭该Channel
            ctx.close();
        }
    }
    复制代码

    代码要点解读:

    • ServerBootStrap是引导类,帮助服务启动的辅助类,可以设置 Socket参数
    • EventLoopGroup是处理I/O操作的线程池,用来分配 服务于Channel的I/O和事件的 EventLoop,而NioEventLoopGroupEventLoopGroup的一个实现类。这里实例化了两个 NioEventLoopGroup,一个 boss,主要用于处理客户端连接,一个 worker用于处理客户端的数据读写工作
    • EchoServerHandler实现了业务逻辑
    • 通过调用ServerBootStrap.bind()方法以绑定服务器

    Client 代码

    public class EchoClient {
        private final String host;
        private final int port;
    
    
        public EchoClient(String host, int port) {
            this.host = host;
            this.port = port;
        }
    
        public void start() throws InterruptedException {
            EventLoopGroup group = new NioEventLoopGroup();
            try {
                Bootstrap b = new Bootstrap();
                b.group(group)
                        .channel(NioSocketChannel.class)
                        .remoteAddress(new InetSocketAddress(host, port))
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel socketChannel) throws Exception {
                                socketChannel.pipeline().addLast(new EchoClientHandler());
                            }
                        });
                ChannelFuture channelFuture = b.connect().sync();
                channelFuture.channel().closeFuture().sync();
            } finally {
                group.shutdownGracefully().sync();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            new EchoClient("127.0.0.1", 8888).start();
        }
    }
    复制代码

    EchoClientHandler

    @ChannelHandler.Sharable
    public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    
        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
            System.out.println("Client received: "+byteBuf.toString());
        }
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks",CharsetUtil.UTF_8));
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }
    复制代码

    代码要点解读:

    • 为初始化客户端,创建了一个BootStrap实例,与ServerBootStrap一样,也是一个引导类,主要辅助客户端
    • 分配了一个 NioEventLoopGroup实例,里面的 EventLoop,处理连接的生命周期中所发生的事件
    • EchoClientHandler类负责处理业务逻辑,与服务端的EchoSeverHandler作用相似。

    参考资料 & 鸣谢


    作者:pjmike_pj
    链接:https://juejin.im/post/5ba35a76f265da0a951ed4db

  • 相关阅读:
    python实现二分查找算法例子代码
    Python实现计算圆周率π的值到任意位的方法示例
    什么是TWS耳机
    [置顶] 单例模式lua实现
    推荐五星级C语言学习网站
    poj2689 Prime Distance 有难度 埃拉托斯尼斯筛法的运用
    局域网
    pager-taglib分页处理的使用
    创建ListView的基本步骤
    求最大连续子数列和(只扫描一次数列)
  • 原文地址:https://www.cnblogs.com/kkdn/p/9685786.html
Copyright © 2011-2022 走看看