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) { } } } } }