NIO是jdk1.4之后出现的API,称作新IO(NEW IO),也叫非阻塞型IO(NON-BLOCKING IO),区别于以前的IO可以提供多路非阻塞式高伸缩性网络IO,它为所有的java基本类型提供缓存支持(不包括boolean类型),ByteBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer,CharBuffer以及MappedByteBuffer(映射到文件的字节缓冲区)。

      在NIO中比较重要的三个概念是selector(选择器),channel(管道),buffer(缓冲区),它们之间的关系为selector管理channel(一个selector管理多个管道),channel与buffer交互。channel就像是IO中的stream流,数据的读取,写入都需要经过这个管道,但是跟流不一样的是:channel是双向的,可以读也可以写,而流是单向的不可复用的。输入流和输出流是分开的。在NIO中由事件触发,将管道以某一个事件(或者多个事件OP_READ,OP_WRITE,OP_ACCEPT,OP_CONNECT)注册到selector后,当管道中的中的事件准备好后,selector.select() 即可获取到准备好管道的数量,通过selector.selectedKeys()获取到所有准备好的管道,通过判断管道状态,进而获取到socketChannel进度读写链接操作....下面看一下NioServer和NioClient的小例子.....

首先编写服务端:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
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;
import java.util.Set;

public class NioServer {
    private static final Logger LOGGER = LoggerFactory.getLogger(NioServer.class);
    private final Selector selector;

    public NioServer(int port) throws IOException {
        this.selector = Selector.open();//根据电脑操作系统生成select选择器
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//生成服务器socket通道
        InetSocketAddress inetSocketAddress = new InetSocketAddress(InetAddress.getLocalHost(), port);
        serverSocketChannel.socket().bind(inetSocketAddress);//为服务器绑定地址和监听端口
        serverSocketChannel.configureBlocking(false);//将阻塞设置成false,如果不设置就会出现IllegalBlockingModeException()异常
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//将通道注册到选择器上,并制定监听状态....
    }

    public void listen() throws Exception {
        System.out.println("服务端启动成功.....");
        while (!Thread.interrupted()) {//采用轮询的方式...
            LOGGER.info("正在扫描服务器端准备好的selector.....");
            int selectCount = selector.select();
            LOGGER.info("服务器准备好的选择器:" + selectCount);
            if (selectCount != 0) {
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
            while (keyIterator.hasNext()) {
                SelectionKey selectionKey = keyIterator.next();
                keyIterator.remove();//将取出来的selectedKey删除掉,避免重复处理
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.write(ByteBuffer.wrap(new String("收到连接请求....").getBytes()));
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    LOGGER.info("服务端已发送数据....");
                } else if (selectionKey.isReadable()) {
                    //有读取的权限
                    readMsg(selectionKey);
                }
            }
            }
        }
    }

    public void readMsg(SelectionKey selectionKey) throws Exception {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        //创建缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //从管道中读取数据到buffer中
        socketChannel.read(byteBuffer);//将通道中的数据写出到byteBuffer中
        byte[] bytes = byteBuffer.array();
        String getMsg = new String(bytes).trim();
        System.out.println("服务器收到的消息是:" + getMsg);
        BufferedReader br  = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入应答数据:");
        byteBuffer.clear();//将缓冲区清空
        byteBuffer.flip();//现在来看这句代码可以注释掉
        Thread.sleep(5000);
        socketChannel.write(ByteBuffer.wrap(br.readLine().getBytes()));
        socketChannel.register(selector, SelectionKey.OP_READ);//设置通道监控的状态
        LOGGER.info("服务器已发送应答请求.....");
    }

    public static void main(String[] args) throws Exception {
        NioServer nioServer = new NioServer(8080);
        nioServer.listen();
    }
}

服务端的代码需要先写...然后再写客户端的代码

客户端代码:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
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;

public class NioClient {
    private static final Logger LOGGER = LoggerFactory.getLogger(NioClient.class);
    private final Selector selector;

    public NioClient(int port) throws Exception {
        this.selector = Selector.open();//根据操作系统创建选择器
        SocketChannel socketChannel = SocketChannel.open();//创建socketChannel通道
        socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));//连接到指定的服务器
        socketChannel.configureBlocking(false);//设置阻塞参数为false,不设置在register时会报IllegalBlockingModeException()异常
        socketChannel.register(selector, SelectionKey.OP_READ);
    }

    public void listen() throws Exception {
        System.out.println("正在建立连接.....");
        while (true) {
            LOGGER.info("正在扫描客户端准备好的selector.....");
            int selectCount = selector.select();
//            LOGGER.info("准备好的通道:"+selectCount);
            if (selectCount != 0) {
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
//                    LOGGER.info("begin doing......");
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if (selectionKey.isConnectable()) {
                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                        socketChannel.finishConnect();
                        socketChannel.configureBlocking(false);
                        LOGGER.info("客户端准备发送数据....");
                        socketChannel.write(ByteBuffer.wrap(new String("这是客户端发送的你好数据....").getBytes()));
//                        socketChannel.register(selector, SelectionKey.OP_READ);
                    } else if (selectionKey.isReadable()) {
                        readServerMsg(selectionKey);
                    }
                }
            }
        }
    }

    public void readServerMsg(SelectionKey selectionKey) throws Exception {
        while (selectionKey.isReadable()) {
            int selectedCount = selector.select();
            if (selectedCount != 0) {
                SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                socketChannel.configureBlocking(false);
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                socketChannel.read(byteBuffer);
                byte[] msgByte = byteBuffer.array();
                String msg = new String(msgByte).trim();
                if(!"".equals(msg)){//这个判断的主要原因是在客户端发送数据之后,会立刻从通道中读取数据,而此时客户端还未相应数据,所以用这个判断限制......有更好解决办法的同学可以留言互相讨论下
                    System.out.println("客户端收取的信息:" + msg);
                    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
                    System.out.println("请输入客户端应答信息:");
                    byteBuffer.clear();
                    byteBuffer.flip();
                    Thread.sleep(2000);
                    socketChannel.write(ByteBuffer.wrap(br.readLine().getBytes()));
                    socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);//设置通道需要监听的状态
                }else{
                    continue;
                }
            }
        }
    }

    public static void main(String[] args) throws Exception{
        NioClient nioClient = new NioClient(8080);
        nioClient.listen();
    }
}



根据测试这个代码有一点点小问题:
一:在客户端中如果没有  !"".equals(msg)的判断,每次客户端发送消息之后都会立刻都回一个空的字符串显示...这个问题还未整明白
二:客户端中通道监听的状态必须要为
socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);//设置通道需要监听的状态

如果只设置OP_READ的话,通道会一直阻塞,不会轮询selector.....


以上两个问题,希望有人可以帮我解答,谢谢。