`
xuyuanshuaaa
  • 浏览: 394748 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

java NIO

 
阅读更多
一、NIO的出现
        NIO是JDK1.4里面才出现的东东,他给大家带来的最大好处是异步socket。其它file,pipe暂时就不多谈了。
        在JDK1.4出现之前,如果你需要编写一个Java服务器,为了实现异步操作,你必须为每个连接请求生成一个Java线程,当连接请求很多时,线程的调度,上下文切换,所付出的代价是非常昂贵,而且由于Java是跨平台的,各个平台对线程的支持并不相同,性能也不相同,因此传统的Java服务器编程架构是低效的且代价贵,dl大侠写了个util.concurrent包后,总算是减轻了线程调度给java程序员带来的痛苦,但是相比之与C、C++写出来的服务器,java服务器在性能要求很高的情况下,基本上没有什么竞争力,甚至是入围的权利的都没有。

二、异步socket的实现
       NIO出现后,好像让java的程序员有了杨眉吐气的机会,怎么个吐气法,当时大家是个什么感受,俺是不知道,因为当时俺不搞java,对java的认识有限。
       NIO 是一个基于事件的IO架构,最基本的思想就是:有事件我通知你,你再去做你的事情,没事件时你大可以节约大把时间去做其它任何事情。而且NIO的主线程 only one,不像传统的模型,需要N个线程去,也减轻了JVM的工作量,使得JVM处理任务时显得更加高效。
       刚开始接触 NIO时,被N层的Channel架构、网上铺天盖地的好评给镇住了,想想也应当是个很成熟的产品了,网上资料这么多,抄一抄Jetty、Tomcat以及其它一些牛B的源代码,基本上就能搞定了,此时没有想到大家受同步的影响这么深,也没有想到连最基本的异步概念都没有搞清楚就去写代码,搞出一堆的问题来(这是后话,后面再说)。
       现在研究了NIO以后,发现NIO实际上在Java中做的工作是很简单,就是将事件进行收集和分发。

NIO方案

目前,单独线程管理单条Socket链路,之所以耗费资源的根本原因在于:每个线程以阻塞的方式工作,很可能在Socket链路没有数据传输的时候处于Idle状态,比较浪费;浪费的严重程度取决于处于Idle状态的时间长度。

举一个例子,非常不切实际,但希望能够说明问题。

银行柜台,十位工作人员同时工作,第一位负责接待帐号尾号为0的客户、第二位负责接待帐号尾号为1的客户……依此类推。这样,如果一整天都没有一个帐号尾号为0的客户上门,那么第一位工作人员就打盹儿一整天。

显然效率较低。

就这个例子,更好的模式应该是:一位工作人员负责接待所有客户,在没有客户上门的时候可以打盹儿,有客户上门时,类似Seven-Eleven门口的“欢迎光临”口号把打盹儿中的工作人员惊醒,开始接待工作。或者,客户较多,工作人员非但没有机会打盹儿,客户还总需要排队,就可以稍微多设置几位地位平等工作人员,每次客户上门,随意一位恰好闲着的就负责接待。类似于我们现实世界的情形。

Java NIO,就提供了机制采用后一种模式进行工作。
  Java NIO
概述

NIO是Java1.4相对于过去版本的一个较大的功能亮点,N表示New,相关API部署在java.nio.*包中。NIO更多地利用了现代操作系统所提供的底层I/O机制,为我们提供了一种更加高效的I/O解决方案。

Java1.4之前,Java的I/O相关的内容,均由java.io.*包解决,为了描述方便,暂称为“旧IO”。旧IO最重要的概念是流(InputStream和OutputStream)。旧IO以流的方式处理数据。旧IO以阻塞的方式操控流数据。

与本方案相关的,NIO最重要的概念是Buffer、Channel、Selector。NIO以块的方式处理数据。NIO可以按照非阻塞的方式操控数据。

下面简要说明一下NIO的工作模式。(注:仅介绍与本方案相关的NIO内容。)
概念

1.  Buffer:在NIO的世界中,所有数据都在Buffer中,从Buffer中读、写入Buffer。每中基本数据类型都有一个对应的Buffer类,其中ByteBuffer最为常用,也比较特殊——与Channel类联系最为紧密。

Buffer有三个状态变量:position、limit、capacity。Buffer可理解为比较高级的数据,这几个状态变量描述了数组中的数据装载情况,从变量名即可大约知道他们各自是什么意思。详细信息可参考本文末尾罗列的相关参考文献。

2.  Channel:类似于旧IO的Stream,ServerSocket、Socket都有对应的Channel类,即ServerSocketChannel、SocketChannel,他们之间是双向关联的,即可以互相取得对方的句柄。与Stream不同的是,Channel是双向的。我们需要着重注意的是,SelectableChannel类,顾名思义,该类及其子类,表示“可以被选择”,说得更清楚一些,这样的Channel可以注册到某个Selector对象上,Selector对象可以在Channel关注的某些事件到来的时候,以某种方式给予通知。与著名的Observer模式在思路上惺惺相惜。事实上,我们所关心的ServerSocketChannel、SocketChannel都是SelectableChannel的子类。

3.  Selector:可理解为Channel们的调度器,多个Channel可以注册到一个Selector对象,如前所述,Selector在Channel关注的事件发生的时候给予通知。比如,ServerSocketChannel在注册到Selector的时候表示,我关心accept操作,即关心哪些客户端试图与我建链;再比如,SocketChannel就关心read操作,看看什么时候会从Socket上读到数据。Selector提供的select方法是我们最为关注的,该方法是同步的,返回此时Selector收到的所有事件,返回形式是包含多个SelectionKey的Set。

4.  SelectionKey:包裹Selector和Channel关联关系的类。从SelectionKey中可以得到如下信息:Channel、Selector、操作类型(accept、read、write)等。
如何利用NIO解决我们的问题

1.  我们的基本设备:ServerSocketChannel*1、Selector*1、SocketChannel*N、SelectionKey*N、工作线程Worker*1(也可以是由少量线程组成的线程池,即Worker*M)。

2.  开启ServerSocketChannel,并打开某端口,等待客户端接入。

3.  将ServerSocketChannel注册到Selector上,表示自己关心accept操作,即关心哪些客户端来请求建链。

4.  Worker启动,while-true调用Selector的select方法。

5.  有客户端请求上来的时候,select方法会返回相关的SelectionKey对象,从中取得ServerSocketChannel对象,并调用其accept方法获得SocketChannel。

6.  把获得的SocketChannel注册到Selector上,同时保存起来,并表示自己关心read操作,即关心何时、从该Socket上读到什么数据。

7.  (此时,while-true调用Selector的select方法仍旧在马不停蹄地运行着)当有某个Socket有数据上来的时候,select方法会返回相关的SelectionKey对象,从中取得SocketChannel对象,调用read方法读出数据。


可以看出,利用NIO,一个工作线程可以完成所有Socket的数据读取,从根本上解决了上面的问题。当然,如果有需要,也可以设置线程池,或可进一步提高性能。

首先先分析下:为什么要nio套接字?
nio的主要作用就是用来解决速度差异的。举个例子:计算机处理的速度,和用户按键盘的速度。这两者的速度相差悬殊。如果按照经典的方法:一个用户设定一个线程,专门等待用户的输入,无形中就造成了严重的资源浪费:每一个线程都需要珍贵的cpu时间片,由于速度差异造成了在这个交互线程中的cpu 都用来等待。
nio套接字是怎么做到的?
其实,其中的思想很简单:轮询。一个线程轮询多个input;传统的方式是:有n个客户端就要有n个服务线程+一个监听线程,现在采取这种凡是,可以仅仅使用1个线程来代替n个服务线程以此来解决。

package com.cxz.io;   
  
import java.io.BufferedReader;   
import java.io.IOException;   
import java.io.InputStreamReader;   
import java.net.ServerSocket;   
import java.net.Socket;   
import java.util.HashMap;   
import java.util.Map;   
import java.util.Collections;   
  
public class IoEchoServer implements Runnable {   
  
    // ThreadLocal<Socket> localSocket = new ThreadLocal<Socket>();   
    Map<String, Socket> socketMap = Collections   
            .synchronizedMap(new HashMap<String, Socket>());   
  
    int threadCounter = 0;   
  
    synchronized private int getCounter() {   
        return threadCounter++;   
    }   
  
    public IoEchoServer() throws IOException {   
        ServerSocket server = new ServerSocket(1984);   
        while (true) {   
            Socket socket = server.accept();   
            // happened in the main thread.   
            // localSocket.set(socket);   
            String threadName = "---Thread" + getCounter() + "---";   
            socketMap.put(threadName, socket);   
            this.start(threadName);   
        }   
    }   
  
    /**  
     * @param args  
     * @throws IOException  
     */  
    public static void main(String[] args) throws IOException {   
        new IoEchoServer();   
    }   
  
    public void run() {   
        try {   
            Socket socket = socketMap.get(Thread.currentThread().getName());   
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));   
            //PrintWriter out = new PrintWriter(socket.getOutputStream());   
            String buffer = null;   
            while(!"END".equals(buffer)){   
                buffer = in.readLine();   
                System.out.println(buffer);   
            }   
            in.close();   
            socket.close();   
        } catch (IOException e) {   
            e.printStackTrace();   
        }   
    }   
  
    public void start(String threadName) {   
        new Thread(this, threadName).start();   
    }   
  
}  

下面这个例子采取了nio方式实现,虽然还是有阻塞部分,但是与上一个相比,效率已经大幅提高。仅仅阻塞到一个监听线程中。
package com.cxz.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;
import java.util.Set;

public class NioEchoServer {

	private static Selector roller = null;

	private static final int port = 8080;

	private static NioEchoServer instance = null;

	private ThreadLocal<StringBuffer> stringLocal = new ThreadLocal<StringBuffer>();

	private NioEchoServer() throws IOException {
		ServerSocketChannel serverChannel = ServerSocketChannel.open();
		serverChannel.socket().bind(new InetSocketAddress(port));
		serverChannel.configureBlocking(false);
		serverChannel.register(roller, SelectionKey.OP_ACCEPT);
	}

	public synchronized static NioEchoServer getInstance() throws IOException {
		if (instance == null) {
			roller = Selector.open();
			instance = new NioEchoServer();
		}
		return instance;
	}

	public void start() throws IOException {
		int keyAdded = 0;
		while ((keyAdded = roller.select()) > 0) {
			Set<SelectionKey> keySets = roller.selectedKeys();
			Iterator iter = keySets.iterator();
			while (iter.hasNext()) {
				SelectionKey key = (SelectionKey) iter.next();
				iter.remove();
				actionHandler(key);
			}
		}
	}

	public void actionHandler(SelectionKey key) throws IOException {
		if (key.isAcceptable()) {
			ServerSocketChannel serverChannel = (ServerSocketChannel) key
					.channel();
			SocketChannel socketChannel = serverChannel.accept();
			socketChannel.configureBlocking(false);
			socketChannel.register(roller, SelectionKey.OP_READ);
		} else if (key.isReadable()) {
			ByteBuffer buffer = ByteBuffer.allocate(16);
			SocketChannel socketChannel = (SocketChannel) key.channel();
			socketChannel.read(buffer);
			buffer.flip();
			String temp = decode(buffer);
			StringBuffer strBuffer = stringLocal.get();
			if (strBuffer == null) {
				strBuffer = new StringBuffer();
			}

			strBuffer.append(temp);

			if (temp.equals("\r\n")) {
				System.out.println(strBuffer.toString());
				strBuffer = null;
			}
			stringLocal.set(strBuffer);
		}
	}

	public String decode(ByteBuffer buffer) {
		Charset charset = null;
		CharsetDecoder decoder = null;
		CharBuffer charBuffer = null;
		try {
			charset = Charset.forName("UTF-8");
			decoder = charset.newDecoder();
			charBuffer = decoder.decode(buffer);
			return charBuffer.toString();
		} catch (Exception ex) {
			ex.printStackTrace();
			return "";
		}
	}

	public static void main(String[] args) {
		try {
			NioEchoServer.getInstance().start();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}


分享到:
评论
2 楼 zhuchao_ko 2013-08-07  
1 楼 liangzhu 2012-07-10  
红色字体部分是精华!归纳得很好.

相关推荐

    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...

    JavaNIO chm帮助文档

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

    Java NIO英文高清原版

    Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java平台中用于替代标准I/O(BIO)模型的一种新机制。NIO在Java 1.4版本引入,提供了更高效的数据处理和通道通信方式,特别适用于高并发、大数据...

    java NIO.zip

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

    java nio 包读取超大数据文件

    ### Java NIO 处理超大数据文件的知识点详解 #### 一、Java NIO简介 Java NIO(New IO)是Java平台上的新输入/输出流API,它提供了与传统IO(即Java IO)不同的数据处理方式。NIO在Java 1.4版本引入,并在后续版本...

    java NIO 视频教程

    Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。 Java NIO: Channels and Buffers(通道和缓冲区) 标准的IO基于字节流和字符流进行操作的,...

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

    Java NIO 深入探讨了 1.4 版的 I/O 新特性,并告诉您如何使用这些特性来极大地提升您所写的 Java 代码的执行效率。这本小册子就程序员所面临的有代表性的 I/O 问题作了详尽阐述,并讲解了 如何才能充分利用新的 I/O ...

    java NIO技巧及原理

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

    一个java NIO的例子

    Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java标准库提供的一种替代传统I/O模型的新技术。在传统的Java IO模型中,读写操作是阻塞的,即当调用read或write方法时,线程会等待数据准备好或...

    java NIO实例

    Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java从1.4版本开始引入的一种新的I/O模型,它为Java应用程序提供了更高效的数据传输方式。传统的Java I/O模型(BIO)在处理大量并发连接时效率较...

    java nio入门学习,两个pdf

    Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java标准库提供的一种替代传统I/O模型的新技术。在Java 1.4版本中引入,NIO提供了一种全新的I/O编程方式,使得Java开发者能够更高效地处理I/O操作...

    java nio 实现socket

    ### Java NIO 实现Socket通信详解 #### 一、NIO与传统IO的区别及优势 在探讨如何使用Java NIO实现Socket通信之前,我们需要先理解NIO(Non-blocking I/O,非阻塞I/O)与传统阻塞I/O之间的区别。 **传统阻塞I/O...

    Java NIO测试示例

    Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java从1.4版本开始引入的一种新的I/O模型,它为Java应用程序提供了更高效、灵活的I/O操作方式。NIO与传统的 Blocking I/O(阻塞I/O)模式相比,...

Global site tag (gtag.js) - Google Analytics