`
逸情公子
  • 浏览: 899495 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

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

阅读更多

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

最近在分析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,下面贴出服务端和客户端的简单代码实现。

服务端:

 

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();
	}

}

 

 

客户端:

 

 

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) {
			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机制源码了,博客地址:http://weixiaolu.iteye.com/blog/1504898 。不过如果对java NIO的理解存在异议的,欢迎一起讨论。

 

 

 

如需转载,请注明出处:http://weixiaolu.iteye.com/blog/1479656

 

  • 大小: 43 KB
  • 大小: 46.4 KB
  • 大小: 48.2 KB
45
6
分享到:
评论
37 楼 fytain 2017-04-07  
感谢分享,获益匪浅!
36 楼 herman_liu76 2017-02-08  
看完文章有几个体会:
1.客户端建好连接后,得到一个channel,这个可以用来写,一般写好了,就需要读。这个写是客户端自己控制的,需要写时就写喽,但读的时候是要等待的,真正阻塞的是这个时候。那么需要安排一个线程专门侦听,把通道自己注册给这个线程,等通知。有点异步的感觉了。侦听器听到了,就找到通道,让通道再来处理,又有点回调的感觉。
--类比:我打电话给她,但等待电话时我想办点其它事,比如回个微信啥的,我让秘书帮我听着,有了回复通知我来接。
2.服务器接受一个连接要等,连接好也要等着。这时候都是阻塞的。那么也找秘书帮着侦听呗。问题是如果服务器主动要发一个信息怎么办?是不是连接后,把产生的一个个通道都按客户记录在一个map中,如果想给谁发,从map中拿出来通道,如果通道有效就发。我想selector作为秘书,也是服务多个人的,她自己也有一个map记录通道。
35 楼 marsyoung 2016-03-03  
ite.remove() 会有illegalStateException.
34 楼 qq6413260 2016-02-29  
有2个问题请教下
第一个问题:
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);// 将消息回送给客户端 
    } 
这个channel.write(outBuffer)应该要放到
else if (key.isReadable()) { 
       read(key); 
}
这个后面写一个key.isWriteable吧。
第二个问题:
channel.read(buffer);这个应该是非阻塞方法,每次这个方法返回不一定都写到buffer吧,
是不是应该用while循环判断。
33 楼 hehefan 2016-01-31  
Exception in thread "main" java.io.IOException: 远程主机强迫关闭了一个现有的连接。
请问这是怎么回事呢,先开启服务端,然后起客户端的时候就报了这个
32 楼 三片仙人掌 2015-11-02  
没注册写的事件,服务端为什么可以向客户端write呢?
31 楼 346925294 2015-10-29  
学习了,麻烦请教楼主一个问题:
我用TCP工具作为客户端调试服务端程序时发现,我在工具侧断开连接,会导致一直循环触发读事件,我加打印看了下selector.selectedKeys()中key的数量,每次处理完会正常变为0,但下次又会莫名其妙变为1,导致select跳出,继续处理,read也不会抛异常,程序就是循环读出空数据。这是什么情况?
30 楼 di1984HIT 2015-07-26  
学习了。
29 楼 ddjj7 2015-07-10  
这些channel在最底层应该还是传统的socket吧,难道不会很快的就占满了系统所有连接?
28 楼 ddjj7 2015-07-10  
每一个客户端连接上来的channel,假设是请求-响应交互就完成这个场景,每个channel不用显式的关闭么?
服务端在readable事件中进行读取,然后复用这个channel直接做了write返回,之后就等待这个已经不用的channel被gc自动回收?
27 楼 jiangxia207 2015-06-25  
read方法中用到的ByteBuffer有问题,用下面方法就不存在20楼所说的问题了。


SocketChannel channel = (SocketChannel) key.channel();
// 创建读取的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(80);
buffer.clear();
channel.read(buffer);
byte[] data = buffer.array();
//buffer.position(0);
String msg = new String(data).trim();
System.out.println("服务端收到信息:" + msg);


channel.write(buffer);// 将消息回送给客户端
26 楼 abc98103 2015-04-09  
25 楼 xiaoyukid 2015-03-24  
lan__bo 写道
刚刚学习nio, 看了博主的文章有一个问题想问下:
一个selector可以管理多个通道, 通道是按照什么来划分的(按不同端口吗? 一个端口建立一个通道?),怎么建立多个通道? 看了博主的代码应该只是建了一个通道, 然后将通道注册到selector上, 并给他相应的权限。

selector.selectedKeys().iterator()

通过这个Key
24 楼 sundhu 2015-03-11  
这个NIO,可以这样理解吗:
基于事件(或值)队列阻塞的读写分离双通道模型。
23 楼 lan__bo 2014-10-30  
刚刚学习nio, 看了博主的文章有一个问题想问下:
一个selector可以管理多个通道, 通道是按照什么来划分的(按不同端口吗? 一个端口建立一个通道?),怎么建立多个通道? 看了博主的代码应该只是建了一个通道, 然后将通道注册到selector上, 并给他相应的权限。
22 楼 从此醉 2014-10-16  
减少无谓的线程切换?
21 楼 kakarottoz 2014-10-09  
好文支持  感谢逸情公子分享
20 楼 冰上王国 2014-07-21  
不错!但有一点不明白,望lz赐教:
   就是假设把client中的read代码补充完整(跟server中的read一样),然后运行起来就发现,程序在不停的读,且server和client的读的内容都是一样的。我的问题就是,既然是io交互,server写的应该是给client读,client写的应该是给server,而不是全混在一起

<!--一下是测试日志-->

服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息2
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息2
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息2
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息2
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息2
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息2
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息2
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息2
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息2
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息2
19 楼 xunke515 2013-09-03  
逸情公子 写道
引用
服务器启动成功,运行客户端代码,服务端没反应.
用浏览器访问:localhost:8000//
响应如下.
你自己再研究研究吧,看看是哪里出现了问题,可以分享出来


- -, 那好吧.
18 楼 逸情公子 2013-09-03  
引用
服务器启动成功,运行客户端代码,服务端没反应.
用浏览器访问:localhost:8000//
响应如下.
你自己再研究研究吧,看看是哪里出现了问题,可以分享出来

相关推荐

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

    - **Java NIO原理及通信模型**:NIO采用了非阻塞的方式,通过一个单独的线程来处理所有IO事件,使用事件驱动机制,当事件发生时才进行处理。它依赖于选择器(Selector)来监控多个通道(Channel)上的事件,如连接...

    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反应器模式设计与实现

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

    java NIO.zip

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

    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