网络编程之NIO(二)
一.NIO定义
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。学习NIO首先要知道其有两个核心,一个是ByteBuffer字节缓冲区,一个是Selector通道选择器
二.ByteBuffer字节缓冲区
1.ByteBuffer设计原理
2.demo测试
import java.nio.ByteBuffer;
/**
* Created by ${ligh} on 2019/2/22 下午4:54
*
* 图解参考 https://www.processon.com/
*/
public class TestBufferDemo {
public static void main(String[] args) {
//1.创建ByteBuffer
byte[] bytes = new byte[5];
ByteBuffer buffer = ByteBuffer.wrap(bytes);
showMetircs("初始化状态",buffer); //打印结果: 0 5 5
//写入字节
buffer.put((byte) 'a');
buffer.put((byte) 'b');
showMetircs("写入两个字节之后",buffer); //打印结果: 2 5 5
//遍历读取数据
/* while (buffer.hasRemaining()){
System.out.print(buffer.get()+" "); //读取到的是 0 0 0
}
System.out.println();*/
//为了读取到添加进去的a,b两字节,调用flip()方法
buffer.flip();
//遍历读取数据
while (buffer.hasRemaining()){
System.out.print(buffer.get()+" "); //读取到的是 97 98 此时position和limit的值都是2,不能再存储数据了,因为没有缓冲区了
}
System.out.println();
//查看此时buffer中属性的值
showMetircs("读完a,b两字节之后",buffer); //打印结果 2 2 5 表示pos和limit的之间没有存储空间了
//调用clear()方法,然后添加字节c
buffer.clear();
buffer.put((byte)'c');
showMetircs("写完c字节之后",buffer); //打印结果 1 5 5
//遍历添加完c字节之后的缓冲区buffer
while (buffer.hasRemaining()) {
System.out.print(buffer.get() + " "); //读取到的数据是 98 0 0 0
}
//如果想读到新添加的字节c也就是98只需要调用flip即可
}
public static void showMetircs(String state,ByteBuffer buffer){
System.out.println(state+ " pos: "+buffer.position()+" ,limit: "+buffer.limit()+" ,capacity: "+buffer.capacity());
}
}
三.Selector通道选择器
1.定义:
Selector会自动把注册队列中的附件封装成一个个的SelectionKey放在事件队列中,然后一直遍历事件队列获得SelectionKey对应的通道类型或者附件,然后执行相应的IO操作类型是accept还是read或者write,最后执行完成之后把事件队列中的SelectionKey移除,注册队列不进行操作,继续监控.作用就是辅助检查Channel状态,将就绪的IO提供给线程。
2.设计原理
三.NIO编程套路
1.服务端:
创建ServerSocketChannel
绑定监听端口
设置通道非阻塞
创建通道选择器
注册事件类型
迭代遍历事件列表
2.客户端:
创建客户端实例 Socket s = new Socket();
连接服务器 s.connect(new InetSocketAddress(host,port));
发送请求 OutputStream out = s.getOutputStream();
获取响应 InputStream result = s.getInputStream();
关闭资源 s.close();
四.实战demo演练
1.服务端:
import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
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.Date;
import java.util.Iterator;
/**
* 服务端
*
*/
public class NioServerBootstrap {
public static void main(String[] args) throws Exception{
//1.创建ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();
//2.绑定监听端口
ssc.bind(new InetSocketAddress(9999));
//3.设置通道非阻塞
ssc.configureBlocking(false);
//4.创建通道选择器
Selector selector = Selector.open();
//5.注册事件类型
ssc.register(selector, SelectionKey.OP_ACCEPT);
//6.遍历事件列表
while (true){
System.out.println("------我在9999-------");
int n = selector.select();
//判断是否阻塞 0表示阻塞
if(n>0){ //n大于0表示有事件需要处理
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
//遍历获取每一个key
SelectionKey key = iterator.next();
//处理对应的IO事件
if(key.isAcceptable()){
System.out.println("------isAcceptable------");
//获取通道
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
//调用channel的转发
SocketChannel socketChannel = channel.accept();//立即返回一个非null的SocketChannel
//设置通道非阻塞
socketChannel.configureBlocking(false);
//注册到通道选择器注册队列
socketChannel.register(selector,SelectionKey.OP_READ);
}else if(key.isReadable()){
System.out.println("------isReadable------");
//读就绪的情况下进入
SocketChannel channel = (SocketChannel) key.channel();
//读事件
//用户意图
ByteBuffer buffer = ByteBuffer.allocate(1024);
ByteArrayOutputStream req = new ByteArrayOutputStream();
while (true){
buffer.clear();
int num = channel.read(buffer);
if(num == -1) break;
buffer.flip();
req.write(buffer.array(),0,num);
}
channel.register(selector,SelectionKey.OP_WRITE,req);
}else if(key.isWritable()){
System.out.println("------isWritable-----");
//写事件
//获取附件
ByteArrayOutputStream req = (ByteArrayOutputStream) key.attachment();
ByteBuffer buffer = ByteBuffer.wrap((new Date().toLocaleString()).getBytes());
SocketChannel channel = (SocketChannel) key.channel();
channel.write(buffer);
//告诉通道写结束
channel.shutdownOutput();
//关闭通道
channel.close();
}
//移除已经处理的事件
iterator.remove();
}
}else {
continue;
}
}
}
}
2.客户端:
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
* Created by ${ligh} on 2019/2/22 上午10:08
*
* 在NIO中服务端不一样,但是客户端几乎都是差不多的
*
* 客户端
*/
public class Clientbootstrap {
public static void main(String[] args) throws Exception{
//1. 创建客户端实例
Socket s = new Socket();
//2. 连接服务器
s.connect(new InetSocketAddress("127.0.0.1",9999));
//3. 发送请求
OutputStream out = s.getOutputStream();
PrintWriter pw = new PrintWriter(out);
pw.println("你好,我是客户端...");
pw.flush();
//告诉服务器 写结束
s.shutdownOutput();
//4. 获取响应
InputStream result = s.getInputStream();
InputStreamReader isr= new InputStreamReader(result);
BufferedReader reader= new BufferedReader(isr);
//读取响应
String line = null;
StringBuffer sb = new StringBuffer();
while ((line = reader.readLine())!=null){
sb.append(line);
}
System.out.println("客户端收到: "+sb.toString());
//5. 关闭资源
s.close();
}
}
五.拓展
通过NIO实现文件的拷贝,具体demo如下:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* Created by ${ligh} on 2019/2/22 下午5:18
*
*/
public class TestFileCopy {
public static void main(String[] args) throws Exception{
// testWrite();
// testRead();
testCopy();
}
/**
* 写文件
*
* 特点:
* 应用 --> ByteBuffer --> 通道 --> 磁盘 应用将数据传到ByteBuffer中,然后通过通道将数据写到磁盘中
*
* @throws Exception
*/
public static void testWrite() throws Exception{
//所写入文件中的位置
FileOutputStream fos = new FileOutputStream("aa.txt");
//创建缓冲区
ByteBuffer buffer = ByteBuffer.wrap(new byte[1024]);
//创建通道
FileChannel channel = fos.getChannel();
//向缓冲区中存储数据
buffer.put("hello world!".getBytes());
//读到刚存进缓冲区中的数据
buffer.flip();
//写入到通道中
channel.write(buffer);
//关闭资源
channel.close();
fos.close();
}
/**
* 读文件
*
* 特点:
* 磁盘 ---> 通道 ---> ByteBuffer中 ---> 应用 磁盘数据通过通道写入到ByteBuffer中,然后应用从ByteBuffer中拿读取数据
* @throws Exception
*/
public static void testRead() throws Exception{
//要读取的文件
FileInputStream fis = new FileInputStream("aa.txt");
//创建通道
FileChannel inchannel = fis.getChannel();
//创建缓冲区
ByteBuffer buffer = ByteBuffer.wrap(new byte[1024]);
//通道读取数据到buffer中,相当于存储到其中
int i = inchannel.read(buffer);
buffer.flip();
//获取buffer中的数据
System.out.println(new String(buffer.array(),0,i));
//关闭资源
inchannel.close();
fis.close();
}
/**
* 文件的拷贝
*
* 实际就是边读边写
*
* @throws Exception
*/
public static void testCopy()throws Exception{
FileInputStream fis = new FileInputStream("aa.txt");
FileOutputStream fos = new FileOutputStream("bb.txt");
//创建通道
FileChannel inchannel = fis.getChannel();
FileChannel outchannel = fos.getChannel();
//创建ByteBuffer
ByteBuffer buffer = ByteBuffer.wrap(new byte[1024]);
//开始读取文件,直到读完为止
while (true){
//每次读之前首先要调用clear方法
buffer.clear();
int i = inchannel.read(buffer);
//如果i等于-1就表示已经读完,否则就继续读
if(i == -1) break;
buffer.flip();
//写出到文件中
outchannel.write(buffer);
}
//关闭资源
inchannel.close();
outchannel.close();
fis.close();
fos.close();
}
}
总结
原来的BIO是一种多线程阻塞的IO,非常消耗资源,由此产生了NIO,这个单线程阻塞,多线程畅通的IO,单线程阻塞情况是通道选择器中的事件队列为空的情况下,所以不会消耗很多资源,因此是一种理想化的IO处理方式,但是,为了开发简单快捷,还是不建议使用,因此由此产生了NIO的框架Netty/Mina,一种便捷式的开发框架,具体介绍请看下一篇。
还没有评论,来说两句吧...