前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java 的 NIO 是如何工作的?

Java 的 NIO 是如何工作的?

作者头像
水货程序员
修改于 2018-11-16 01:59:07
修改于 2018-11-16 01:59:07
1.7K00
代码可运行
举报
文章被收录于专栏:javathingsjavathings
运行总次数:0
代码可运行

在这个数据爆炸的时代,有大量的数据在系统中流动,一个应用系统的瓶颈往往都是 IO 瓶颈。传统的 javaIO 模型是 BIO,也就是同步阻塞 IO,数据在写入 OutputStream 或者从 InputStream 读取时,如果没有数据没有读到或写完,线程都会被阻塞,处于等待状态,直到数据读取完成或写入完成。而在网络编程中,每一个客户端连接发出后,服务端都会有一个对应线程来处理请求,服务器线程与并发数成 1:1 关系,然而一个服务器所能处理的线程是有限的,处理高并发时就会有问题。

NIO 是一种非阻塞同步 IO,它是一种 Reactor 模式的编程模型,简单来讲,就是当服务端有多个连接接入时,并不为每个连接单独创建线程,而是创建一个 Reactor 线程,用多路复用器来不断的轮询每一个接入的连接,找出有数据需要读写的连接进行后续操作,从而具有处理多个连接的能力。

java 原生的 NIO 实现有很多类和组件,但其核心组件有三个,其他的都是一些相关的工具类:

  • Channel    与 BIO 中的流不同,NIO 用 Chananl 来抽象数据通道,数据通过 Channel 来读取和写入,从 Channle 的类图来看,通道分为两大类:用于网络读写的 SelectableChannel 和用于文件读写的 FileChannel
  • Buffer     在 NIO 中,数据与 Channel 之间的交互是通过 buffer 来进行的,数据读写先经过 buffer 再进入通道
  • Selector   多路复用器 Selector 是 NIO 的基础。Selector 会不断轮询注册在其上面的 Channel,如果某个 Channel 上发生读或写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取就绪 Channel 集合,让后异步的将 Channel 的数据读入缓冲区

下面是一个简单 NIO 服务器,用来演示 NIO 的编程模型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
 
import javax.swing.text.html.HTMLDocument.Iterator;
 
/**
* Simple echo-back server which listens for incoming stream connections and
* echoes back whatever it reads. A single Selector object is used to listen to
* the server socket (to accept new connections) and all the active socket
* channels.
* @author zale (zalezone.cn)
*/
public class SelectSockets {
    public static int PORT_NUMBER = 1234;
    public static void main(String[] argv) throws Exception 
    {
        new SelectSockets().go(argv);
    }
    public void go(String[] argv) throws Exception 
    {
        int port = PORT_NUMBER;
        if (argv.length > 0) 
        { // 覆盖默认的监听端口
            port = Integer.parseInt(argv[0]);
        }
        System.out.println("Listening on port " + port);
        ServerSocketChannel serverChannel = ServerSocketChannel.open();// 打开一个未绑定的serversocketchannel
        ServerSocket serverSocket = serverChannel.socket();// 得到一个ServerSocket去和它绑定 
        Selector selector = Selector.open();// 创建一个Selector供下面使用
        serverSocket.bind(new InetSocketAddress(port));//设置server channel将会监听的端口
        serverChannel.configureBlocking(false);//设置非阻塞模式
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);//将ServerSocketChannel注册到Selector
        while (true) 
        {
            // This may block for a long time. Upon returning, the
            // selected set contains keys of the ready channels.
            int n = selector.select();
            if (n == 0) 
            {
                continue; // nothing to do
            }           
            java.util.Iterator<SelectionKey> it = selector.selectedKeys().iterator();// Get an iterator over the set of selected keys
            //在被选择的set中遍历全部的key
            while (it.hasNext()) 
            {
                SelectionKey key = (SelectionKey) it.next();
                // 判断是否是一个连接到来
                if (key.isAcceptable()) 
                {
                    ServerSocketChannel server =(ServerSocketChannel) key.channel();
                    SocketChannel channel = server.accept();
                    registerChannel(selector, channel,SelectionKey.OP_READ);//注册读事件
                    sayHello(channel);//对连接进行处理
                }
                //判断这个channel上是否有数据要读
                if (key.isReadable()) 
                {
                    readDataFromSocket(key);
                }
                //从selected set中移除这个key,因为它已经被处理过了
                it.remove();
            }
        }
    }
    // ----------------------------------------------------------
    /**
    * Register the given channel with the given selector for the given
    * operations of interest
    */
    protected void registerChannel(Selector selector,SelectableChannel channel, int ops) throws Exception
    {
        if (channel == null) 
        {
            return; // 可能会发生
        }
        // 设置通道为非阻塞
        channel.configureBlocking(false);
        // 将通道注册到选择器上
        channel.register(selector, ops);
    }
    // ----------------------------------------------------------
    // Use the same byte buffer for all channels. A single thread is
    // servicing all the channels, so no danger of concurrent acccess.
    //对所有的通道使用相同的缓冲区。单线程为所有的通道进行服务,所以并发访问没有风险
    private ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    /**
    * Sample data handler method for a channel with data ready to read.
    * 对于一个准备读入数据的通道的简单的数据处理方法
    * @param key
    *
    A SelectionKey object associated with a channel determined by
    the selector to be ready for reading. If the channel returns
    an EOF condition, it is closed here, which automatically
    invalidates the associated key. The selector will then
    de-register the channel on the next select call.
 
    一个选择器决定了和通道关联的SelectionKey object是准备读状态。如果通道返回EOF,通道将被关闭。
    并且会自动使相关的key失效,选择器然后会在下一次的select call时取消掉通道的注册
    */
    protected void readDataFromSocket(SelectionKey key) throws Exception 
    {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        int count;
        buffer.clear(); // 清空Buffer
        // Loop while data is available; channel is nonblocking
        //当可以读到数据时一直循环,通道为非阻塞
        while ((count = socketChannel.read(buffer)) > 0) 
        {
            buffer.flip(); // 将缓冲区置为可读
            // Send the data; don't assume it goes all at once
            //发送数据,不要期望能一次将数据发送完
            while (buffer.hasRemaining()) 
            {
                socketChannel.write(buffer);
            }
            // WARNING: the above loop is evil. Because
            // it's writing back to the same nonblocking
            // channel it read the data from, this code can
            // potentially spin in a busy loop. In real life
            // you'd do something more useful than this.
            //这里的循环是无意义的,具体按实际情况而定
            buffer.clear(); // Empty buffer
        }
        if (count < 0) 
        {
            // Close channel on EOF, invalidates the key
            //读取结束后关闭通道,使key失效
            socketChannel.close();
        }
    }
    // ----------------------------------------------------------
    /**
    * Spew a greeting to the incoming client connection.
    *
    * @param channel
    *
    The newly connected SocketChannel to say hello to.
    */
    private void sayHello(SocketChannel channel) throws Exception 
    {
        buffer.clear();
        buffer.put("Hi there!\r\n".getBytes());
        buffer.flip();
        channel.write(buffer);
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
BIO、NIO、IO多路复用模型的演进&Java NIO 网络编程
上文介绍了网络编程的基础知识,并基于 Java 编写了 BIO 的网络编程。我们知道 BIO 模型是存在巨大问题的,比如 C10K 问题,其本质就是因其阻塞原因,导致如果想要承受更多的请求就必须有足够多的线程,但是足够多的线程会带来内存占用问题、CPU上下文切换带来的性能问题,从而造成服务端崩溃的现象。怎么解决这一问题呢?优化呗,所以后面就有了NIO、AIO、IO多路复用。本文将对这几个模型详细说明并基于 Java 编写 NIO。
王二蛋
2024/01/18
7800
012. NIO 非阻塞网络编程
1. Java NIO ---- 始于 Java1.4,提供了新的 JAVA IO 操作非阻塞 API。用意是替代 Java IO 和 Java Networking 相关的 API。 三个核心组件 Buffer 缓冲区 Channel 通道 Selector 选择器 2. Buffer 缓冲区 ---- 1. 介绍 缓冲区本质上是一个可以写入数据的内存块(类似数组),然后可以再次读取。此内存块包含在 NIO Buffer 对象中,该对象提供了一组方法,可以更轻松地使用内存块。 相比较直接对数组的操
山海散人
2021/03/03
4100
012. NIO 非阻塞网络编程
NIO复习(3):selector
selector的工作原理,简单来看,就是上面这张图,Channel必须先向Selector注册(注:register的时候,可以选择关注哪些事件,比如:有新连接 或 有数据可读 等),注册成功后,Selector通过select方法来检查这些Channel上是否有事件发生,比如:有数据发过来,channel就可以把数据读到Buffer中。
菩提树下的杨过
2020/03/19
3740
NIO server && ThreadPoolServer
package socket; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.So
大学里的混子
2019/03/12
3540
一些模板代码
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/155628.html原文链接:https://javaforall.cn
全栈程序员站长
2022/09/07
2730
【面试题精讲】javaIO模型之NIO
NIO(New I/O)是Java提供的一种非阻塞I/O模型,它在JDK 1.4中引入。与传统的I/O模型相比,NIO提供了更高效、更灵活的I/O操作方式。
程序员朱永胜
2023/10/22
2230
Java IO 与 NIO:高效的输入输出操作探究
输入输出(IO)是任何编程语言中的核心概念,而在Java中,IO操作更是应用程序成功运行的基石。随着计算机系统变得越来越复杂,对IO的要求也日益增加。在本文中,我们将探讨Java IO和非阻塞IO(NIO)的重要性以及如何在Java中实现高效的输入输出操作。
程序那些事
2023/10/17
2590
NIO之Channel通道(二)-SelectableChannel、SocketChannel、ServerSocketChannel
SelectableChannel是一个抽象类,它实现了Channel接口,这个类比较特殊。
云飞扬
2022/04/25
6040
Java NIO使用及原理分析 (四)
在上一篇文章中介绍了关于缓冲区的一些细节内容,现在终于可以进入NIO中最有意思的部分非阻塞I/O。通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据。同样,写入调用将会阻塞直至数据能够写入。传统的Server/Client模式会基于TPR(Thread per Request),服务器会为每个客户端请求建立一个线程,由该线程单独负责处理一个客户请求。这种模式带来的一个问题就是线程数量的剧增,大量的线程会增大服务器的开销。大多数的实现为了避免这个问题,都采用了线程池模型,并设置线程池线程的最大数量,这由带来了新的问题,如果线程池中有200个线程,而有200个用户都在进行大文件下载,会导致第201个用户的请求无法及时处理,即便第201个用户只想请求一个几KB大小的页面。传统的 Server/Client模式如下图所示:
用户6182664
2019/09/25
5660
Java NIO使用及原理分析 (四)
Java NIO与IO 区别和比较
NIO包(java.nio.*)引入了四个关键的抽象数据类型,它们共同解决传统的I/O类中的一些问题。
IT工作者
2022/03/30
2170
『互联网架构』软件架构-io与nio线程模型reactor模型(上)(53)
PS:NIO不需要的代码里面根本没有多线程,实际上nio只有一个工作线程,一个线程可以为多个客人服务。
IT架构圈
2019/05/30
5440
JavaIO流:NIO梳理
NIO 也叫 Non-Blocking IO 是同步非阻塞的 IO 模型。线程发起 IO 请求后,立即返回。同步指的是必须等待 IO 缓冲区内的数据就绪,而非阻塞指的是,用户线程不原地等待 IO 缓冲区,可以先做一些其他操作,但是要定时轮询检查 IO 缓冲区数据是否就绪。
栗筝i
2022/12/02
3280
JavaIO流:NIO梳理
终结全网!手写Netty面试题答案
创建一个线程,注册到 Selector,将 serversocketchannel 注册到Selector selectionKey 里就有具体的事件
JavaEdge
2021/10/18
2260
漫谈Java IO之 NIO那些事儿
前面一篇中已经介绍了基本IO的使用以及最简单的阻塞服务器的例子,本篇就来介绍下NIO的相关内容,前面的分享可以参考目录: 网络IO的基本知识与概念 普通IO以及BIO服务器 NIO的使用与服务器Hello world Netty入门与服务器Hello world Netty深入浅出 NIO,也叫做new-IO或者non-blocking-IO,就暂且理解为非阻塞IO吧。 为什么选择NIO 那么NIO相对于IO来说,有什么优势呢?总结来说: IO是面向流的,数据只能从一端读取到另一端,不能随意读写。NI
用户1154259
2018/04/10
9090
漫谈Java IO之 NIO那些事儿
Java NIO 实现网络通信
Java NIO 的相关资料很多,对 channel,buffer,selector 如何相关概念也有详细的阐述。但是,不亲自写代码调试一遍,对这些概念的理解仍然是一知半解。
水货程序员
2018/11/13
1.1K0
从零讲解搭建一个NIO消息服务端
假设你已经了解并实现过了一些OIO消息服务端,并对异步消息服务端更有兴趣,那么本文或许能带你更好的入门,并了解JDK部分源码的关系流程,正如题目所说,笔者将竟可能还原,以初学者能理解的角度,讲诉并构建一个NIO消息服务端。
Java猫说
2019/04/11
5260
从零讲解搭建一个NIO消息服务端
完美诠释NIO的强大之处-原理和实战
平时工作中,很大部分时间都投入了业务。我们对于一些框架、设计思想等都没有太去的关注,第一个深入一个技术底层是比较枯燥与孤独的;第二个就是没有人带领去用一个有趣或者通俗易懂的教导;但如果真的是明白了那些大牛们的思维方式,我们都会异口同声的称赞他们,真的就是牛逼啊。
chengcheng222e
2021/11/04
2220
NIO非阻塞网络编程三大核心理念
1.capacity 容量:作为一个内存块,Buffer具有一定的固定大小,也称为【容量】。 2.position 位置:写入模式时代表写数据的位置。读取模式时代表读取数据的位置。 3.limit 限制:写入模式,限制等于buffer的容量,读取模式下,limit等于写入的数据量。
IT架构圈
2020/12/02
4010
NIO非阻塞网络编程三大核心理念
JAVA NIO Socket通道
DatagramChannel和SocketChannel都实现定义读写功能,ServerSocketChannel不实现,只负责监听传入的连接,并建立新的SocketChannel,本身不传输数据。
WindWant
2020/09/11
1K0
JAVA NIO Socket通道
Java知识点——NIO实现网络聊天室
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yp9zg3qj-1584977796519)(img/Selector大哥.png)]
用户7073689
2020/03/26
1.2K0
相关推荐
BIO、NIO、IO多路复用模型的演进&Java NIO 网络编程
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验