NIO编程

来源:互联网 发布:放置江湖网络错误 编辑:程序博客网 时间:2024/06/06 19:21

1.NIO弥补了原来同步阻塞I/O的不足,它在标准java代码中提供了高速的,面向块的I/O
2.NIO的几个特性
缓冲区(buffer),通道(channel);多路复用器(selector)
3.一个多路复用器selector可以同时轮循多个Channel,JDK使用epoll()代替传统的select实现,因此它并没有最大连接句柄1024的限制,这个意味着只需要一个线程负责selector的轮询。就可以接入成千上完的客户端
以下是个netty权威指南中NIO实现通信的一个例子
1.服务端的serverTime

package com.afan.nio;public class TimeServer {    public static void main(String[] args){        int port = 8080;        if(args != null && args.length > 0){            try {                port = Integer.valueOf(args[0]);            } catch (Exception e) {                // TODO: handle exception            }        }        MultiplexerTimeServer timeServer = new MultiplexerTimeServer(port);        new Thread(timeServer,"NIO-MultiplexTimeServer-001").start();    }}

2.服务端处理类

package com.afan.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.Date;import java.util.Iterator;import java.util.Set;public class MultiplexerTimeServer implements Runnable{    private Selector selector;    private ServerSocketChannel servChannel;    private volatile boolean stop;    /**     * 初始化多路复用器,绑定监听端口     */    public MultiplexerTimeServer(int port) {        try {            selector = Selector.open();            servChannel = ServerSocketChannel.open();            servChannel.configureBlocking(false);            servChannel.socket().bind(new InetSocketAddress(port));            servChannel.register(selector, SelectionKey.OP_ACCEPT);            System.out.println("the time server is start in port : "+ port);        } catch (Exception e) {            e.printStackTrace();            System.exit(1);        }    }    public void stop(){        this.stop = true;    }    @Override    public void run() {        while(!stop){            try {                selector.select(1000);                Set<SelectionKey> selectkeys = selector.selectedKeys();                Iterator<SelectionKey> it = selectkeys.iterator();                SelectionKey key = null;                while(it.hasNext()){                    key = it.next();                    it.remove();                    try {                        handleInput(key);                    } catch (Exception e) {                        if(key != null){                            key.cancel();                            if(key.channel() != null){                                key.channel().close();                            }                        }                    }                }            } catch (Exception e) {                e.printStackTrace();            }        }        //多路复用器关闭后,所有注册在上面的channel和pipe等资源都会被自动        //去注册,所有不需要重复释放资源        if(selector != null){            try {                selector.close();            } catch (Exception e) {                e.printStackTrace();            }        }    }    private void handleInput(SelectionKey key) throws IOException{        if(key.isValid()){            //处理新接入的请求消息            if(key.isAcceptable()){                //accept the new connection                ServerSocketChannel ssc = (ServerSocketChannel)key.channel();                SocketChannel sc = ssc.accept();                sc.configureBlocking(false);                //add the new connection to the selector                sc.register(selector,SelectionKey.OP_READ);            }            if(key.isReadable()){                //read the data                SocketChannel sc = (SocketChannel)key.channel();                ByteBuffer readBuffer = ByteBuffer.allocate(1024);                int readBytes = sc.read(readBuffer);                if(readBytes > 0){                    readBuffer.flip();                    byte[] bytes = new byte[readBuffer.remaining()];                    readBuffer.get(bytes);                    String body = new String(bytes,"UTF-8");                    System.out.println("the time server receive order :" + body);                    String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)                            ?new Date(System.currentTimeMillis()).toString():"BAD ORDER";                    doWrite(sc,currentTime);                }else if(readBytes < 0){                    //对端链路关闭                    key.cancel();                    sc.close();                }            }        }    }    private void doWrite(SocketChannel channel,String response) throws IOException{        if(response != null && response.trim().length() > 0){            byte[] bytes = response.getBytes();            ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);            writeBuffer.put(bytes);            writeBuffer.flip();            channel.write(writeBuffer);        }    }}

3.客户端接入的clientITme

package com.afan.nio;public class TimeClient {    public static void main(String[] args){        int port = 8080;        if(args != null && args.length > 0){            try{                port = Integer.valueOf(args[0]);            }catch(Exception e){            }        }        new Thread(new TimeClientHandle("127.0.0.1",port),"TimeClient-001").start();    }}

4.客户端处理类handle

package com.afan.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;import java.util.Set;public class TimeClientHandle implements Runnable{    private String host;    private int port;    private Selector selector;    private SocketChannel socketChannel;    private volatile boolean stop;    public TimeClientHandle(String host,int port) {        this.host = host == null ? "127.0.0.1" : host;        this.port = port;        try {            selector = Selector.open();            socketChannel = SocketChannel.open();            socketChannel.configureBlocking(false);        } catch (Exception e) {            e.printStackTrace();            System.exit(1);        }    }    @Override    public void run() {        try {            doConnect();        } catch (Exception e) {            e.printStackTrace();            System.exit(1);        }        while(!stop){            try {                selector.select(1000);                Set<SelectionKey> selectedKeys = selector.selectedKeys();                Iterator<SelectionKey> it = selectedKeys.iterator();                SelectionKey key = null;                while(it.hasNext()){                    key = it.next();                    it.remove();                    try {                        handleInput(key);                    } catch (Exception e) {                        if(key != null){                            key.cancel();                            if(key.channel() != null){                                key.channel().close();                            }                        }                    }                }            } catch (Exception e) {                e.printStackTrace();                System.exit(1);            }        }        //多路复用器关闭后,所有注册在上面的channel和pipe等资源        //都会被自动去注册并关闭,所不需要重复释放资源        if(selector != null){            try {                selector.close();            } catch (Exception e) {                e.printStackTrace();            }        }    }    private void handleInput(SelectionKey key) throws IOException{        if(key.isValid()){            //判断是否连接成功            SocketChannel sc = (SocketChannel)key.channel();            if(key.isConnectable()){                if(sc.finishConnect()){                    sc.register(selector, SelectionKey.OP_READ);                    doWrite(sc);                }else{                    System.exit(1);                }            }            if(key.isReadable()){                ByteBuffer readBuffer = ByteBuffer.allocate(1024);                int readBytes = sc.read(readBuffer);                if(readBytes > 0){                    readBuffer.flip();                    byte[] bytes = new byte[readBuffer.remaining()];                    readBuffer.get(bytes);                    String body = new String(bytes,"UTF-8");                    System.out.println("NOW IS" + body);                    this.stop = true;                }else if(readBytes < 0){                    //对端链路关闭                    key.cancel();                    sc.close();                }            }        }    }    private void doConnect() throws IOException{        //如果直接连接成功,则注册到多路复用器上,发送请求消息,读应答        if(socketChannel.connect(new InetSocketAddress(port))){            socketChannel.register(selector,SelectionKey.OP_READ);            doWrite(socketChannel);        }else{            socketChannel.register(selector,SelectionKey.OP_CONNECT);        }    }    private void doWrite(SocketChannel sc) throws IOException{        byte[] req = "QUERY TIME ORDER".getBytes();        ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);        writeBuffer.put(req);        writeBuffer.flip();        sc.write(writeBuffer);        if(!writeBuffer.hasRemaining()){            System.out.println("Send order 2 server succeed.");        }    }}

总结:

NIO编程的优点:  1.客户端发起的连接操作时异步的,可以通过在多路复用器注册OP_CONNECT等待后续结果,不需要像以前的客户端那样被同步阻塞  2.SocketChannel的读写操作都是异步的,如果没有可读写的数据它不会同步等待,直接返回,这样I/O通信线程就可以处理其他的链路,不需要同步等待这个链路可用  3.线程模型的优化(也就是使用了epoll)