NIO
概念
我们传统上处理socket IO时, 需要给每一个连接创建一个线程, 当并发的连接数量非常大的时候, 线程所占用的栈内存和CPU线程切换的开销将非常大. 如果使用NIO, 那么不需要为每一个连接创建一个单独的线程, 可以用一个含有有限数量线程的线程池, 甚至是一个线程来为任意数量的连接提供服务. 由于线程数量小于连接数量, 那么就决定了IO的工作模式不能是阻塞, 否则有些连接就会被饿死.
原理
- 由一个专门的线程来处理所有的IO事件, 并负责转发.
- 事件驱动机制: 事件到的时候触发, 而不是同步的去监视事件.
- 线程通讯: 线程之间通过wait, notify等方式通讯, 保证每次上下文切换是有意义的, 减少无畏的线程切换.
NIO实现了IO多路复用中的Reactor模型, 一个线程使用Selector通过轮询的方式去监听多个Channel上的时间, 从而使一个线程可以处理多个事件.
注意的是, 只有为SocketChannel才能配置非阻塞, 而为FileChannel不能, 况且这也没有意义.
注册Channel
当我们将Channel注册到Selector上时, 要指定注册的具体事件, 事件的定义如下:
1 2 3 4
| public static final int OP_READ = 1 << 0; public static final int OP_WRITE = 1 << 2; public static final int OP_CONNECT = 1 << 3; public static final int OP_ACCEPT = 1 << 4;
|
我们将事件状态压缩, 这样可以通过|来表示多个事件类型.
优点
总的来说, NIO对比BIO有如下优点:
节约了大量线程所占用的栈空间.
避免线程之间的切换所带来的花费.
数据块为单位的IO速度高于传统的流式读取速度.
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; 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;
class NIOServer { private static String readDataFromSocketChannel(SocketChannel socketChannel) throws IOException { ByteBuffer byteBuffer = ByteBuffer.allocate(1024); StringBuilder data = new StringBuilder(); while(true) { byteBuffer.clear(); int n = socketChannel.read(byteBuffer); if(n == -1) { break; } byteBuffer.flip(); int limit = byteBuffer.limit(); char[] currentData = new char[limit]; for(int i = 0; i < limit; i++) { currentData[i] = (char) byteBuffer.get(i); } data.append(currentData); } return data.toString(); }
public static void main(String[] args) throws IOException { Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
ServerSocket serverSocket = serverSocketChannel.socket(); InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888); serverSocket.bind(inetSocketAddress);
while(true) { selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = keys.iterator(); while(keyIterator.hasNext()) { SelectionKey selectionKey = keyIterator.next(); if(selectionKey.isAcceptable()) { ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel(); SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } else if(selectionKey.isReadable()) { SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); System.out.println(readDataFromSocketChannel(socketChannel)); socketChannel.close(); } keyIterator.remove(); } } } }
class NIOClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 8888); OutputStream outputStream = socket.getOutputStream(); String string = "Hello World!"; outputStream.write(string.getBytes()); outputStream.close(); } }
|