前段时间在项目组分享了下NIO,现重新整理一下
一.概念
什么是NIO,和传统IO有什么区别:
- java NIO全称java non-blocking IO,是指jdk1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。(百度百科)
- 主要区别
IO | NIO |
---|---|
面向流 | 缓冲区 |
阻塞 | 非阻塞 |
-- | 多路复用 |
阻塞IO和非阻塞IO
https://www.cnblogs.com/aspirant/p/6877350.htmlutm_source=itdadao&utm_medium=referral
这篇博文介绍的比较清楚.
二.核心组件
- Channels(通道)
- Buffers(缓冲区)
- Selectors(选择器)
三.Buffer(缓冲区)
Java NIO的Buffer用于和NIO通道进行交互,缓冲区本质上可以写入数据,读取数据的内存,这块内存被包装成Buffer对象
数据都是通过通道(Channel)读入缓冲区,从缓冲区写入通道
属性
Capacity(容量):缓冲区能够容纳的元素的最大数量,初始化时就被设定
Limit(上界) :缓冲区的第一个不能被读或写的元素(缓冲区中现存元素的个数)
Position(位置):下一个要被读或写的元素索引,位置会自动由get()和put()更新
Mark(标记):一个备忘位置
APi
put() 写数据
get() 读取
flip()翻转:将一个能够继续添加元素的缓冲区翻转成一个准备读出元素的释放状态
hasRemaining():函数会在释放缓冲区时告诉是否已经达到缓冲区的上界
clear 回到初始状态
mark()标记, reset()回滚
ByteBuffer.allocate(10); 创建 底层都是数组
ByteBuffer.allocateDirect(10); 创建 底层不是数组
wrap(new byte[10]); 传入byte数组,使用整个数组创建
wrap(new byte[10],2,3); 传入byte数组,使用数组部分创建
compact 将未读数据前移,positom指向前移后数据的后一位,limit = capacity;一次没有读完,切换到写模式
四.Channel(通道)
Java nio的通道类似流,但又有不同:既可以从通道中读取数据,又可以写数据到通道,但是流的读写通常时单向的通道可以异步去读写通道中的数据总是要先读到一个Buffer,或总是要从一个Buffer中写入
主要实现:
FileChannel : 从文件中读写数据
DatagramChannel: 通过UDP读写网络数据
SocketChannel:通过TCP读写网络数据
ServerSocketChannel:监听新进来的tcp连接
通道读是将数据从通道读到buffer里面;
通道写是将数据从buffer写入到通道;
五.selector(选择器)
选择器是java NIO中能够检测一道多个NIO通道,并能够知晓通道是否为读写事件做好准备的组件一个单独的线程可以管理多个channel
selector使用:
- 创建selector
Selector selector = Selector.open();
- 注册channel到selector
channel.configureBlocking(false);
SelectionKey key = channel.register(selector,SelectionKey.OP_READ );
与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将 FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。
- 通道选择
int count = selector.select();
- 访问“已选择键集(selected key set)”中的就绪通道
Set selectedKeys = selector.selectedKeys();
注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:
1. Connect
2. Accept
3. Read
4. Write
SelectionKeys
属性
- slector() 获取对象本身
- channel() 获取当前就绪的通道
- cancel() 取消注册事件(通道)
- interst(int ops)注册感兴趣事件
- attachment() 附加对象
/**
* @ClassName NioServer
* @Description TODO
* @Author liuzetian
* @Date 2019/5/5 17:55 PM
* @Version 1.0
**/
public class NioServer {
private static Selector selector;
private static ServerSocketChannel serverSocketChannel;
private static ByteBuffer bf = ByteBuffer.allocate(1024);
public static void main(String[] args) throws Exception {
//创建selector
selector = Selector.open();
//打开serverSocketChannel通道
serverSocketChannel = ServerSocketChannel.open();
//设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
//开启绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
//向selector注册
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
//count为准备就绪通道的数量 阻塞
int count = selector.select();
//已选择键集
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
//遍历
while (it.hasNext()) {
SelectionKey key = it.next();
//连接状态
if (key.isAcceptable()) {
System.out.println("新连接进入");
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
//数据可读状态
System.out.println("读准备就绪,开始读.......................");
//获取SocketChannel 读取数据
SocketChannel channel = (SocketChannel) key.channel();
System.out.println("客户端的数据如下:");
int readLen = 0;
bf.clear();
StringBuffer sb = new StringBuffer();
//注意一定要读取完数据然后关闭通道,
while ((readLen = channel.read(bf)) > 0) {
bf.flip();
byte[] temp = new byte[readLen];
bf.get(temp, 0, readLen);
sb.append(new String(temp));
bf.clear();
}
if (-1 == readLen) {
channel.close();
}
System.out.println(sb.toString());
//注册写事件
channel.register(selector, SelectionKey.OP_WRITE);
} else if (key.isWritable()) {
//写事件
SocketChannel channel = (SocketChannel) key.channel();
channel.write(ByteBuffer.wrap(("客户端,已接收到传送来的数据").getBytes()));
//注销写事件
key.cancel();
// channel.close();
}
it.remove();
}
}
}
}
介绍的比较简单, 如有错误, 欢迎指正