BIO,NIO,AIO详解

IO模型

IO模型就是说用什么样的通道进行数据的发送和接收,Java共支持3种网络编程IO模式:BIO,NIO,AIO

BIO

特点
同步阻塞模型,一个客户端连接对应一个处理线程
缺点
1、IO代码里read操作是阻塞操作,如果连接不做数据读写操作会导致线程阻塞,浪费资源
2、如果线程很多,会导致服务器线程太多,压力太大。
应用场景
BIO 方式适用于连接数目比较小且固定的架构, 这种方式对服务器资源要求比较高, 但程序简单易理解。

public class SocketServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9000);
        while (true) {
            System.out.println("等待连接。。");
            final Socket socket = serverSocket.accept();
            //阻塞方法
            System.out.println("有客户端连接了。。");
//            new Thread(new Runnable() {
//                @Override
//                public void run() {
//                    try {
//                        handler(socket);
//                    } catch (IOException e) {
//                        e.printStackTrace();
//                    }
//                }
//            }).start();
            handler(socket);
        }
    }

    private static void handler(Socket socket) throws IOException {
        System.out.println("thread id = " + Thread.currentThread().getId());
        byte[] bytes = new byte[1024];
        System.out.println("准备read。。");
        //接收客户端的数据,阻塞方法,没有数据可读时就阻塞
        int read = socket.getInputStream().read(bytes);
        System.out.println("read完毕。。");
        if (read != -1) {
            System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));
            System.out.println("thread id = " + Thread.currentThread().getId());
        }
        socket.getOutputStream().write("HelloClient".getBytes());
        socket.getOutputStream().flush();
    }
}

以上代码是bio的服务端代码,我们看到他有一个accept()方法用于接收客户端连接,如果没有连接会一直阻塞到这里,有链接之后进入handle方法,然后准备接收客户端发送的数据,但是如果一直没有数据发过来的话就会阻塞到这里,即使我们在数据处理中使用线程池也是不行的,如果一直有资源卡到这里最终只会导致资源耗尽。

NIO

特点
同步非阻塞,服务器实现模式为一个线程可以处理多个请求(连接),客户端发送的连接请求都会注册到多路复用器selector上,多路复用器 轮询到连接有IO请求就进行处理。
应用场景
NIO 方式适用于连接数目比较多且连接比较短的架构, 比如聊天服务器,弹幕系统等。
NIO有三大组件:Channel(通道)、Buffer(缓冲区)、Selector(复用器)
1.channel就相当于是流,每个channel对应一个缓冲区buffer;
2.channel还会注册到selector上根据channel的读写事件的发生将其交给某个空闲的线程处理;
3.selector可以对应一个或多个线程,在业务处理的时候可以选择使用线程池;

//服务端代码
public class NIOServer {
    public static void main(String[] args) throws IOException {
        // 创建一个在本地端口进行监听的服务Socket通道.并设置为非阻塞方式
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //必须配置为非阻塞才能往selector上注册,否则会报错,selector模式本身就是非阻塞模式
        ssc.configureBlocking(false);
        ssc.socket().bind(new InetSocketAddress(9000));
        // 创建一个选择器并将serverSocketChannel注册到它上面
        Selector selector = Selector.open();
        // 把channel注册到selector上,并且selector对客户端accept连接操作感兴趣
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            System.out.println("等待事件发生。。");
            // 轮询监听key,select是阻塞的,accept()也是阻塞的
            selector.select();
            System.out.println("有事件发生了。。");
            // 有客户端请求,被轮询监听到
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                //删除本次已处理的key,防止下次select重复处理
                it.remove();
                handle(key);
            }
        }
    }

    private static void handle(SelectionKey key) throws IOException {
        if (key.isAcceptable()) {
            System.out.println("有客户端连接事件发生了。。");
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            //此处accept方法是阻塞的,但是这里因为是发生了连接事件,所以这个方法会马上执行完
            SocketChannel sc = ssc.accept();
            sc.configureBlocking(false);
            //通过Selector监听Channel时对读事件感兴趣
            sc.register(key.selector(), SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            System.out.println("有客户端数据可读事件发生了。。");
            SocketChannel sc = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            buffer.clear();
            int len = sc.read(buffer);
            if (len != -1) {
                System.out.println("读取到客户端发送的数据:" + new String(buffer.array(), 0, len));
            }
            ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
            sc.write(bufferToWrite);
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
            sc.close();
        }
    }
}

1.创建一个ServerSocketChannel和Selector,设置ServerSocketChannel为非阻塞;
2.将ServerSocketChannel注册到Selector上,监听连接事件;
3.Selector通过select()方法监听channel的连接事件,当客户端连接的时候,selector监听到连接事件,会获取到连接中的SelectionKey,select()方法相当于BIO中的accept方法,也是阻塞的。
4.当有事件发生的时候会轮询SelectionKey(一般用epoll事件通知方式),接收到通知之后,判断是哪种事件;
5.如果是连接事件,SelectionKey通过channel方法获取到连接的ServerSocketChannel,ServerSocketChannel通过accept方法获取到SocketChannel;
6.把SocketChannel注册到Selector上,监听读取事件;
7.当发生读取事件时,根据key获取SocketChannel;
8.将SocketChannel数据读取出来。
9.使用ByteBuffer写到客户端。

AIO

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

//服务端代码
public class AIOServer {
    public static void main(String[] args) throws Exception {
        final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open()
                .bind(new InetSocketAddress(9000));
        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(final AsynchronousSocketChannel socketChannel, Object attachment) {
                try {
                    // 再此接收客户端连接,如果不写这行代码后面的客户端连接连不上服务端
                    serverChannel.accept(attachment, this);
                    System.out.println(socketChannel.getRemoteAddress());
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer buffer) {
                            buffer.flip();
                            System.out.println(new String(buffer.array(), 0, result));
                            socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes()));
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer buffer) {
                            exc.printStackTrace();
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            @Override
            public void failed(Throwable exc, Object attachment) {
                exc.printStackTrace();
            }
        });
        Thread.sleep(Integer.MAX_VALUE);
    }
}

我们可以看出accept方法时,方法是阻塞的,然后再使用回调函数进行监听,这样的话就是异步操作,可以同时进行接收事件和读写事件。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值