一、NIO
1.它是同步非阻塞式IO
2.同步:一个对象在某个时段,只能被一个线程使用
3.非阻塞:线程无论是否获取到结果,都会继续往下执行或者报错
4.IO:传输数据
二、NIO三大组件
1.Buffer(缓冲区)
⑴作用:临时存储数据
⑵Buffer底层是基于数组来进行存储的,只能存储基本类型的数据。Buffer针对八种基本类型,提供了7个实现类,唯独没有针对boolean类型的实现类
⑶重要位置:capacity >= limit >= position
①capacity:容量位。用于标记缓冲区的容量,指向缓冲区的最后一位。容量位不可变
②position:操作位。类似于数组中的下标,position指向哪一位就会读写哪一位。每次读写完成之后position会自动的后挪一位。在缓冲区刚创建的时候,position默认执行第0位
③limit:限制位。用于限定position所能达到的最大下标的。当limit和position重合的时候,表示所有的数据都已经被读写完。在缓冲区刚创建的时候,limit默认和capacity重合
⑷常用方法
buffer.flip() 翻转缓冲区
buffer.wrap() 数据已知
ByteBuffer.allocate() 构建ByteBuffer对象,参数实际上是指底层的字节数组的容量
buffer.capacity() 获取容量位
buffer.hasRemaining() 获取当前位置到限制位是否有元素
public class ByteBufferDemo {
public static void main(String[] args) {
//构建ByteBuffer对象
//参数实际上是指底层的字节数组的容量
ByteBuffer buffer = ByteBuffer.allocate(10);
//获取容量位
System.out.println(buffer.capacity());
//添加数据
buffer.put("abc".getBytes());
buffer.put("def".getBytes());
/*//操作操作位
buffer.position(0);
//获取数据
byte b = buffer.get();
System.out.println(b);*/
//将limit和position重合
/*buffer.limit(buffer.position());
//再将position归零
buffer.position(0);
//上面两步操作,叫做翻转缓冲区*/
buffer.flip();
//while (buffer.position()<buffer.limit()){
while (buffer.hasRemaining()){
byte b = buffer.get();
System.out.println((char)b);
}
}
}
2.Channel(通道)
⑴作用:传输数据
⑵Channel可以实现双向传输
⑶Channel默认是阻塞的,但是可以手动设置为非阻塞
⑷常见的Channel
文件: FileChannel
public static void main(String[] args) throws IOException {
// r --- 只读方式
// rw --- 读写方式
// rwd --- 读写方式,不同于rw地方在于,要求每次写入的数据必须更新到磁盘上而不是内存中
// RandomAccessFile将整个文件看作是一个大型的字节数组
// 在RandomAccessFile中,每次读写完之后,下标会自动后挪
RandomAccessFile raf = new RandomAccessFile("e:\\b.txt", "rw");
// 获取到channel
FileChannel fc = raf.getChannel();
// 读取数据
ByteBuffer buffer = ByteBuffer.allocate(3);
fc.read(buffer);
System.out.println(new String(buffer.array(), 0, 3));
// 指定在文件中的写入位置
raf.seek(20);
// 写出数据
fc.write(ByteBuffer.wrap("abc".getBytes()));
// 关流
fc.close();
}
UDP:DatagramChannel
TCP:SocketChannel,ServerSocketChannel
public class Server {
public static void main(String[] args) throws IOException {
// 开启服务器端通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 绑定端口
ssc.bind(new InetSocketAddress(8090));
// 设置为非阻塞
ssc.configureBlocking(false);
// 接收连接
SocketChannel sc = ssc.accept();
// 判断是否接收到了连接
while (sc == null)
sc = ssc.accept();
// 连接建立
// 读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
sc.read(buffer);
// 解析数据
System.out.println(new String(buffer.array(), 0, buffer.position()));
// 关流
ssc.close();
}
}
public class Client {
public static void main(String[] args) throws IOException {
// 开启客户端通道
SocketChannel sc = SocketChannel.open();
// 手动设置为非阻塞
sc.configureBlocking(false);
// 发起连接 - 阻塞
// 非阻塞:无论是否建立连接,都会继续往下执行
sc.connect(new InetSocketAddress("localhost", 8090));
// 判断连接是否建立
// 如果多次连接没有成功,说明这个连接无法建立
while (!sc.isConnected()) {
// 试图重新建立连接
// 这个方法会自动进行计数,多次计数之后如果仍然无法建立连接,会抛出异常
sc.finishConnect();
}
// 连接建立
// 写出数据
sc.write(ByteBuffer.wrap("hello server".getBytes()));
// 关闭连接
sc.close();
}
}
⑸利用FileChannel实现"Zero-copy"(零拷贝)机制 - 零拷贝指的并不是没有数据传输而是没有状态的转化
⑹Channel都是面向Buffer进行操作
3.Selector(多路复用选择器)
⑴作用:针对事件(客户端和服务器端之间能够产生的操作)进行选择
public class Client {
public static void main(String[] args) throws IOException {
// 开启客户端通道
SocketChannel sc = SocketChannel.open();
// 发起连接
sc.connect(new InetSocketAddress("localhost", 8070));
// 写出数据
sc.write(ByteBuffer.wrap("hi from client".getBytes()));
// 读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
sc.read(buffer);
System.out.println(new String(buffer.array(), 0, buffer.position()));
// 关流
sc.close();
}
}
public class Server {
public static void main(String[] args) throws IOException {
// 开启服务器端的通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 绑定端口
ssc.bind(new InetSocketAddress(8070));
// 设置非阻塞
ssc.configureBlocking(false);
// 获取选择器
// 实际过程中,将选择器设置为单例的
Selector sel = Selector.open();
// 将通道注册到选择器上,指定要注册的事件
ssc.register(sel, SelectionKey.OP_ACCEPT);
// 实际生产过程中,服务器开了应该不会关闭
// 用while(true)来模拟服务器一直开着
while (true) {
// 当服务器一直开着的时候,随着运行时间的延长,服务器就会接收到越来越多的请求
// 不代表这些请求都是有用请求 - 需要进行选择,选出有用的请求
sel.select();
// 确定这一些请求可能会出发的操作:accept/read/write
Set<SelectionKey> set = sel.selectedKeys();
// 遍历集合,根据请求的类型不同来分别处理
Iterator<SelectionKey> it = set.iterator();
while (it.hasNext()) {
// 获取到请求类型
SelectionKey key = it.next();
// 可接受事件
if (key.isAcceptable()) {
// 先从选择器中获取到通道
ServerSocketChannel sscx = (ServerSocketChannel) key.channel();
// 接收请求
SocketChannel sc = sscx.accept();
// 设置非阻塞
sc.configureBlocking(false);
// 注册可读事件
// sc.register(sel, SelectionKey.OP_READ);
// 注册可写事件
// sc.register(sel, SelectionKey.OP_WRITE);
// 在注册事件的时候,后注册的事件会覆盖之前注册的事件
// sc.register(sel, SelectionKey.OP_READ + SelectionKey.OP_WRITE);
// sc.register(sel, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
sc.register(sel, SelectionKey.OP_READ ^ SelectionKey.OP_WRITE);
}
// 可读事件
if (key.isReadable()) {
// 获取通道
SocketChannel sc = (SocketChannel) key.channel();
// 读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
sc.read(buffer);
// 解析数据
System.out.println(new String(buffer.array(), 0, buffer.position()));
// 注销可读事件
// key.interestOps()获取到当前通道上的所有事件 --- 再从这些事件中抠掉可读事件
// sc.register(sel, key.interestOps() - SelectionKey.OP_READ);
sc.register(sel, key.interestOps() ^ SelectionKey.OP_READ);
}
// 可写事件
if (key.isWritable()) {
// 获取通道
SocketChannel sc = (SocketChannel) key.channel();
// 写出数据
sc.write(ByteBuffer.wrap("hello client".getBytes()));
// 注销可写事件
sc.register(sel, key.interestOps() - SelectionKey.OP_WRITE);
}
// 移除
it.remove();
}
}
}
}
⑵选择器放在服务端设置
⑶Selector是面向Channel操作,要求Channel必须是非阻塞的