`
yale
  • 浏览: 359915 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

请您先登录,才能继续操作

你还在用IO吗?

 
阅读更多

回顾传统
有必要了解传统的I/O操作的方式。以网络应用为例,传统方式需要监听一个ServerSocket,接受请求的连接为其提供服务(服务通常包括了处理请求并发送响应),下图是服务器的生命周期图,其中标有粗黑线条的部分表明会发生I/O阻塞。

 

 

ServerSocket server=new ServerSocket(10000);
//接受新的连接请求 
Socket newConnection=server.accept();
//对于accept方法的调用将造成阻塞,直到ServerSocket接受到一个连接请求为止。一旦连接请求被接受,服务器可以读客户socket中的请求。
InputStream in = newConnection.getInputStream();
InputStreamReader reader = new InputStreamReader(in);
BufferedReader buffer = new BufferedReader(reader);
Request request = new Request();
while(!request.isComplete()) {
String line = buffer.readLine();
request.addLine(line);
}
//这样的操作有两个问题,首先BufferedReader类的readLine()方法在其缓冲区未满时会造成线程阻塞,只有一定数据填满了缓冲区或者客户关闭了套接字,方法才会返回。其次,它回产生大量的垃圾,BufferedReader创建了缓冲区来从客户套接字读入数据,但是同样创建了一些字符串存储这些数据。虽然BufferedReader内部提供了StringBuffer处理这一问题,但是所有的String很快变成了垃圾需要回收。
//同样的问题在发送响应代码中也存在
Response response = request.generateResponse();
OutputStream out = newConnection.getOutputStream();
InputStream in = response.getInputStream();
int ch;
while(-1 != (ch = in.read())) {
out.write(ch);
}
newConnection.close();
//类似的,读写操作被阻塞而且向流中一次写入一个字符会造成效率低下,所以应该使用缓冲区,但是一旦使用缓冲,流又会产生更多的垃圾。

  

   传统的解决方法
  通常在Java中处理阻塞I/O要用到线程(大量的线程)。一般是实现一个线程池用来处理请求,如下图:

 

  

  线程使得服务器可以处理多个连接,但是它们也同样引发了许多问题。每个线程拥有自己的栈空间并且占用一些CPU时间,耗费很大,而且很多时间是浪费在阻塞的I/O操作上,没有有效的利用CPU。


  选择NIO

  NIO包(java.nio.*)引入了四个关键的抽象数据类型,它们共同解决传统的I/O类中的一些问题。
1. Buffer:它是包含数据且用于读写的线形表结构。其中还提供了一个特殊类用于内存映射文件的I/O操作。
2. Charset:它提供Unicode字符串影射到字节序列以及逆影射的操作。
3. Channels:包含socket,file和pipe三种管道,它实际上是双向交流的通道,你可能注意到现有的java.io类中没有一个能够读写Buffer类型,所以NIO中提供了Channel类来读写Buffer。channel就是一个读写的管道,通过管道的读写来完成IO操作。channel分为ServerSocketChannel和SocketChannel,前者用于监听,获得客户端的连接,后者直接用于操作IO,来看看Channel如何进行Socket操作
4. Selector:它将多元异步I/O操作集中到一个或多个线程中,在过去的阻塞I/O中,我们一般知道什么时候可以向stream中读或写,因为方法调用直到stream准备好时返回。但是使用非阻塞通道,我们需要一些方法来知道什么时候通道准备好了。在NIO包中,设计Selector就是为了这个目的。SelectableChannel可以注册特定的事件,而不是在事件发生时通知应用,通道跟踪事件。然后,当应用调用Selector上的任意一个selection方法时,它查看注册了的通道看是否有任何感兴趣的事件发生

 

String host = 127.0.0.1;
InetSocketAddress socketAddress = new InetSocketAddress(host, 80);
//默认情况下,所有channel(包括ServerSocketChannel, SocketChannel)的工作模式是阻塞
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.connect(socketAddress);
//阻塞监听客户端,获得客户端的连接channel
SocketChannel sc = ssc.accept();
Charset charset = Charset.forName("ISO-8859-1");
CharsetEncoder encoder = charset.newEncoder();
String request = "GET / \r\n\r\n";
//阻塞写
sc.write(encoder.encode(CharBuffer.wrap(request)));
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
//阻塞读
while(sc.read(buffer) != -1){
    //do something
}

  

NIO通过channel来操作IO,概念上更加清晰。但发现:它仍然是阻塞的操作模式,本质上于传统Socket IO来比,工作模式并没有本质上的变化呀,还不是要分配线程。的确,如果NIO只带来了这些,那么NIO也没有什么优势。但是NIO带来的不只是这些,这些channel可以配置成no-blocking模式,借助于selector,NIO带来了一种新的socket编写模式:

 

String host = 127.0.0.1;
InetSocketAddress socketAddress = new InetSocketAddress(host, 80);
ServerSocketChannel  ssc = ServerSocketChannel.open();
//配置channel的阻塞模式
ssc.configureBlocking(false);
ssc.connect(socketAddress);
Selector selector = Selector.open(); 
//将ServerSocketChannel注册到selector上,selector可以检测多路channel
ssc.register(selctor, SelectionKey.OP_ACCEPT);

while(true){
    //阻塞等待事件响应
    selector.select();
    Set<SelectionKey> selectionKeys = selector.selectedKeys();
    Iterator<SelectionKey> it = selectionKeys.iterator();
    //获得多路的channel,这些channel此时都已准备就绪,工作在非阻塞模式,可以非阻塞读写
    while (it.hasNext()) { 
            SelectionKey key = it.next(); 
            it.remove(); 
            if (key.isAcceptable()) { 
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); 
        //此时的accept是非阻塞的,立马返回
            SocketChannel channel = serverSocketChannel.accept(); 
            channel.configureBlocking(false); 
        //可以不断得将这些channel注册成不同的类型,使之即可读,又可写
            channel.register(selector, SelectionKey.OP_READ); 
            } else if (key.isReadable()) { 
            SocketChannel channel = (SocketChannel) key.channel();
        //非阻塞读,立马返回数据
        channel.read(buffer);
        //...
        SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_WRITE); 
            selectionKey.attach(new HandleClient(clientName));
              } else if (key.isWritable()) { 
            SocketChannel channel = (SocketChannel) key.channel(); 
            HandleClient handleClient = (HandleClient) key.attachment(); 
            ByteBuffer buffer = handleClient.readBlock(); 
       
            } 
        } 
}

 

在这种模式下,原来负责端口监听的accept()方法换成了select()方法,两者都是阻塞的,本质上没有分别.区别在于select()之后返回的所有channel都是非阻塞的,都是可以马上读写的而accept()之后的channel则是阻塞的,不能保证此时返回的channel的读写能够马上返回。因此,NIO的非阻塞方式就可以设置比较少的线程,因为这些线程拿到的channel都是立马可以读写的,这些线程的工作都是满负荷的,效率高。反之,阻塞方式需要创建同样较多的线程,因为这些线程很多都处于阻塞休眠状态,大家都不是满负荷在工作。这样NIO的优点就很明显了。

分享到:
评论

相关推荐

    PlatformIO 离线安装资源

    对于ESP32和ESP8266的Arduino框架开发,PlatformIO提供了预配置的平台定义,你可以通过在`platformio.ini`配置文件中指定相应的平台来使用: ```ini [env:esp32dev] platform = espressif32 board = esp32dev ...

    逻辑IO与物理IO 逻辑IO与物理IO 逻辑IO与物理IO

    在一些体系结构中,例如PowerPC、m68k等,IO端口被映射到内存空间中,称为内存映射方式,CPU可以直接使用内存访问指令与这些端口交互。这种方式简化了CPU与外设的交互,因为不需要专门的IO指令,但它可能导致内存...

    KUKA机器人如何查看输入输出IO信号?.docx

    在实际操作中,理解并能查看机器人的输入输出(IO)信号至关重要,因为这些信号控制着机器人与周围设备的交互。本篇将详细介绍如何在KUKA机器人控制系统中查看输入输出IO信号。 首先,打开KUKA机器人的控制系统界面...

    S7-300 PROFINET IO 通信快速入门.pdf

    在讨论S7-300 PROFINET IO通信快速入门时,必须首先理解PROFINET的基础知识。...通过逐步的指导和实际操作,使用者可以有效地完成PROFINET IO通信的配置与管理,从而在实际应用中实现自动化设备的稳定通信和高效控制。

    SINAMICS V20变频器如何扩展数字量IO输入输出?.docx

    这种方式不仅提供了灵活的IO扩展,还便于实现复杂的逻辑控制和数据处理。 在设置通信连接时,要确保变频器和PLC的波特率、地址以及握手协议等参数一致。同时,需要正确配置PLC的程序,定义输入输出变量,并确保通讯...

    EPLAN中绘制PLC盒子时如何切换不同品牌PLC的IO地址?.docx

    EPLAN中绘制PLC盒子时如何切换不同品牌PLC的IO地址?

    DDRIO模块简要使用说明

    DDRIO(Double Data Rate Input/Output)模块是Xilinx FPGA设计中用于实现高速数据传输的关键...在实际工程中,配合详细的文档(如“DDR IO简要使用说明.doc”)进行学习和参考,将有助于你更好地掌握DDRIO模块的运用。

    stm32用IO模拟mipi时序

    stm32 IO模拟MIPI协议 #define MIPI_DATA_IN() {GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; GPIO_Init(GPIOE,&GPIO;_InitStructure);} #define MIPI_DATA_OUT() {GPIO_...

    第11讲 Java提供了哪些IO方式? NIO如何实现多路复用?1

    理解IO不仅是对文件的操作,还包括网络编程中的Socket通信。InputStream和OutputStream用于处理字节流,适合处理二进制数据,如图像文件。Reader和Writer处理字符流,支持字符编码和解码,适合文本处理。 总的来说...

    TIA博途中如何在IO域中添加单位?.docx

    在工业自动化领域,西门子的TIA博途( Totally Integrated Automation Portal)是一款广泛使用的集成自动化软件,它涵盖了从设计、编程、模拟到诊断的全过程。在TIA博途中,IO(Input/Output)域是用于处理设备或...

    网络IO模型:同步IO和异步IO,阻塞IO和非阻塞IO

    在计算机科学中,网络I/O(输入/输出)模型是处理数据传输的关键部分,尤其是在Java编程语言中。这里我们将深入探讨同步IO、异步IO、阻塞IO和非...在网络编程中,正确理解和使用IO模型是提高系统效率和用户体验的关键。

    IO Server.zip Intouch设备配置驱动IO Server软件软件安装包

    Intouch是一款广泛应用于工业自动化领域的上位机监控软件,由Wonderware公司开发,它提供了强大的可视化界面设计和数据...如果你是Intouch的使用者,了解和掌握如何正确安装和配置IO Server驱动将对你的工作大有裨益。

    socket.io,socket.io-client下载

    在客户端,你可以使用`socket.io-client`库,这个库通常用于JavaScript运行环境,如浏览器或React Native等,但也有针对Java和Android的实现,如你提到的`socket.io-client-0.5.0.jar`。 在Java或Android项目中,`...

    drawio obsidian 安装包

    例如,当你在规划项目时,可以先用Draw.io绘制出流程图,然后将其导入到Obsidian的项目笔记中,方便团队成员查看和讨论。当项目发生变化时,只需更新Draw.io中的图表,Obsidian中的图表也会自动更新,保持信息的一致...

    网络IO模型:同步IO和异步IO,阻塞IO和非阻塞IO.pdf

    同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问题其实不同的人给出的答案都可能不同,比如wiki,就认为asynchronous IO和non...

    RAPIDIO嵌入式系统互连_rapidio中文协议_rapidio嵌入式_

    通过学习这本书,读者不仅可以了解 RapidIO 协议的基本原理,还能掌握如何在实际项目中运用这些知识。对于那些需要处理高吞吐量、低延迟任务的系统设计师,RapidIO 技术是一个值得深入研究的领域。

    基恩士下IO-Link配置方式.zip_IO-link_io link_基恩士 iolink_基恩士IO—LINK_基恩士远程I

    5. **编程逻辑**:在PLC编程时,使用IO-Link变量和功能块来与从站设备交互。例如,读取传感器数据,控制执行器动作,或者利用IO-Link的事件通知功能实现自动化逻辑。 6. **测试与调试**:配置完成后,进行全面的...

    RapidIO V3.0

    RapidIO使用信用机制,即接收方在可以接受新数据前会通知发送方其“信用”情况。只有当发送方接收到足够的信用时,才开始发送数据,从而避免了接收缓冲区的溢出。 在RapidIO V3.0规范中,增加了对更高线路速率的...

    drawio.js源码

    在实际应用中,drawio.js不仅限于在线编辑,还可以嵌入到其他Web应用中,提供图形编辑功能。例如,它可以集成到项目管理工具、文档编辑器或者IDE中,帮助用户快速绘制图表,提升工作效率。 总结来说,draw.io.js...

    IO端口和IO内存详解

    几乎每一种外设都是通过读写设备上的寄存器来进行...CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/O-mapped),另一种是内存映射方式(Memory-mapped)。而具体采用哪一种则取决于CPU的体系结构。

Global site tag (gtag.js) - Google Analytics