NIO核心的三大组件
- Channels
- Buffers
- Selectors
Channel
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
socketChannel 的非阻塞模式
socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80)); while(! socketChannel.finishConnect() ){ //wait,&nbs***bsp;do something else... }
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); serverSocketChannel.configureBlocking(false); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ //do something with socketChannel... } }ServerSocketChannel可以设置成非阻塞模式。在非阻塞模式下,accept() 方***立刻返回,如果还没有新进来的连接,返回的将是null。 因此,需要检查返回的SocketChannel是否是null
datagramChannel UDP在数据传送方面没有任何保证
接收数据
ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); channel.receive(buf);发送数据
String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));
Buffer
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- MappedByteBuffer (内存映射文件)
- 写入数据到buffer
- 调用 flip 方法从写模式切换到读模式
- 从buffer中读取数据
- 调用clear 或者 compact方法 清除数据 , clear是清除所有数据,compact是清除已读数据,未读的数据会移动到buffer起始位置
allocate 分配缓冲区
rewind 将position置为0 ,重新读取数据
mark 标记一个指定的position
reset 将position 重置回到mark标记的位置
Selector
Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。
要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方***一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件
Selector selector = Selector.open() 创建选择器
向selector 注册通道
channel.configureBlocking(false); SelectionKey key = channel.register(selector,Selectionkey.OP_READ);与 selector 一起使用,channel必须是非阻塞的,这意味着 FileChannel 不能和 selector 一起使用,
第二个参数 是 interest 集合,标识监听的感兴趣的事件
- SelectionKey.OP_CONNECT 连接就绪
- SelectionKey.OP_ACCEPT 接收就绪
- SelectionKey.OP_READ 读就绪
- SelectionKey.OP_WRITE 写就绪
SelectionKey
注册完之后就会返回一个selectionKey对象,
int interestSet = selectionKey.interestOps(); boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT; boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;ready集合
selectionKey.isAcceptable(); selectionKey.isConnectable(); selectionKey.isReadable(); selectionKey.isWritable();附加对象
selectionKey.attach(theObject); Object attachedObj = selectionKey.attachment();监听就绪事件
Set selectedKeys = selector.selectedKeys(); Iterator keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading } else if (key.isWritable()) { // a channel is ready for writing } keyIterator.remove(); }注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。
分散和聚集 (Scatter / Gather)
分散就是一个channel 写到 多个buffer
ByteBuffer[] bufferArray = { header, body }; channel.read(bufferArray);
聚集就是多个buffer 写入同一个 channel
通道之间数据传输
transferFrom
transferTo
管道 Pipe
向管道写数据,需要用到sinkChannel
Pipe.SinkChannel sinkChannel = pipe.sink(); String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()) { sinkChannel.write(buf); }从管道读取数据,需要用到SourceChannel
Pipe.SourceChannel sourceChannel = pipe.source(); ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead = sourceChannel.read(buf);