netty学习02-javaIO进化过程和模式

1. BIO 同步阻塞IO

1.1 BIO服务器端示例代码

public class JavaBioServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(7777);
        System.out.println("服务端启动...");
        while (true) {
            // 获取socket套接字
            // accept()阻塞点
            final Socket socket = serverSocket.accept();
            System.out.println("有新客户端连接上来了...");
            //一个客户端请求对应一个处理线程
            new Thread(new Runnable() {

                public void run() {
                    try {
                        // 获取客户端输入流
                        InputStream is = socket.getInputStream();
                        byte[] b = new byte[1024];
                        while (true) {
                            // 循环读取数据
                            // read() 阻塞点
                            int data = is.read(b);
                            if (data != -1) {
                                String info = new String(b, 0, data, "UTF-8");
                                System.out.println(info);
                            } else {
                                break;
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

1.2 BIO客户端示例代码

public class JavaBioClient {

    public static void main(String[] args) throws Exception{
        java.net.Socket s = new java.net.Socket("localhost", 7777);
        java.io.OutputStream out = s.getOutputStream();

        String msg = "hello server bio";
        // 阻塞,写完成
        out.write(msg.getBytes("UTF-8"));
        s.close();
    }
}

1.3 BIO模式的缺点:

  • 同步阻塞:accept、read、write操作都是阻塞方法

2. NIO 同步非阻塞

2.1 NIO基础知识

    NIO由如下几部分组成:

  • buffer
  • channel
  • selector

2.1.1 buffer

buffer的类图关系如上图所示,在buffer的实现类中,如ByteBufer的子类中,分为堆内存缓冲和直接内存缓冲两类实现。至于对buffer的操作api,这里暂不做说明。

2.1.2 channel

常见的channel如下图所示:

2.1.3 selector

在NIO中,一个selector可以管理多个channel的IO事件,并在IO事件触发时,得到通知。

2.2 示例代码

/**
* 服务端代码
*/
public class JavaNioServer {
    public static void main(String[] args) throws Exception {
        // 1. 创建Selector对象
        Selector selector = Selector.open();

        // 2. 向Selector对象绑定通道
        // a. 创建可选择通道,并配置为非阻塞模式
        ServerSocketChannel server = ServerSocketChannel.open();
        server.configureBlocking(false);

        // b. 绑定通道到指定端口
        ServerSocket socket = server.socket();
        socket.bind(new InetSocketAddress(7777));

        // c. 向Selector中注册感兴趣的事件
        server.register(selector, SelectionKey.OP_ACCEPT);


        // 3. 处理事件
        while (true) {
            // 该调用会阻塞,直到至少有一个事件就绪、准备发生
            selector.select();
            // 一旦上述方法返回,线程就可以处理这些事件
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iter = keys.iterator();
            while (iter.hasNext()) {
                SelectionKey key = (SelectionKey) iter.next();
                //切记一定要remove
                iter.remove();
                //处理读写和具体业务逻辑,可以是在当前线程中处理,也可以在子线程中处理
                if (key.isValid()) {
                    // 处理新接入的请求消息
                    if (key.isAcceptable()) {
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        // 接收客户端的连接,并创建一个SocketChannel
                        SocketChannel sc = ssc.accept();
                        sc.configureBlocking(false);
                        // 将SocketChannel和感兴趣事件注册到selector
                        sc.register(selector, SelectionKey.OP_READ);
                    }else if (key.isReadable()) {
                        // 读数据的处理
                    }else if(key.isWritable()){
                        //写数据处理
                    }
                }
            }
        }
    }
}

2.3 selector和channel关系

    在一个程序中(可以是客户端,也可以是服务器端),可以有多个selector(一般只有一个),有多个channel,每个channel需要向某个selector注册,并说明感兴趣的操作,注册操作返回一个selectionKey。即selectionKey是channel和selector之间的桥梁。

    一个selector内部维护了3个selectkey集合,已注册的键集合,敢兴趣事件触发的计划,已经取消的集合。

    一个selectkey,可以知道该key对应的selector和channel,该key感兴趣的操作,那些感兴趣的操作已经准备就绪了,以及一个附加对象。

2.4 优缺点

优点:同步非阻塞。可以用一个线程监控N个通道进行网络通信。

缺点:编程难道较大,如果以原生的NIO进行网络应用开发,需要进行复杂二次封装,并监控封装后框架的稳定性。

3. netty框架 异步非阻塞

netty是一个异步的事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络服务器和客户端程序。netty是在javaNIO基础上,进行了各种封装。

3.1 示例代码

/**
* 服务端
*/
public class MyServer2 {

    public static void main(String[] args) {
        //负责接收客户端的连接请求
        EventLoopGroup boosGroup = new NioEventLoopGroup(1);
        //负责接收客户端读写请求
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(boosGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler())
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            //解码器
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                            //编码器
                            pipeline.addLast(new LengthFieldPrepender(4));
                            pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
                            pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
                            pipeline.addLast(new MyServerHandler());
//                            pipeline.addLast(new MyServerHandler1());
//                            pipeline.addLast(new MyServerHandler2());
                        }
                    });

            ChannelFuture channelFuture = bootstrap.bind(9872).sync();
            System.out.println("系统启动成功!!!");
            channelFuture.channel().closeFuture().sync();
            System.out.println("系统执行完成!!!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            boosGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

/**
* 客户端
*/
public class MyClient2 {

    public static void main(String[] args) {
        EventLoopGroup group=new NioEventLoopGroup();
        try {
            Bootstrap boot = new Bootstrap();
            boot.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline=ch.pipeline();
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
                            pipeline.addLast(new LengthFieldPrepender(4));
                            pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
                            pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
                            pipeline.addLast(new MyClientHandler());
                        }
                    });

            ChannelFuture channelFuture = boot.connect("localhost", 9872).sync();
            channelFuture.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            group.shutdownGracefully();
        }
    }
}

上述代码都是模块代码,唯一需要开发的是Handler,如MyServerHandler。pipeline可以理解成一个拦截器链,handler可以理解成一个具体的拦截器(这种说法不太准确)。

3.2 netty结构说明

 

在ServerBootstrap类中,包含了2个事件循环组(EventLoopGroup),事件循环组可以理解成线程池,bossGroup这个事件循环组主要负责接收客户端的连接请求,workerGroup这个事件循环组主要负责读写IO请求。默认2个事件循环组里的EventLoop为CUP核数的2倍。

在事件循环组中,包含一个EventExecutor数组,用来保存N个EventLoop,EventLoop可以理解成是一个线程。同时还包含一个选择器,选择器作用是从EventLoop数组中选择一个EventLoop进行服务。

一个EventLoop包含一个selector,并且将负责的通道注册到这个selector上,当select方法返回时,创建一个pipeline,并在pipeline上添加相应的处理器,并根据pipeline上处理器,依次执行相关逻辑。

一个pipeline可以理解为一个拦截器链,链中包含一个headContext、一个TailContext以及多个业务相关的HandlerContext,一个HandlerContext对应一个Handler。

假设服务器为16核,那么默认一个事件循环组中应该包括32个事件循环,而一个事件循环中有一个selector,假设一个selector每秒可以同时管理1000个客户端连接事件,那么整个系统的并发为:1000*32=32000。

4. 网络模式 reactor模式

4.1 单线程模式

    缺点:

  • 服务端串行处理,且责任过重(需要建立连接、处理读写和具体业务逻辑)

4.2 多线程模式

    相比单线程模式的优点:

  • 服务端能并行处理,即服务端在主线程中和客户端进行连接的建立,子线程中进行读写和业务逻辑的处理。

    缺点:

  • 服务器子线程数量不可控,线程过多时会导致大量的线程上下文切换

4.3 线程池模式

    相比多线程模式的优点:

  • 服务端能并行处理,且能控制后台线程数量

    缺点:

  • 后台线程如果处理业务逻辑时间过长,则当线程池没有空闲线程时,将会阻塞服务端的主线程

4.4 IO与业务逻辑分离模式

将服务器端分为3个部分,用一个线程来完成连接的建立,用一个线程池来实现具体channel的读写,用一个线程池来实现具体业务逻辑处理。

注意:如果具体业务逻辑简单,执行时间较短,则可以直接用IO线程来执行。如果有耗时操作(如数据存库),最好将耗时操作用单独的线程执行。

优点:IO读写操作和具体业务逻辑处理分开,理论上能支持更大的并发读写操作。

缺点:需要根据并发情况调整IO线程池大小,需要根据业务逻辑的执行性能和并发情况调整业务线程池大小。

4.5 netty 网络模式

netty服务器端,有2个EventLoopGroup(boosGroup和workerGroup),职责分别为为客户端建立连接、处理IO读写,即实现了线程池模式,可根据具体业务逻辑的耗时,实现IO与业务逻辑分离模式。以前的代码的网络模式都属于线程池模式,以下代码为实现IO与业务逻辑分离模式。

public class MyServer2 {

    public static void main(String[] args) {
        //负责接收客户端的连接请求
        EventLoopGroup boosGroup = new NioEventLoopGroup(1);
        //负责接收客户端读写请求
        EventLoopGroup workerGroup = new NioEventLoopGroup(1);

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(boosGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler())
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();

                            //解码器
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                            //编码器
                            pipeline.addLast(new LengthFieldPrepender(4));
                            pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
                            pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
                            pipeline.addLast(new MyServerHandler3());
                        }
                    });

            ChannelFuture channelFuture = bootstrap.bind(9872).sync();
            System.out.println("系统启动成功!!!");
            channelFuture.channel().closeFuture().sync();
            System.out.println("系统执行完成!!!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            boosGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

public class MyServerHandler3 extends ChannelInboundHandlerAdapter {

    private static ThreadPoolExecutor pool=new ThreadPoolExecutor(5,10,60,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(1024));
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("服务端收到的消息为:"+msg);
        //将读取到的客户端数据 提交给线程池进行处理
        pool.execute(new MyThread(ctx,msg));
    }

    private static class MyThread implements Runnable{

        private ChannelHandlerContext ctx;
        private Object msg;
        public MyThread(ChannelHandlerContext ctx, Object msg){
            this.ctx=ctx;
            this.msg=msg;
        }
        public void run() {
            //执行具体的耗时业务逻辑
            this.ctx.writeAndFlush("执行结果:");
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值