BIO/NIO/AIO

Netty学习

1、 BIO/NIO/AIO

2、netty组件解析

3、Netty编解码&粘包拆包&心跳检测与重连&零拷贝

      Netty心跳检测代码实例

BIO:blocking io,阻塞io。阻塞节点有以下两点:

模式一、单线程bio

1、serverSocket.accept()时,一直等待客户端的连接请求;

2、socket.getInputStream().read(bytes)一直等待读取客户端的消息。

可使用telnet localhost:8099 模拟客户端的连接请求,使阻塞节点一继续往下;使用telnet.send命令模拟客户端发起命令。

该模式是最早期时,用户量很少,互联网不是很普及的时候使用的处理方式,不用考虑并发情况。

public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8099);
            while (true){
                logger.info("等待客户端连接。。。");
                Socket socket = serverSocket.accept();
                logger.info("客户端已连接。。。。");
                handle(socket);
            }
        } catch (IOException e) {
            logger.error("服务端异常。。。");
        }
}

private static void handle(Socket socket){
        byte[] bytes = new byte[1024];
        try {
            logger.info("等待客户端发送消息。。。");
            int read = socket.getInputStream().read(bytes);
            if (read != -1){
                logger.info(String.format("客户端发送消息:[%s]", new String(bytes, 0, read)));
            }
            socket.getOutputStream().write("hello client".getBytes());
            socket.getOutputStream().flush();
        } catch (IOException e) {
            logger.error("服务端处理消息异常。。。");
        }
}

模式二、多线程bio

1、serverSocket.accept()时,一直等待客户端的连接请求;

2、当多个客户端连接时,会使用子线程单独处理各个客户端的数据处理。

但同时存在以下问题:

1、线程上下文切换比较耗时且耗资源,影响性能;

2、当客户端只连接,没有发送消息,则会一直占用该线程,造成线程浪费。

比如C10k问题:服务器如何支持10k个并发连接,也就是concurrent 10000 connection

public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8099);
            while (true){
                logger.info("等待客户端连接。。。");
                Socket socket = serverSocket.accept();
                logger.info("客户端已连接。。。。");
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        logger.info(String.format("current thread:[%s]", Thread.currentThread().getName()));
                        handle(socket);
                    }
                });
            }
        } catch (IOException e) {
            logger.error("服务端异常。。。");
        }
}

 模式三、NIO(non blocking io)

采用Selector多路复用器,服务器采用一个线程可以处理多个请求,客户端发送的请求都会注册到多路复用器selector上,多路复用器轮询到连接有IO请求就会处理。

I/O多路复用底层使用到linux api(select,poll,epoll)来实现,区别如下:

NIO从1.4开始支持,流程基本如下:

NIO主要有三大核心:channel(通道),Buffer(缓存区),Selector(选择器)

1、channel类似于流,每个channel对应一个buffer缓冲区,buffer底层就是一个byte数组;

2、channel会注册到selector上,由seletor根据channel的读写事件的发生将其交由一个空闲线程处理;

3、selector可以对应一个或多个线程;4、NIO的buffer都是既可以读又可以写。

public static void main(String[] args) {
        try {
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false);
            ssc.socket().bind(new InetSocketAddress(8099));
            logger.info("服务端启动。。。");
            Selector selector = Selector.open();
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            while (true){
                logger.info("等待事件发生。。。");
                int select = selector.select();
                logger.info("有事件发生了。。。");
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
                    SelectionKey sk = iterator.next();
                    iterator.remove();
                    handle(sk);
                }

            }
        } catch (IOException e) {
            logger.info("有事件发生了。。。");
        }
    }
private static void handle(SelectionKey selectionKey){
        if (selectionKey.isAcceptable()){
            try {
                ServerSocketChannel ssc = (ServerSocketChannel)selectionKey.channel();
                SocketChannel sc = ssc.accept();
                sc.configureBlocking(false);
                sc.register(selectionKey.selector(), SelectionKey.OP_READ);
            } catch (IOException e) {
                logger.info("客户端连接异常。。。");
            }
        }
        if (selectionKey.isReadable()){
            SocketChannel sc = (SocketChannel)selectionKey.channel();
            ByteBuffer bb = ByteBuffer.allocate(1024);
            try {
                int read = sc.read(bb);
                logger.info(String.format("读取客户端数据:[%s]", new String(bb.array(), 0, read)));
                ByteBuffer bbWrite = ByteBuffer.wrap("hello client".getBytes());
                sc.write(bbWrite);
                selectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (selectionKey.isWritable()){
            SocketChannel sc = (SocketChannel)selectionKey.channel();
            logger.info("write事件");
            selectionKey.interestOps(SelectionKey.OP_READ);
        }
    }

1、创建一个ServerSocketChannel和Selector,将ServerSocketChannel注册到Selector上;

2、Selector通过select()方法监听channel事件,当客户端连接时,Selector监听到连接事件,获取到ServerSocketChannel注册时绑定的selectionKey;

3、selectionKey通过channel()方法可以获取绑定的ServerSocketChannel;

4、ServerSocketChannel通过accept()方法得到SocketChannel;

5、将SocketChannel注册到Selector上,关心read事件;

6、注册后返回一个selectionKey,会和该SocketChannel关联;

7、Selector继续通过select()方法监听事件,当客户端发送数据给服务端,Selector监听到read事件,获取到SocketChannel注册时绑定的selectionKey;

8、selectionKey通过channel()方法客户获取绑定的SocketChannel;

9、将SocketChannel里的数据读取出来;

10、用SocketChannel将服务端的数据写回客户端。

总结:NIO模型的selector 就像一个大总管,负责监听各种IO事件,然后转交给后端线程去处理

NIO相对于BIO非阻塞的体现就在,BIO的后端线程需要阻塞等待客户端写数据(比如read方法),如果客户端不写数据线程就要阻塞。 NIO把等待客户端操作的事情交给了大总管 selector,selector 负责轮询所有已注册的客户端,发现有事件发生了才转交给后端线程处理,后端线程不需要做任何阻塞等待,直接处理客户端事件的数据即可,处理完马上结束,或返回线程池供其他客户端事件继续使用。channel 的读写是非阻塞的。
 
Redis就是典型的NIO线程模型 ,selector收集所有连接的事件并且转交给后端线程,线程连续执行所有事件命令并将结果写回客户端。

使用man 函数名,查看linux中该方法的作用。

当s收到c的请求到达系统网卡时,操作系统内核会硬中断。
 elector.open JNI调用epoll.create方法创建一个epoll实例,是一个空的就绪事件列表rdlist,当client有发起连接、读写等事件响应时,操作系统内核能感知到,中断程序,将socketchannel事件放到rdlist中,调用epoll.wait时,只是查看rdlist中有没有数据,有数据就跳出阻塞,没数据就阻塞上。

selector有两个集合,一个时所有socketchannel集合,一个是wait做真正的绑定,有响应就放到rdlist中。

最早1.4版本时。selector遍历,没有用到内核中断回调函数,所以最大连接有1024的限制,1.5使用epoll,使用内核的中断、回调函数,放到epoll描述文件中,epoll_ctl添加绑定事件到epfd中。redis线程模型实现主要在。_epoll.c  _selector.c文件中。

Version:0.9 StartHTML:0000000105 EndHTML:0000000965 StartFragment:0000000141 EndFragment:0000000925

AIO(NIO 2.0)
异步非阻塞, 由操作系统完成后回调通知服务端程序启动线程去处理, 一般适用于连接数较多且连接时间较长的应用
应用场景:
AIO方式适用于连接数目多且连接比较长(重操作) 的架构,JDK7 开始支持

主要用到了AsynchronousServerSocketChannel,目前因操作系统无法友好的支持,所以未普及。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值