`
逸情公子
  • 浏览: 896245 次
  • 性别: 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原理的知识,这里整理了详细资料及简单示例代码和原理图,有需要的小伙伴可以参考下

    java NIO技巧及原理

    java NIO技巧及原理解析,java IO原理,NIO框架分析,性能比较

    Java NIO原理分析及代码实例

    NULL 博文链接:https://dengqsintyt.iteye.com/blog/2083316

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

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

    java nio 实现socket

    java nio 实现socketjava nio 实现socketjava nio 实现socketjava nio 实现socketjava nio 实现socket

    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原理和使用

    java nio 附带例子 以及原理 java nio 附带例子 以及原理 java nio 附带例子 以及原理 java nio 附带例子 以及原理

    JavaNIO chm帮助文档

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

    Java NIO原理解析

    Java NIO原理解析jdk供的无阻塞I/O(NIO)有效解决了多线程服务器存在的线程开销问题,但在使用上略显得复杂一些。在NIO中使用多线程,主要目的已不是为了应对每个客户端请求而分配独立的服务线程,而是通过多线程...

    java nio示例代码

    该资源为Java nio入门的部分简单示例代码,目的是用简单的示例程序,说明nio中的知识点,希望对你的学习有所帮助

    Java NIO的介绍及工作原理

    Java NIO的介绍及工作原理Java NIO的介绍及工作原理

    Java NIO学习资料+代码.zip

    Java NIO学习资料+代码.zip

    Java NIO英文高清原版

    Java NIO英文高清原版

    Java NIO 中文 Java NIO 中文 Java NIO 中文文档

    Java 代码的执行效率。这本小册子就程序员所面临的有代表性的 I/O 问题作了详尽阐述,并讲解了 如何才能充分利用新的 I/O 特性所提供的各种潜能。您将通过实例学会如何使用这些工具来解决现 实工作中常常遇到的 I/O ...

    Java NIO原理和使用

    Java NIO非堵塞技术实际是采取Reactor模式,或者说是Observer模式为我们监察I/O端口,如果有内容进来,会自动通知我们,这样,我们就不必开启多个线程死等,从外界看,实现了流畅的I/O读写,不堵塞了。 Java NIO...

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

    Java NIO反应器模式讲解,目前热门的Java网络通信框架中Mina,Netty等都采用NIO

    java NIO 中文版

    讲解了 JavaIO 与 JAVA NIO区别,JAVA NIO设计理念,以及JDK中java NIO中语法的使用

Global site tag (gtag.js) - Google Analytics