`
lvwenwen
  • 浏览: 958376 次
  • 性别: Icon_minigender_1
  • 来自: 魔都
社区版块
存档分类
最新评论

Java NIO原理图文分析及代码实现

    博客分类:
  • Io
阅读更多
Java NIO原理图文分析及代码实现
前言:
文章链接:http://weixiaolu.iteye.com/blog/1479656
最近在分析hadoop的RPC(Remote Procedure Call Protocol ,远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。可以参考:http://baike.baidu.com/view/32726.htm )机制时,发现hadoop的RPC机制的实现主要用到了两个技术:动态代理(动态代理可以参考博客:http://weixiaolu.iteye.com/blog/1477774 )和java NIO。为了能够正确地分析hadoop的RPC源码,我觉得很有必要先研究一下java NIO的原理和具体实现。

这篇博客我主要从两个方向来分析java NIO

目录:
一.java NIO 和阻塞I/O的区别
     1. 阻塞I/O通信模型
     2. java NIO原理及通信模型
二.java NIO服务端和客户端代码实现

具体分析:

一.java NIO 和阻塞I/O的区别

1. 阻塞I/O通信模型

假如现在你对阻塞I/O已有了一定了解,我们知道阻塞I/O在调用InputStream.read()方法时是阻塞的,它会一直等到数据到来时(或超时)才会返回;同样,在调用ServerSocket.accept()方法时,也会一直阻塞到有客户端连接才会返回,每个客户端连接过来后,服务端都会启动一个线程去处理该客户端的请求。阻塞I/O的通信模型示意图如下:



如果你细细分析,一定会发现阻塞I/O存在一些缺点。根据阻塞I/O通信模型,我总结了它的两点缺点:
1. 当客户端多时,会创建大量的处理线程。且每个线程都要占用栈空间和一些CPU时间

2. 阻塞可能带来频繁的上下文切换,且大部分上下文切换可能是无意义的。

在这种情况下非阻塞式I/O就有了它的应用前景。

2. java NIO原理及通信模型

Java NIO是在jdk1.4开始使用的,它既可以说成“新I/O”,也可以说成非阻塞式I/O。下面是java NIO的工作原理:

1. 由一个专门的线程来处理所有的 IO 事件,并负责分发。
2. 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
3. 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。

阅读过一些资料之后,下面贴出我理解的java NIO的工作原理图:



(注:每个线程的处理流程大概都是读取数据、解码、计算处理、编码、发送响应。)

Java NIO的服务端只需启动一个专门的线程来处理所有的 IO 事件,这种通信模型是怎么实现的呢?呵呵,我们一起来探究它的奥秘吧。java NIO采用了双向通道(channel)进行数据传输,而不是单向的流(stream),在通道上可以注册我们感兴趣的事件。一共有以下四种事件:

事件名 对应值
服务端接收客户端连接事件 SelectionKey.OP_ACCEPT(16)
客户端连接服务端事件 SelectionKey.OP_CONNECT(8)
读事件 SelectionKey.OP_READ(1)
写事件 SelectionKey.OP_WRITE(4)

服务端和客户端各自维护一个管理通道的对象,我们称之为selector,该对象能检测一个或多个通道 (channel) 上的事件。我们以服务端为例,如果服务端的selector上注册了读事件,某时刻客户端给服务端发送了一些数据,阻塞I/O这时会调用read()方法阻塞地读取数据,而NIO的服务端会在selector中添加一个读事件。服务端的处理线程会轮询地访问selector,如果访问selector时发现有感兴趣的事件到达,则处理这些事件,如果没有感兴趣的事件到达,则处理线程会一直阻塞直到感兴趣的事件到达为止。下面是我理解的java NIO的通信模型示意图:



二.java NIO服务端和客户端代码实现

为了更好地理解java NIO,下面贴出服务端和客户端的简单代码实现。

服务端:

Java代码 
package cn.nio; 
 
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.SocketChannel; 
import java.util.Iterator; 
 
/**
* NIO服务端
* @author 小路
*/ 
public class NIOServer { 
    //通道管理器 
    private Selector selector; 
 
    /**
     * 获得一个ServerSocket通道,并对该通道做一些初始化的工作
     * @param port  绑定的端口号
     * @throws IOException
     */ 
    public void initServer(int port) throws IOException { 
        // 获得一个ServerSocket通道 
        ServerSocketChannel serverChannel = ServerSocketChannel.open(); 
        // 设置通道为非阻塞 
        serverChannel.configureBlocking(false); 
        // 将该通道对应的ServerSocket绑定到port端口 
        serverChannel.socket().bind(new InetSocketAddress(port)); 
        // 获得一个通道管理器 
        this.selector = Selector.open(); 
        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后, 
        //当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。 
        serverChannel.register(selector, SelectionKey.OP_ACCEPT); 
    } 
 
    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
     * @throws IOException
     */ 
    @SuppressWarnings("unchecked") 
    public void listen() throws IOException { 
        System.out.println("服务端启动成功!"); 
        // 轮询访问selector 
        while (true) { 
            //当注册的事件到达时,方法返回;否则,该方法会一直阻塞 
            selector.select(); 
            // 获得selector中选中的项的迭代器,选中的项为注册的事件 
            Iterator ite = this.selector.selectedKeys().iterator(); 
            while (ite.hasNext()) { 
                SelectionKey key = (SelectionKey) ite.next(); 
                // 删除已选的key,以防重复处理 
                ite.remove(); 
                // 客户端请求连接事件 
                if (key.isAcceptable()) { 
                    ServerSocketChannel server = (ServerSocketChannel) key 
                            .channel(); 
                    // 获得和客户端连接的通道 
                    SocketChannel channel = server.accept(); 
                    // 设置成非阻塞 
                    channel.configureBlocking(false); 
 
                    //在这里可以给客户端发送信息哦 
                    channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息").getBytes())); 
                    //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。 
                    channel.register(this.selector, SelectionKey.OP_READ); 
                     
                    // 获得了可读的事件 
                } else if (key.isReadable()) { 
                        read(key); 
                } 
 
            } 
 
        } 
    } 
    /**
     * 处理读取客户端发来的信息 的事件
     * @param key
     * @throws IOException 
     */ 
    public void read(SelectionKey key) throws IOException{ 
        // 服务器可读取消息:得到事件发生的Socket通道 
        SocketChannel channel = (SocketChannel) key.channel(); 
        // 创建读取的缓冲区 
        ByteBuffer buffer = ByteBuffer.allocate(10); 
        channel.read(buffer); 
        byte[] data = buffer.array(); 
        String msg = new String(data).trim(); 
        System.out.println("服务端收到信息:"+msg); 
        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes()); 
        channel.write(outBuffer);// 将消息回送给客户端 
    } 
     
    /**
     * 启动服务端测试
     * @throws IOException 
     */ 
    public static void main(String[] args) throws IOException { 
        NIOServer server = new NIOServer(); 
        server.initServer(8000); 
        server.listen(); 
    } 
 



客户端:


Java代码 
package cn.nio; 
 
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.SocketChannel; 
import java.util.Iterator; 
 
/**
* NIO客户端
* @author 小路
*/ 
public class NIOClient { 
    //通道管理器 
    private Selector selector; 
 
    /**
     * 获得一个Socket通道,并对该通道做一些初始化的工作
     * @param ip 连接的服务器的ip
     * @param port  连接的服务器的端口号         
     * @throws IOException
     */ 
    public void initClient(String ip,int port) throws IOException { 
        // 获得一个Socket通道 
        SocketChannel channel = SocketChannel.open(); 
        // 设置通道为非阻塞 
        channel.configureBlocking(false); 
        // 获得一个通道管理器 
        this.selector = Selector.open(); 
         
        // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调 
        //用channel.finishConnect();才能完成连接 
        channel.connect(new InetSocketAddress(ip,port)); 
        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。 
        channel.register(selector, SelectionKey.OP_CONNECT); 
    } 
 
    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
     * @throws IOException
     */ 
    @SuppressWarnings("unchecked") 
    public void listen() throws IOException { 
        // 轮询访问selector 
        while (true) { 
            // 选择一组可以进行I/O操作的事件,放在selector中,客户端的该方法不会阻塞, 
            //这里和服务端的方法不一样,查看api注释可以知道,当至少一个通道被选中时, 
            //selector的wakeup方法被调用,方法返回,而对于客户端来说,通道一直是被选中的 
            selector.select(); 
            // 获得selector中选中的项的迭代器 
            Iterator ite = this.selector.selectedKeys().iterator(); 
            while (ite.hasNext()) { 
                SelectionKey key = (SelectionKey) ite.next(); 
                // 删除已选的key,以防重复处理 
                ite.remove(); 
                // 连接事件发生 
                if (key.isConnectable()) { 
                    SocketChannel channel = (SocketChannel) key 
                            .channel(); 
                    // 如果正在连接,则完成连接 
                    if(channel.isConnectionPending()){ 
                        channel.finishConnect(); 
                         
                    } 
                    // 设置成非阻塞 
                    channel.configureBlocking(false); 
 
                    //在这里可以给服务端发送信息哦 
                    channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息").getBytes())); 
                    //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。 
                    channel.register(this.selector, SelectionKey.OP_READ); 
                     
                    // 获得了可读的事件 
                } else if (key.isReadable()) { 
                        read(key); 
                } 
 
            } 
 
        } 
    } 
    /**
     * 处理读取服务端发来的信息 的事件
     * @param key
     * @throws IOException 
     */ 
    public void read(SelectionKey key) throws IOException{ 
        //和服务端的read方法一样 
    } 
     
     
    /**
     * 启动客户端测试
     * @throws IOException 
     */ 
    public static void main(String[] args) throws IOException { 
        NIOClient client = new NIOClient(); 
        client.initClient("localhost",8000); 
        client.listen(); 
    } 
 



小结:

终于把动态代理和java NIO分析完了,呵呵,下面就要分析hadoop的RPC机制源码了。不过如果对java NIO的理解存在异议的,欢迎一起讨论。


分享到:
评论

相关推荐

    Java NIO原理 图文分析及代码实现

    ### Java NIO原理 图文分析及代码实现 #### 前言 在深入探讨Java NIO之前,我们先简要回顾一下NIO的概念及其引入的原因。随着互联网的发展,越来越多的应用程序需要处理高并发的网络连接请求。传统的阻塞I/O模型在...

    Java NIO原理分析及代码实例

    Java NIO(New IO)是Java 1.4版本引入的一个新API,全称为Non-blocking Input/Output,它提供了一种不同于传统IO的编程模型,传统IO...在实际编码时,参考博文链接中的代码实例,可以帮助你更好地理解和实践Java NIO。

    java NIO技巧及原理

    Java NIO(New Input/Output)是Java标准库提供的一种I/O模型,它与传统的 Blocking I/O(IO)相比,提供了更加高效的数据传输方式。在Java NIO中,"新"主要体现在非阻塞和多路复用这两个特性上,这使得NIO更适合于...

    基于java的BIO、NIO、AIO通讯模型代码实现

    压缩包中的文件可能包含了实现这三种通信模型的Java代码示例,通过学习和理解这些代码,你可以更好地掌握Java I/O的运用,并根据需求选择合适的模型。建议从简单的BIO开始,逐步过渡到更复杂的NIO和AIO,理解其工作...

    java基于NIO实现Reactor模型源码.zip

    java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现...

    java NIO和java并发编程的书籍

    java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java...

    java nio示例代码

    4. **文件系统操作**:Java NIO提供了FileChannel用于文件的读写,可以实现大文件的高效传输。例如,使用transferTo和transferFrom方法可以直接将文件数据从一个通道传输到另一个通道,无需额外的缓冲区。 5. **...

    java NIO实例

    下面将详细介绍Java NIO的主要组件和工作原理,并结合这两个文件名推测它们可能包含的内容。 1. **Selector(选择器)**:选择器是NIO的核心组件,它能够监控多个通道(Channel)的状态变化,当某个通道准备进行...

    JavaNIO chm帮助文档

    Java NIO系列教程(一) Java NIO 概述 Java NIO系列教程(二) Channel Java NIO系列教程(三) Buffer Java NIO系列教程(四) Scatter/Gather Java NIO系列教程(五) 通道之间的数据传输 Java NIO系列教程(六)...

    java NIO原理和使用

    ### Java NIO 原理与使用详解 #### 一、Java NIO 概述 在深入了解 Java NIO 的工作原理及其使用之前,我们首先来了解一下什么是 Java NIO(New I/O)。Java NIO 是 Java SE 1.4 版本引入的一个全新的 I/O API,...

    java nio 实现socket

    #### 四、示例代码分析 下面以服务端为例,简要分析其实现过程: 1. **初始化ServerSocketChannel和Selector**: ```java ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); Selector ...

    Java NIO——Selector机制解析三(源码分析)

    本文将深入探讨Java NIO中的Selector机制,并通过源码分析来理解其实现原理。 Selector机制是Java NIO中的核心组件,它允许单线程同时监控多个通道(Channels)的状态变化,例如连接就绪、数据可读或可写等。这种...

    Java NIO原理解析

    Java NIO,即Non-Blocking I/O,是Java在JDK 1.4引入的一套新的I/O API,旨在提供一种更加高效的方式来处理I/O操作,尤其是对于网络编程和高并发场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器...

    Java NIO学习资料+代码.zip

    Java NIO提供了FileChannel,可以实现高效的大文件读写。例如,可以使用FileChannel的transferTo和transferFrom方法进行文件间的零拷贝操作,减少系统调用,提高效率。 **6. 多路复用** Java NIO的选择器使得单...

    java NIO.zip

    Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java标准库提供的一种替代传统的I/O模型的新技术。自Java 1.4版本引入NIO后,它为Java开发者提供了更高效的数据传输方式,尤其是在处理大量并发...

    基于Java NIO反应器模式设计与实现

    Java NIO的反应器模式设计与实现,首先涉及到理解Java NIO的基本概念,如通道(Channel)、缓冲区(Buffer)、选择器(Selector)等。通道是进行读写操作的基础,是连接I/O源和数据的端点。缓冲区是在NIO中用于数据...

    Java NIO Socket基本

    它们都是`java.nio.channels.Channel`接口的实现类。 2. **缓冲区(Buffer)**:NIO的核心是缓冲区,它提供了一种存储和操作数据的高效方式。缓冲区类型包括ByteBuffer、CharBuffer、IntBuffer、DoubleBuffer等,...

    基于Java NIO实现五子棋游戏.zip

    基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现...

Global site tag (gtag.js) - Google Analytics