java网络编程-NIO

一、概述

Java NIO(New IO)是一个可替代Java IO API(从Java1.4开始),JAVA NIO提供了与标准IO不同的工作方式。

Java NIO:Channels and Buffers(通道和缓冲区)

标准的IO基于字节流或者字符流进行操作,而NIO基于通道和缓冲区进行操作,数据是总是从通道读取的缓冲区中,或者从缓冲区写入到通道中。

Java NIO:Non-blocking IO(非阻塞IO):Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情,当数据被写入缓冲区时,线程可以继续处理它,从缓冲区写入通道也类似。

Java NIO:Selectors(选择器):Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。

1.1、NIO SelectionKey中定义的4种事件

  • SelectionKey.OP_ACCEPT —— 接收连接继续事件,表示服务器监听到了客户连接,服务器可以接收这个连接了
  • SelectionKey.OP_CONNECT —— 连接就绪事件,表示客户与服务器的连接已经建立成功
  • SelectionKey.OP_READ —— 读就绪事件,表示通道中已经有了可读的数据,可以执行读操作了(通道目前有数据,可以进行读操作了)
  • SelectionKey.OP_WRITE —— 写就绪事件,表示已经可以向通道写数据了(通道目前可以用于写操作)

 这里 注意,下面两种,SelectionKey.OP_READ ,SelectionKey.OP_WRITE ,

1.当向通道中注册SelectionKey.OP_READ事件后,如果客户端有向缓存中write数据,下次轮询时,则会 isReadable()=true;

2.当向通道中注册SelectionKey.OP_WRITE事件后,这时你会发现当前轮询线程中isWritable()一直为ture,如果不设置为其他事件

1.2、与传统IO区别

IO

NIO

面向流

面向缓冲区

阻塞IO

非阻塞IO

选择器

 

 

 

 

 

 

 

 

1.3、ByteBuffer

SocketChannel.read(ByteBuffer dst)SocketChannel.write(ByteBuffer src)的方法中的参数则都变为了java.nio.ByteBuffer,该类型就是JavaNIO对byte数组的一种封装

ByteBuffer包含几个基本的属性:

  • position:当前的下标位置,表示进行下一个读写操作时的起始位置;
  • limit:结束标记下标,表示进行下一个读写操作时的(最大)结束位置;
  • capacity:该ByteBuffer容量;
  • mark: 自定义的标记位置;

ByteBuffer的基本用法就是:
初始化(allocate)–> 写入数据(read / put)–> 转换为写出模式(flip)–> 写出数据(get)–> 转换为写入模式(compact)–> 写入数据(read / put)…

public static void main(String args[]) throws FileNotFoundException {
 
        System.out.println("----------Test allocate--------");
        System.out.println("before alocate:"
                + Runtime.getRuntime().freeMemory());
        
        // 如果分配的内存过小,调用Runtime.getRuntime().freeMemory()大小不会变化?
        // 要超过多少内存大小JVM才能感觉到?
        ByteBuffer buffer = ByteBuffer.allocate(102400);
        System.out.println("buffer = " + buffer);
        
        System.out.println("after alocate:"
                + Runtime.getRuntime().freeMemory());
        
        // 这部分直接用的系统内存,所以对JVM的内存没有影响
        ByteBuffer directBuffer = ByteBuffer.allocateDirect(102400);
        System.out.println("directBuffer = " + directBuffer);
        System.out.println("after direct alocate:"
                + Runtime.getRuntime().freeMemory());
        
        System.out.println("----------Test wrap--------");
        byte[] bytes = new byte[32];
        buffer = ByteBuffer.wrap(bytes);
        System.out.println(buffer);
        
        buffer = ByteBuffer.wrap(bytes, 10, 10);
        System.out.println(buffer);    
    }

 

 

public static void main(String args[]){
 
        System.out.println("--------Test reset----------");
        buffer.clear();
        buffer.position(5);
        buffer.mark();
        buffer.position(10);
        System.out.println("before reset:" + buffer);
        buffer.reset();
        System.out.println("after reset:" + buffer);
 
        System.out.println("--------Test rewind--------");
        buffer.clear();
        buffer.position(10);
        buffer.limit(15);
        System.out.println("before rewind:" + buffer);
        buffer.rewind();
        System.out.println("before rewind:" + buffer);
 
        System.out.println("--------Test compact--------");
        buffer.clear();
        buffer.put("abcd".getBytes());
        System.out.println("before compact:" + buffer);
        System.out.println(new String(buffer.array()));
        buffer.flip();
        System.out.println("after flip:" + buffer);
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        System.out.println("after three gets:" + buffer);
        System.out.println("\t" + new String(buffer.array()));
        buffer.compact();
        System.out.println("after compact:" + buffer);
        System.out.println("\t" + new String(buffer.array()));
 
        System.out.println("------Test get-------------");
        buffer = ByteBuffer.allocate(32);
        buffer.put((byte) 'a').put((byte) 'b').put((byte) 'c').put((byte) 'd')
                .put((byte) 'e').put((byte) 'f');
        System.out.println("before flip()" + buffer);
        // 转换为读取模式
        buffer.flip();
        System.out.println("before get():" + buffer);
        System.out.println((char) buffer.get());
        System.out.println("after get():" + buffer);
        // get(index)不影响position的值
        System.out.println((char) buffer.get(2));
        System.out.println("after get(index):" + buffer);
        byte[] dst = new byte[10];
        buffer.get(dst, 0, 2);
        System.out.println("after get(dst, 0, 2):" + buffer);
        System.out.println("\t dst:" + new String(dst));
        System.out.println("buffer now is:" + buffer);
        System.out.println("\t" + new String(buffer.array()));
 
        System.out.println("--------Test put-------");
        ByteBuffer bb = ByteBuffer.allocate(32);
        System.out.println("before put(byte):" + bb);
        System.out.println("after put(byte):" + bb.put((byte) 'z'));
        System.out.println("\t" + bb.put(2, (byte) 'c'));
        // put(2,(byte) 'c')不改变position的位置
        System.out.println("after put(2,(byte) 'c'):" + bb);
        System.out.println("\t" + new String(bb.array()));
        // 这里的buffer是 abcdef[pos=3 lim=6 cap=32]
        bb.put(buffer);
        System.out.println("after put(buffer):" + bb);
        System.out.println("\t" + new String(bb.array()));

}

1.4直接缓冲区和间接缓冲区

非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中,当从物理磁盘读取数据的时候,先读取到物理空间(其实还是在设备上的),再copy到jvm空间,然后才从jvm空间里进行读取,由于这里涉及到一个copy的过程,所以效率相比直接缓冲区较低。IO缓冲区属于非直接,大多数缓冲区都是非直接缓冲区

直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率。非常占内存,安全性相比非直接缓冲区较低

 

二、样例

public static void main(String[] args) throws IOException {  
    System.out.println("Listening for connection on port " + DEFAULT_PORT);  
  
    Selector selector = Selector.open();  
    initServer(selector);  
    //开始监听
    while (true) {  
        selector.select();  //监控所有注册的 channel,当没有可用的IO 操作时会阻塞,有可用的IO 操作时往下执行
  
        for (Iterator itor = selector.selectedKeys().iterator(); itor.hasNext();) { //可进行 IO 操作的 channel的集合:selectedKeys
            SelectionKey key = (SelectionKey) itor.next();  
            itor.remove();  
            try {  
                if (key.isAcceptable()) {  
                    ServerSocketChannel server = (ServerSocketChannel) key.channel(); //获取Accept操作的Channel
                    SocketChannel client = server.accept();   //客户端的SocketChannel 
                    System.out.println("Accepted connection from " + client);  
                    client.configureBlocking(false);  //客户端配置为非阻塞
                    SelectionKey clientKey = client.register(selector, SelectionKey.OP_READ);  //把客户端套接字通道 注册到selector,并注明为OP_READ操作
                    ByteBuffer buffer = ByteBuffer.allocate(100);  
                    clientKey.attach(buffer); ////客户端SelectionKey附上一个ByteBuffer
                } else if (key.isReadable()) {  
                    SocketChannel client = (SocketChannel) key.channel();  //获取客户端的SocketChannel
                    ByteBuffer buffer = (ByteBuffer) key.attachment();  buffer .clear();
                    int n = client.read(buffer);  //将客户端套接字通道的数据 读取到缓冲区
                    if (n > 0) {  
                        receiveText = new String(buffer.array(),0,n);//接受到的数据
                        
                        client.register(selector, SelectionKey.OP_WRITE);     // switch to OP_WRITE  
                    }  
                } else if (key.isWritable()) {  
                    System.out.println("is writable...");  
                    SocketChannel client = (SocketChannel) key.channel();   //获取客户端的SocketChannel
                    ByteBuffer buffer = (ByteBuffer) key.attachment();  buffer.clear();buffer.put(sendText.getBytes()); //sendText :"message from server"
                    buffer.flip(); client.write(buffer);  //输出到通道
                    if (buffer.remaining() == 0) {  // write finished, switch to OP_READ  
                        client.register(selector, SelectionKey.OP_READ);  
                        
                    }  
                }  
            } catch (IOException e) {  
                key.cancel();  
                try { key.channel().close(); } catch (IOException ioe) { }  
            }  
        }  
    }  
}  

 

posted @ 2021-06-16 15:02  枫之羽&  阅读(331)  评论(0编辑  收藏  举报