1,NIO vs BIO
2,NIO的工作模式
NIO有三大核心:
1. Channel(通道)
2. Buffer(缓冲区)
3. Selector(选择器) NIO基于Channel和Buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
Channel提供了从文件,网络读取数据的通道,但是读或写数据,都必须经过Buffer,如下图所示:
Selector用于监听多个通道的事件,从而达到一个线程监听多个客户端通道的效果。即多路复用技术。
1,NIO vs BIO
BIONIO
流(单向)通道Channel(双向)
数组Buffer
阻塞式非阻塞式
2,NIO的工作模式
NIO有三大核心:
1. Channel(通道)
2. Buffer(缓冲区)
3. Selector(选择器) NIO基于Channel和Buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
Channel提供了从文件,网络读取数据的通道,但是读或写数据,都必须经过Buffer,如下图所示:
Selector用于监听多个通道的事件,从而达到一个线程监听多个客户端通道的效果。即多路复用技术。
3,文件IO的核心API
3.1 Buffer
3.2 Channel
4,文件IO-编程实战
4.1 采用NIO写文件
@Test
public void writeTest() throws Exception {
//1.创建一个输出流
FileOutputStream fileOutputStream = new FileOutputStream("nio.txt");
//2.通过输出流创建一个通道
FileChannel channel = fileOutputStream.getChannel();
//3.创建一个缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//4.将字节数据写入到缓冲区中buffer.put("hello,nio".getBytes());
//5.注意,此时缓冲区的指针已经随着放入的数据而发生偏移
//所以,需要重置
buffer.flip();
//6.将缓冲区的数据写入通道,通道负责将数据写入到文件中channel.write(buffer);
//7.关闭资源fileOutputStream.close();
}
4.2 采用NIO读文件
@Test
public void readTest() throws IOException {
//1.创建一个输入流
FileInputStream fileInputStream = new FileInputStream("nio.txt");
//2.通过输入流创建一个通道
FileChannel channel = fileInputStream.getChannel();
//3.创建一个缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//4.从通道中读取数据,并写入到缓冲区中channel.read(buffer);
//5.输出读取到的数据
System.out.println(new String(buffer.array()));
//6.关闭资源fileInputStream.close();
}
4.3 完成文件的复制
@Test
public void copyTest() throws Exception{
//1.创建输入流和输出流
FileInputStream fileInputStream = new FileInputStream("nio.txt"); FileOutputStream fileOutputStream = new FileOutputStream("nio_copy.txt");
//2.通过流创建两个通道
FileChannel source = fileInputStream.getChannel(); FileChannel target = fileOutputStream.getChannel();
//3.通过通道实现快速拷贝
//target.transferFrom(source,0,source.size()); source.transferTo(0,source.size(),target);
//4.关闭资源
fileOutputStream.close(); fileInputStream.close();
}
5,网络IO
5.1 工作模式
NIO的主要应用场景就是网络IO,网络IO的通道是非阻塞式的,基于事件驱动,适合于大量连接,但数据交换量不大的情况。 比如即时通讯。
包括4个关键API
1. Selector
2. SelectionKey
3. ServerSocketChannel
4. SocketChannel
5.2 Selector
通过选择器可以检测多个注册的通道是否有事件发生,如果有事件发生,便捕获事件并进行处理。
这样就可以达到一个线程处理多个客户端连接的效果
5.3 SelectionKey
代表Selector和网络通道的注册关系。
1. OP_ACCEPT:表示有新的网络连接可以accept
2. OP_CONNECT:表示连接已经建立
3. OP_READ:表示读操作
4. OP_WRITE:表示写操作
5.4 ServerSocketChannel
服务端监听新的客户端socket连接
5.5 SocketChannel
网络通道,负责具体的读写操作。 将缓存区的数据写入通道,或者从通道将数据读到缓冲区。
6,编码实战
6.1 目标
实现客户端和服务端之间的网络通信
客户端程序
/**
* @author huangguizhao
*/
public class NIOClient {
public static void main(String[] args) throws IOException {
//1.创建一个网络通道
SocketChannel channel = SocketChannel.open();
//2.设置该通道为非阻塞channel.configureBlocking(false);
//3.设置连接的服务端IP及端口
InetSocketAddress address = new InetSocketAddress("127.0.0.1",6666);
//4.连接服务器
//如果连接不上服务器,则尝试重复连接
if(!channel.connect(address)){//没连接成功
while (!channel.finishConnect()){
//等待连接,体现NIO的非阻塞优势
System.out.println("客户端在连接服务端的同时,还可以做其他的事");
}
}
//5.创建一个缓存区,用于存放发送的数据
ByteBuffer buffer = ByteBuffer.wrap("hello,nio server".getBytes());
//6.发送数据channel.write(buffer);
//7.避免服务端程序结束,设置阻塞System.in.read();
}
}
6.2 服务端程序
/**
* @author huangguizhao
*/
public class NIOServer {
public static void main(String[] args) throws IOException {
//1.创建ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.绑定监听的端口号
serverSocketChannel.bind(new InetSocketAddress(6666));
//3.设置为非阻塞式
serverSocketChannel.configureBlocking(false);
//4.创建一个Selector对象
Selector selector = Selector.open();
//5.把ServerSocketChannel对象注册给Selector对象
//同时关注连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.Selector开始监控while (true){
//每隔1秒钟查看是否可处理的事件if(selector.select(1000) == 0){
//非阻塞的体现
System.out.println("当前没有事件需要处理,服务器可以做点其他事");
continue;
}
//获取需要处理的事件,因为可能存在多个客户端的事件需要处理,所以返回的是集合Iterator keyIterator = selector.selectedKeys().iterator();
//开始循环处理事件
while (keyIterator.hasNext()){
//获取到事件
SelectionKey key = keyIterator.next();
//根据不同的事件,做不同的处理if(key.isAcceptable()){
System.out.println("有新的客户端连接......... ");
SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false);
//将当前的channel注册到Selector上,监听可读事件
socketChannel.register(selector,SelectionKey.OP_READ);
}
if(key.isReadable()){
SocketChannel socketChannel = (SocketChannel) key.channel();
//创建一个缓存区对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
//将通道的数据读取到缓冲区socketChannel.read(buffer);
//输出结果
System.out.println(new String(buffer.array()));
}
//关键一步
//需要将当前的key移除,避免重复处理
keyIterator.remove();
}
}
}
}