网络编程,BIO到NIO,从Java代码角度理解

BIO Blocking IO

在Java中 java.io 包下就是同步阻塞式IO,代表有InputStream OutputStream Reader Writer等,核心思想是面向流编程。

NIO Non Blocking IO

java.nio 包下的新IO模型,为了解决传统IO带来的同步阻塞低效问题。

但是今天不讲文件流的IO,今天面向的是网络IO,从这个角度理解下BIO到NIO的发展,以及操作系统底层epoll技术在Java的实现。

public static void main(String[] args) throws IOException {

    ServerSocket bioServer = new ServerSocket(0);

    while (true) {

        // 当有新的TCP连接请求,封装成Socket,Socket即是一个通信的双向流,从客户端读或写数据到客户端

        Socket socket = bioServer.accept();

        InputStream inputStream = socket.getInputStream();

        byte[] buffer = new byte[2048];

        int i ;

        // 关键点在这里,服务器接收到一个socket连接后代表有客户端连接请求要与服务器通信,TCP连接

        // read()函数会阻塞读取客户端的数据,但是客户端什么时候发送数据时未知的

        // 当有很多连接请求时怎么办?10000+个并发连接请求,主线程此时就阻塞在这里等待其中一个客户端的数据吗?

        while ((i = inputStream.read(buffer)) != -1) {

            // do IO operation

            // read or write

        }

    }

}

从上面的场景可以理解为什么传统网络IO会低效,原因就在于有很多个并发socket都要处理,而read()函数是阻塞的,其中一个socket不发送数据

就会被阻塞住,所以后面的socket假设有数据到达也不能处理。基于这种IO模型,如果要处理多个socket就得开线程池,但是也没用所有线程都会阻塞在read()中。

来看看 NIO 的编程模型

public static void main(String[] args) throws IOException {

    try (Selector selector = Selector.open();

         ServerSocketChannel serverSocket = ServerSocketChannel.open();) {

        serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));

        serverSocket.configureBlocking(false);

        // 指定多个IO事件,当有新连接到达 或 某个socket有读事件发生即有数据到达,selector就会感知到

        serverSocket.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {

            // 同步阻塞等待监听的 ServerSocketChannel 发生监听的IO事件后返回

            selector.select();

            // 获得文件描述符,这些key都是发生监听的IO事件后的描述符

            for (SelectionKey selectionKey : selector.selectedKeys()) {

                // 进一步判断当前socket是发生了什么IO事件

                if (SelectionKey.OP_ACCEPT == selectionKey.readyOps()) {

                    ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();

                    SocketChannel socketChannel = channel.accept();

                    if (null != socketChannel) {

                        // a new socket do something

                        socketChannel.configureBlocking(false);

                        // 新的客户端连接 监听数据读取事件

                        socketChannel.register(selector, SelectionKey.OP_READ);

                    }

                }

                if (SelectionKey.OP_READ == selectionKey.readyOps()) {

                    // do read

                    SocketChannel socketChannel = (SocketChannel) selectionKey.attachment();

                    ByteBuffer byteBuffer = ByteBuffer.allocate(2048);

                    int i ;

                     // 同步读取到达的客户端数据,同样是read()这里能保证一定要数据可读而避免了无效的阻塞浪费时间

                    // 将TCP连接就绪的数据从内核缓冲读到用户空间的缓冲

                    while ((i = socketChannel.read(byteBuffer)) != -1) {

                        // 这里就涉及到NIO中的 Buffer 对象操作。数据已到达用户空间的缓冲区可直接读写数据

                    }

                }

            }

        }

    }

}

这样一个线程就可以处理很多个并发连接请求,关键在于这个IO模型解决了不知道哪些socket有数据可读的问题。

当 serversocket 向selector注册指定的IO事件后,就可以在有指定IO事件发生后直接处理发生IO事件的socket,避免了无效的read()阻塞。

又因为read()是基于内存读取数据非常快,所以一般一个线程都可以处理多个并发连接。

NIO的处理模型就是基于IO事件监听和通知机制,底层在Linux系统上使用的epoll api实现。

一个线程处理多个socket连接请求,这就是多路复用机制。

参考文章:

java 提供了哪些IO方式 - 小南天门 - 博客园java 提供了哪些IO方式

Java网络编程 -- NIO非阻塞网络编程 - CodingDiary - 博客园Java网络编程 -- NIO非阻塞网络编程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值