Java NIO

Java NIO

IO模型

IO模型: http://blog.csdn.net/lilongjiu/article/details/78147070

IO

传统的IO一般是采用多线程 + 阻塞 IO 达到类似 I/O 复用模型效果。但是这种模式需要为每个客户端Socket创建一个新的Thread,每个线程都占用一定的资源,分配过多的线程会导致资源的浪费。再有,过多的线程会导致更频繁的上下文切换。

public class IOTest {

    public static void main(String[] args) throws Exception {

        //创建一个服务端Socket,绑定到8899端口号
        ServerSocket serverSocket = new ServerSocket(8899);

        //创建一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();

        Socket clientSoket = null;

        while (true) {

            //接收客户端连接
            clientSoket = serverSocket.accept();

            //处理客户端请求
            executorService.submit(new Handler(clientSoket));
        }
    }

    static class Handler implements Runnable {

        private Socket socket;

        public Handler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {

            try {
                process(socket);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        private void process(Socket socket) throws Exception {

            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter writer = new PrintWriter(socket.getOutputStream());

            String line = null;

            while (null != (line = reader.readLine())) {

                System.out.println("receive message from: " + socket.getRemoteSocketAddress() + ", message is: " + line);

                writer.println("response message from: " + socket.getLocalAddress() + ", message is: " + line);

                writer.flush();
            }
        }


    }
}

IO 与 NIO 区别

IO
  • Java IO 核心概念是Stream
  • 面向流编程
  • 一个流要么是输入流,要么是输出流,不可能同时既是输入流又是输出流
NIO
  • Java NIO 核心概念是Buffer,Channel,Selector,SelectionKeys
  • 面向缓冲编程
  • Channel既可以读也可以写

Buffer

Buffer是一个存储原生数据类型的容器,一个Buffer是线性的、有限的特定原生数据类型元素序列,它是非线程安全的。

Java NIO - Buffer: http://blog.csdn.net/lilongjiu/article/details/78145862

Channel

Channel 是 I/O 操作的连接,表示与实体的开放连接,如:硬件设备,文件,网络套接字,或者应用程序组件,这些实体可以执行一个或多个 I/O 读写操作。一个Channel要么处于打开状态,要么出与关闭状态。一个Channel一旦创建就处于打开状态,一旦关闭就会一直处于关闭状态。

下面是一个FileChannel的例子,Channel把数据写到Buffer

public class FileChannelTest {

    public static void main(String[] args) throws Exception {

        FileInputStream fis = new FileInputStream("channel.txt");

        //根据FileInputStream 创建文件通道
        FileChannel channel = fis.getChannel();

        //allocate 静态方法创建buffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        //Channel把数据写到Buffer
        int readSize = channel.read(buffer);

        while (readSize != -1) {

            //一定要调用flip方法
            buffer.flip();

            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }

            buffer.clear();
            readSize = channel.read(buffer);
        }
    }
}

这个例子Channel从Buffer读数据

public class FileChannelTest2 {

    public static void main(String[] args) throws Exception {

        FileOutputStream fos = new FileOutputStream("channel.txt");
        FileChannel channel = fos.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        buffer.put("hello file channel".getBytes());

        buffer.flip();

        while (buffer.hasRemaining()) {
            //Channel从Buffer读数据
            channel.write(buffer);
        }

        channel.close();
    }
}

从上面的两个例子可以看出,Channel是面向Buffer的,读写数据都是与Buffer交互。

Selector

Selector是一个SelectableChannel对象的多路复用器,SelectableChannel可以注册到Selector上,通过SelectionKey来表示注册到Selector上的Channel。一个Selector维护了三个SelectionKey集合:

  • all-keys:当前所有向Selector注册的Channel对应的SelectionKey的集合
  • selected-keys :被Selector检测到发生I/O事件的Channel对应的SelectionKey的集合,是all-keys的子集
  • cancelled-keys: 已经被取消的SelectionKey的集合,是all-keys的子集

阻塞IO模型中,一般是一个客户端Socket对应一个Thread,如图:

java-io-multithread
NIO使用Channel,Channel需要注册到Selector上,Selector可以监听到注册其上的Channel的所有I/O事件,然后使用一个线程去处理

java-nio-thread.png

Selector上可以注册多个Channel,并监听Channel上的事件

SelectionKey 用来表示Selector上发生事件的Channel

下面是一个比较典型的例子,一个线程就可以处理多个连接:

public class SelectorTest {

    public static void main(String[] args) throws IOException {

        //通过open方法获取Selector
        Selector selector = Selector.open();

        //通过open获取ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(8899));

        //一定要配置为非阻塞
        serverSocketChannel.configureBlocking(false);

        //将Channel注册到Selector上,监听连接请求
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            //这个方法是阻塞的,当注册到Selector上的Channel有I/O事件发生,则返回
            selector.select();

            //所有发生I/O事件的Channel对应的SelectionKey,通过SelectionKey可以获取发生I/O事件的Channel
            Set<SelectionKey> selectionKeySet = selector.selectedKeys();

            Iterator<SelectionKey> iter = selectionKeySet.iterator();

            SelectionKey selectionKey = null;

            while (iter.hasNext()) {
                selectionKey = iter.next();

                ServerSocketChannel server = null;
                SocketChannel client = null;

                //客户端连接请求
                if (selectionKey.isAcceptable()) {
                    //通过SelectionKey获取与之关联的Channel,我们只将ServerSocketChannel到Selector监听连接事件,所以这里一定是ServerSocketChannel
                    server = (ServerSocketChannel) selectionKey.channel();
                    client = server.accept();

                    //这里同样要设置成非阻塞
                    client.configureBlocking(false);

                    //客户端的SocketChannel同样需要注册到Selector上,并监听读事件
                    client.register(selector,SelectionKey.OP_READ);

                } else if (selectionKey.isReadable()) {

                    //因为我们只将SocketChannel注册到Selector监听读事件,所以这里一定是SocketChannel
                    client = (SocketChannel) selectionKey.channel();

                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);

                    //将客户端的数据读取到ByteBuffer,简单起见,这里最多只能读1024字节
                    int readCount = client.read(readBuffer);

                    if(readCount < 0){
                        return;
                    }

                    //读写转换,一定要调用flip方法
                    readBuffer.flip();

                    //从客户端读取的数据
                    Charset charset = Charset.forName("UTF-8");
                    String receiveMessage = String.valueOf(charset.decode(readBuffer).array());

                    ByteBuffer writeBuffer = ByteBuffer.allocate(2048);
                    writeBuffer.put(("From server: " +  receiveMessage).getBytes());

                    //读写转换,一定要调用flip方法
                    writeBuffer.flip();

                    //向客户端写数据
                    client.write(writeBuffer);
                }

                //一定要将本次的selectionKey清除
                iter.remove();
            }
        }
    }
}

https://docs.oracle.com/javase/8/docs/api/
http://tutorials.jenkov.com/java-nio/index.html
Netty实战 https://item.jd.com/12070975.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值