NIO和Netty笔记和总结

最近因为编写接口,需要使用到netty,但是我对于niio和netty一直难以入门,这次准备采用边看边记录的方法。大致计划从nio基础和通信流程过度到netty的应用。


NIO三大基础概念

缓冲区buffer

buffer是一个对象,它包含一些要写入或者要读出的对象。在NIO库中,读写数据都是用缓冲区处理的,访问NIO数据,均是通过缓冲区进行。缓冲区不仅仅是一个数组,还包括对数据的结构化访问及维护读写位置等信息。一个Buffer对象是固定数量的数据的容器。其作用是一个存储器,或者分段运输区,在这里数据可被存储并在之后用于检索。缓冲区可以写满和释放。对于每个非布尔原始数据类型都有一个缓冲区类。尽管缓冲区作用于它们存储的原始数据类型,但缓冲区十分倾向于处理字节
缓冲区的工作与通道紧密联系。通道是 I/O 传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。

参考文章https://blog.csdn.net/qq_38526573/article/details/89176469

通道channel

网络数据通过通道读取和写入,通道可以用于读、写或者二者同时进行,是全双工的,Channel相比IO中的Stream更加高效,可以异步双向传输,但是必须和buffer一起使用。通道的类图如下,前三层是定义功能的接口,后面是具体的功能类,分层用于网络读写的selectTable和用于文件操作的FileChannel

netty使用的SeverSocketChannel和SocketChannel都是网络读写selectTable的子类

参考文章https://blog.csdn.net/l18637220680/article/details/79360451

多路复用器Selector

多路复用器Selector是Java NIO编程的基础,熟练地掌握Selector对于掌握NIO编程至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲,Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。

  一个多路复用器Selector可以同时轮询多个Channel,由于JDK使用了epoll代替传统的select实现,所以它并没有最大连接句柄1024/2048的限制。这也就意味着只需要一个线程负责Selector的轮询,就可以介入成千上万的客户端。

Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

参考文章https://blog.csdn.net/duanduan_l/article/details/88577836


NIO序列图

NIO服务端通信序列图

NIO客户端通信序列图


Netty服务端客户端开发

netty服务端

首先创建两个NioEventLoopGroup实例,其中包含一组nio线程,专门用于网络事件的处理。实际上就是Reactor线程组,其中一个用于服务端接受客户端的连接,另一个用于进行SocketChannel的网络读写。之后创建ServerBootstrap对象,他是Netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度。通过调用serverBoostarp的group方法,将两个NIO线程组当作入参传递到ServerBoostrap中。接着设置创建的Channel为NioServerSocketChannel,然后配置NioServerSocketChannel的TCP参数,此处将他的backlog设置为1024,最后绑定事件I/O事件的处理类ChildChannelHandler,类似于Reactor模式的Handler类。主要用于处理网络I/O事件,例如记录日志,对消息进行编码等。

服务端启动辅助类配置完成之后,调用它的bind方法绑定监听端口,随后,调用它的同步阻塞方法sync等待绑定操作完成。完成后Netty回返回一个ChannelFuture,主要用于异步操作的通知回调。

之后调用NIO线程的shutdownGracefully进行优雅退出,它会释放跟shutdownCracefully相关联的资源。

public class JsonServer {
    /**
     * LOGGER
     */
    private static final Logger Logger = LoggerFactory.getLogger(JsonServer.class);
    private final int serverPort;

    ServerBootstrap b = new ServerBootstrap();

    /**
     * 构造函数
     * @param port
     */
    public JsonServer(int port) {
        this.serverPort = port;
    }

    public void runServer(){
        //创建reactor线程组
        EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);
        EventLoopGroup workderLoopGroup = new NioEventLoopGroup(); //使用默认cpu个数*2 的线程个数

        try {
            //设置reacotor线程组
            b.group(bossLoopGroup, workderLoopGroup);
            //设置Nio类型的channel
            b.channel(NioServerSocketChannel.class); //服务器端的serverSocket
            //设置监听端口
            b.localAddress(serverPort); //设置监听本地地址,并且传入端口
            //设置通道的参数
            b.option(ChannelOption.SO_KEEPALIVE, true);
            b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

            //装配子通道流水线
            b.childHandler(new ChannelInitializer<SocketChannel>() {
                //有连接到达时会创建一个channel
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    //pipeline管离子通道channel中的handler
                    //向子channel流水线添加3个handler处理器
                    ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));
                    ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
                    ch.pipeline().addLast(new JsonMsgDecoder());
                }
            });

            //开始绑定server
            //通过sync同步方法阻塞直到绑定成功
            ChannelFuture channelFuture = b.bind().sync();
            Logger.info("服务器启动成功,监听端口: " + channelFuture.channel().localAddress());

            //等待通道关闭的异步任务结束
            //服务器监听通道一致等待通道关闭的异步任务结束
            ChannelFuture closeFuture = channelFuture.channel().closeFuture();
            closeFuture.sync();

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //优雅关闭eventLoopGroup 线程
            workderLoopGroup.shutdownGracefully();
            bossLoopGroup.shutdownGracefully();
        }
    }


    //服务器端业务处理器
    static class JsonMsgDecoder extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            String json = (String) msg;
            JsonMsg jsonMsg = JsonMsg.parseFromJson(json);
            Logger.info("收到一个 Json 数据包 =》" + jsonMsg);
        }
    }

    /**
     * 程序执行入口
     * @param args
     */
    public static void main(String[] args) {
        int port = 8888;
        new JsonServer(port).runServer();
    }

}

netty客户端

首先创建一组客户端处理I/O读写的NioEventLoopGroup线程组。之后创建NIO客户端的辅助启动类ServerBootstrap,随后进行配置。跟服务端不同的是,它的channel需要设置为NioSocketChannel,然后为其添加Handler。此处为了简单直接创建匿名内部类,实现initCHannel方法,其作用是当创建NioSocketChannel成功之后,在进行初始化时,将它的ChannelHandler设置到ChannelPipeline,用于处理网络I/O事件。

客户端启动辅助类配置完成之后,调用connect方法发起异步连接,然后调用同步方法等待连接成功。

最后,当客户端连接关闭了之后,客户端主函数退出,退出之前释放NIO线程组的资源。

public class JsonSendClient {

    /**
     * LOGGER
     */
    private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(JsonServer.class);
    static String content = "!";

    private int serverPort; //端口号
    private String serverIp; //服务器端ip地址


    Bootstrap b = new Bootstrap();

    /**
     * 构造函数
     * @param serverIp
     * @param serverPort
     */
    public JsonSendClient( String serverIp, int serverPort) {
        this.serverPort = serverPort;
        this.serverIp = serverIp;
    }


    public void runClient() {
        //创建reactor 线程组
        EventLoopGroup workerLoopGroup = new NioEventLoopGroup();

        try {
            //1 设置reactor 线程组
            b.group(workerLoopGroup);
            //2 设置nio类型的channel
            b.channel(NioSocketChannel.class);
            //3 设置监听端口
            b.remoteAddress(serverIp, serverPort);
            //4 设置通道的参数
            b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

            //5 装配通道流水线
            b.handler(new ChannelInitializer<SocketChannel>() {
                //初始化客户端channel
                protected void initChannel(SocketChannel ch) throws Exception {
                    // 客户端channel流水线添加2个handler处理器
                    ch.pipeline().addLast(new LengthFieldPrepender(4));
                    ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
                }
            });
            ChannelFuture f = b.connect();
            f.addListener((ChannelFuture futureListener) ->
            {
                if (futureListener.isSuccess()) {
                    Logger.info("EchoClient客户端连接成功!");
                } else {
                    Logger.info("EchoClient客户端连接失败!");
                }
            });

            // 阻塞,直到连接完成
            f.sync();
            Channel channel = f.channel();

            //发送 Json 字符串对象
            for (int i = 0; i < 1000; i++) {
                JsonMsg user = build(i, i + "->" + content);
                channel.writeAndFlush(user.convertToJson());
                Logger.info("发送报文:" + user.convertToJson());
            }
            channel.flush();


            // 7 等待通道关闭的异步任务结束
            // 服务监听通道会一直等待通道关闭的异步任务结束
            ChannelFuture closeFuture = channel.closeFuture();
            closeFuture.sync();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 优雅关闭EventLoopGroup,
            // 释放掉所有资源包括创建的线程
            workerLoopGroup.shutdownGracefully();
        }

    }


    public JsonMsg build(int id, String content) {
        JsonMsg user = new JsonMsg();
        user.setId(id);
        user.setContent(content);
        return user;
    }

    /**
     * 客户端程序入口
     * @param args
     */
    public static void main(String[] args) {
        int port = 8888;
        String ip = "127.0.0.1";
        new JsonSendClient(ip, port).runClient(); //创建对象,并执行客户端程序
    }
}

其他netty相关的就慢慢补充吧

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值