非阻塞IO

来源:互联网 发布:potplayer mac 编辑:程序博客网 时间:2024/05/21 19:35

本篇重点是非阻塞IO,即java1.4提供的nio包,顺带记录一些其他信息。

在Socket技术中我们利连接时间的付出换来数据传输的可靠性。Java提供一些控制连接时间的技术,以增强其传输效率。例如超时和中断。

为防止无限制等待或为控制等待时间,可调用Socket.setSoTimeout(millies)设置超时时间。另外,在应用构造器Socket(address, port)和ServerSocket(port)时,JVM将先建立连接而后创建socket对象,无参构造器Socket()及ServerSocket()则无需建立连接,然后调用发表在JDK1.4中的connect()方法,可以改善因连接而造成的延误,并能指定连接时间。

复制代码
try {    Socket clientSocket = new Socket(); //无参构造函数    //其他代码    ...    clientSocket.connect(address, port, timeout);    ...} catch (SocketException e) {    e.printStackTree();}
复制代码

超时控制有其局限性,在数据发送以及接受过程中,Socket对象无响应,不回答或延误读写的情况,不能控制。此时我们希望中断这个读写操作,可中断的Socket技术,包括在java.nio的API类SocketChannel中。利用SocketChannel创建的对象本身就具有可中断的功能,将抛出InterruptedException,例:

复制代码
...try{    InetSocketAddress addr = new InetSocketAddress(IPaddress, port);    SocketChannel  channel = SocketChannel .open(addr);    //将通道应用到Scanner    Scanner inData = new Scanner(channel);    while(true) {        if(inData.hasNextLine()) {            //得到通道中的数据            String line = inData.nextLine();            ...        } else {            Thread.sleep(500);        }    }} catch (InterruptedException e) {    e.printStackTrace();} catch (IOException e) {  e.printStackTrace();}
复制代码

    具有可中断功能的输出操作如下:

复制代码
...try{    InetSocketAddress addr = new InetSocketAddress(IPaddress, port);    SocketChannel  channel = SocketChannel .open(addr);    //将通道包装在输出流对象中   OutputStream outStream = Channels.newOutputStream(channel);    //刷新方式输出通道中的数据    PrintWriter outData = new PrintWriter(outStream, true);    //向服务器发送请求信息    outData.println(requestMessage);    ...} catch (InterruptedException e) {    e.printStackTrace();} catch (IOException e) {  e.printStackTrace();}...
复制代码

针对网络编程中数据IO,java.nio与java.io相比,应用java.nio包中的API类可提高数据输入、输出的执行速度;纯化Java代码,在传统java.io中,涉及数据输入、输出的底层操作,例如缓冲器的填充和刷新,JVM必须装入本机操作系统的有关代码,完成其IO操作。在java.nio中,涉及缓冲器的操作完全交给操作系统自行,从而提高了Java程序的纯度。
数据流与数据块的比较:面向数据流的IO按照一个个有序的字节处理数据,一次只处理一个字节;面向数据块的IO一次处理整个数据块。这数据块由缓冲器对象类定义,最大数据块容量科大64KB。几乎每种基本数据类型都有其对应的缓冲器类,用来包装不同类型的数据块。数据块的优点是操作速度快;数据流的优点是易于控制和过滤传输的数据,易于编写代码。

通道Channel可看做是数据块进行输入、输出操作的传输带,具有方向性。在网络编程中经常用到的是实现了Channel这个接口的SocketChannel和ServerSocketChannel,它们的一个重要特点是可以运用选择器Selector支持非阻塞输入、输出。

缓冲Buffer是运用通道技术传输数据块的容器。值得注意的是,在应用通道进行数据传输时,必须根据数据类型,来创建匹配的缓冲对象。在java.nio中,Java提供了如下类型的缓冲类:ByteBuffer、CharBufferShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。以上每个缓冲类都是Buffer类的子类。关于缓冲Buffer,缓冲是一块连续的内存区,是nio数据读写的中转地。缓冲有两种工作模式——写模式和读模式,缓冲主要由position,limit,capacity三个变量控制读写过程,在写模式和读模式下缓冲的内部结构如图:

其中capacity,limit和position的含义分别如下表:

参数

写模式

读模式

position

当前写入的单位数据数量。

当前读取的单位数据位置。

limit

代表最多能写多少单位数据和容量是一样的。

代表最多能读多少单位数据,和之前写入的单位数据量一致。

capacity

buffer容量

buffer容量

 

 

 

 

 

  

这三个属性的大小关系为capacity>=limit>=position>=0。缓冲类常见方法:flip()——写模式转换成读模式;rewind()——将position重置为0,一般用户重复读;clear()——清空buffer,准备再次被写入(position变为0,limit变成capacity);compact()——将未读取的数据拷贝到buffer头部;mark(),reset()——mark标记一个位置,reset可重置到该位置。

在利用通道进行数据块块传输中,经常利用java.nio.charset包中提供的Charset类进行数据块的编码和解码操作,以便提高数据块的传输效率和可靠性。除系统预设的Unicode字符集外,Charset还支持如下字符编码定义:US ASCII,ISO-8859-1,UTF-8,UTF-16。Charset是一个抽象类,在应用时必须调用其forName()方法,来返回一个指定字符集编码的对象。并且利用Charset类的encode()以及decode()方法尽心编码和解码操作:Charset forName(String charsetName)——按指定字符集名返回一个Charset对象,ByteBuffer encode(CharBuffer buffer)——对执行CharBuffer对象编码,并返回编码后的ByteBuffer对象,CharBuffer decode(ByteBuffer buffer)——对指定ByteBuffer对象解码,并返回解码后的CharBuffer对象。

阻塞式网络IO的特点:多个线程处理多个连接,每个线程拥有自己的栈空间并占用一些CPU时间,每个线程遇到外部未准备好的时候都会阻塞(accept等待连接到来,会阻塞;recieve/read等待数据,会阻塞)。阻塞会带来大量的线程上下文切换,且大部分的切换都以阻塞告终。

何为非阻塞?

下面有个隐喻:

一辆从A开往B的公共汽车上,路上有很多点可能会有人下车。司机不知道哪些点会有哪些人会下车,对于需要下车的人,如何处理更好?

1.司机过程中定时询问每个乘客是否到达目的地,若有人说到了,那么司机停车,乘客下车。(类似阻塞式)

2.每个人告诉售票员自己的目的地,然后睡觉,司机只和售票员交互,到了某个点由售票员通知乘客下车。 (类似非阻塞)

很显然,每个人要到达某个目的地可以认为是一个线程,司机可以认为是CPU。在阻塞式里面,每个线程需要不断的轮询,上下文切换,以达到找到目的地的结果。而在非阻塞方式里,每个乘客(线程)都在睡觉(休眠),只在真正外部环境准备好了才唤醒,这样的唤醒肯定不会阻塞。

非阻塞的原理

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

以下是异步IO的结构:

一个简单的通道的例子(服务器端程序)

复制代码
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.nio.charset.Charset;import java.util.Iterator;import java.util.Set;public class SelectoServer {    /**     * @param args     * @throws IOException      */    public static void main(String[] args) throws IOException {        // TODO Auto-generated method stub        /* 为第一个用户创建连接 */        //指定端口地址        InetSocketAddress address1 = new InetSocketAddress(10001);        //创建ServerSocketChannel        ServerSocketChannel channel1 = ServerSocketChannel.open();        //设置非阻塞IO        channel1.configureBlocking(false);        //绑定端口        channel1.socket().bind(address1);        /* 为第二个用户创建连接 */        InetSocketAddress address2 = new InetSocketAddress(10002);        ServerSocketChannel channel2 = ServerSocketChannel.open();        channel2.configureBlocking(false);        channel2.socket().bind(address2);        /* 为第三个用户创建连接 */        InetSocketAddress address3 = new InetSocketAddress(10003);        ServerSocketChannel channel3 = ServerSocketChannel.open();        channel3.configureBlocking(false);        channel3.socket().bind(address3);                //创建选择器        Selector selector = Selector.open();        //指定连接方式        channel1.register(selector, SelectionKey.OP_ACCEPT);        channel2.register(selector, SelectionKey.OP_ACCEPT);        channel3.register(selector, SelectionKey.OP_ACCEPT);                //如果发生任何事件        while (selector.select() > 0) {            //得到事件集合            Set keys = selector.selectedKeys();            Iterator iterator = keys.iterator();            while (iterator.hasNext()) {                //得到事件源                SelectionKey key = (SelectionKey) iterator.next();                //得到通道                ServerSocketChannel channel = (ServerSocketChannel) key.channel();                //接收通道连接请求                SocketChannel socketChannel = channel.accept();                //调用自定义的请求处理方法                handleClient(socketChannel);                //删除处理完的事件                iterator.remove();            }        }    }    private static void handleClient(SocketChannel socketChannel) {        // TODO Auto-generated method stub        int port = socketChannel.socket().getLocalPort();        System.out.println("Listen to the client address: "+socketChannel.socket().getInetAddress());        System.out.println("Port: "+port);        switch (port) {        case 10001:            writeClient(socketChannel, "服务器响应。使用端口为:"+port);            break;        case 10002:            writeClient(socketChannel, "服务器响应。使用端口为:"+port);            break;        case 10003:            writeClient(socketChannel, "服务器响应。使用端口为:"+port);            break;        default:            writeClient(socketChannel, "服务器响应,非定义端口"+port);            break;        }    }    private static void writeClient(SocketChannel socketChannel, String string) {        // TODO Auto-generated method stub        //指定字符集        Charset charset = Charset.forName("UTF-8");        //自定义缓冲        ByteBuffer buffer = ByteBuffer.allocate(256);        //        buffer = charset.encode(string);        try {            socketChannel.write(buffer);        } catch (IOException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }}
复制代码

一个简单的通道的例子(客户端程序)

复制代码
import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.CharBuffer;import java.nio.channels.SocketChannel;import java.nio.charset.Charset;public class SelectorClient1 {    /**     * @param args     */    public static void main(String[] args) {        // TODO Auto-generated method stub        try {            InetSocketAddress address = new InetSocketAddress("localhost", 10001);            Charset charset = Charset.forName("UTF-8");            SocketChannel channel = SocketChannel.open(address);            System.out.println("address: " + address);            ByteBuffer buffer = ByteBuffer.allocate(256);            channel.read(buffer);            buffer.flip();            CharBuffer charBuffer = charset.decode(buffer);            System.out.println(charBuffer);            channel.close();        } catch (Exception e) {            // TODO: handle exception            e.printStackTrace();        }    }}
复制代码

三个客户端程序基本相同,这里只列出第一个用户端代码。

其他参考资料:http://www.iteye.com/topic/834447——JAVA NIO 简介

原创粉丝点击