`
xuyuanshuaaa
  • 浏览: 387715 次
  • 性别: 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 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据...

    Java NIO英文高清原版

    Java NIO英文高清原版

    java nio中文版

    java NIO是 java New IO 的简称,在 jdk1.4 里提供的新 api 。 Sun 官方标榜的特性如下: – 为所有的原始类型提供 (Buffer) 缓存支持。 – 字符集编码解码解决方案。 – Channel :一个新的原始 I/O 抽象。 – 支持...

    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 中文版

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

    java nio 实现socket

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

    Java NIO 英文文字版

    Many serious Java programmers, especially enterprise Java programmers, consider the new I/O API--called NIO for New Input/Output--the most important feature in the 1.4 version of the Java 2 Standard ...

    Java Nio selector例程

    java侧起server(NioUdpServer1.java),基于Java Nio的selector 阻塞等候,一个android app(NioUdpClient1文件夹)和一个java程序(UI.java)作为两个client分别向该server发数据,server收到后分别打印收到的消息...

    java NIO技巧及原理

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

    Java NIO

    Java NIO 书籍,讲述new io特性 裴小星 译

    java nio 读文件

    java nio 读文件,java nio 读文件

    java nio proraming pdf

    java.nio (NIO stands for non-blocking I/O) is a collection of Java programming language APIs that offer features for intensive I/O operations. It was introduced with the J2SE 1.4 release of Java by ...

    java NIO.zip

    java NIO.zip

    JAVA NIO 学习资料

    JAVA NIO学习资料JAVA NIO学习资料

    Java NIO测试示例

    Java NIO测试示例

    java nio入门学习,两个pdf

    java nio入门学习,两个pdfjava nio入门学习,两个pdf

    Java NIO.pdf

    java nio编程 非阻塞模式的通信 电子书 带目录标签

Global site tag (gtag.js) - Google Analytics