NIO被叫为 no-blocking io,其实是在网络这个层次中理解的,对于FileChannel来说一样是阻塞。对于网络通信是还有如下几个Channel
java.nio.channels.Channel 接口 |-- SelectableChannel |-- SocketChannel |-- ServerSocketChannel |-- DatagramChannel |-- Pipe.SinkChannel |-- Pipe.SourceChannel
我们通常使用NIO是在网络中使用的,网上大部分讨论NIO都是在网络通信的基础之上的!说NIO是非阻塞的NIO也是网络中体现的!
nio的核心要素有:
Buffer缓冲区 Channel通道 Selector选择器
我们在网络中使用NIO往往是I/O模型的多路复用模型!
NIO阻塞形态
为了更好地理解,我们先来写一下NIO在网络中是阻塞的状态代码,随后看看非阻塞是怎么写的就更容易理解了。是阻塞的就没有Selector选择器了,就直接使用Channel和Buffer就完事了。
客户端代码
/** * 使用NIO完成网络通信的三个核心 * * 1.通道(Channel):负责连接 * java.nio.channels.Channel 接口 * |-- SelectableChannel * |-- SocketChannel * |-- ServerSocketChannel * |-- DatagramChannel * * |-- Pipe.SinkChannel * |-- Pipe.SourceChannel * * 2.缓冲区(Buffer):负责数据的存取 * * 3.选择器(Selector):是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况 * * * @author 波波烤鸭 * @email dengpbs@163.com * */ public class BlockClient { /** * 传递图片 * @param args * @throws Exception */ public static void main(String[] args) throws Exception { // 1.获取通道 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999)); // 2.发送一张图片给服务器 FileChannel fileChannel = FileChannel.open(Paths.get("c:/tools/a9.jpg"), StandardOpenOption.READ); // 3.要使用NIO,有了Channel必然要使用Buffer,Buffer是与数据打交道的 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 4.读取本地文件,发送给服务器 while(fileChannel.read(byteBuffer)!=-1){ byteBuffer.flip();//读取之前切换成读模式 socketChannel.write(byteBuffer); byteBuffer.clear(); } // 显示的告诉服务器数据写完了 socketChannel.shutdownOutput(); int num = 0 ; byteBuffer.clear(); while((num = socketChannel.read(byteBuffer)) !=-1){ // 切回读模式 byteBuffer.flip(); System.out.println(new String(byteBuffer.array(),0,num)); byteBuffer.clear(); } /*// 5.关闭流 fileChannel.close(); socketChannel.close();*/ } }
服务器代码
public class BlockServer { /** * IO阻塞 服务端 * @param args * @throws Exception */ public static void main(String[] args) throws Exception { //1.获取通道 ServerSocketChannel server = ServerSocketChannel.open(); //2.得到文件通道,将客户端传递的图片写入到本地 FileChannel fileChannel = FileChannel.open(Paths.get("c:/tools/bb.jpg"), StandardOpenOption.WRITE ,StandardOpenOption.CREATE);// 如果不存在就创建 // 3.绑定连接 server.bind(new InetSocketAddress(9999)); // 4.获取客户端的连接(阻塞的) SocketChannel client = server.accept(); // 5.声明Buffer存储图片 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 6.将客户端传递的数据保存在本地 while(client.read(byteBuffer)!=-1){ byteBuffer.flip(); fileChannel.write(byteBuffer); byteBuffer.clear(); } byteBuffer.put("图片接收成功!!".getBytes()); byteBuffer.flip(); client.write(byteBuffer); byteBuffer.clear(); // 显示的告诉客户端信息输完了 client.shutdownOutput(); /*fileChannel.close(); client.close(); server.close();*/ } }
NIO非阻塞形态
如果使用非阻塞模式的话,那么我们就可以不显式告诉服务器已经发完数据了,但是需要显示的指定是非阻塞的
客户端:
public class NoBlockClient { public static void main(String[] args) throws Exception { // 1. 获取通道 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666)); // 1.1切换成非阻塞模式 socketChannel.configureBlocking(false); // 2. 发送一张图片给服务端吧 FileChannel fileChannel = FileChannel.open(Paths.get("c:/tools/a9.jpg"), StandardOpenOption.READ); // 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢 ByteBuffer buffer = ByteBuffer.allocate(1024); // 4.读取本地文件(图片),发送到服务器 while (fileChannel.read(buffer) != -1) { // 在读之前都要切换成读模式 buffer.flip(); socketChannel.write(buffer); // 读完切换成写模式,能让管道继续读取文件的数据 buffer.clear(); } // 5. 关闭流 fileChannel.close(); socketChannel.close(); } }
服务器:
public class NoBlockServer { public static void main(String[] args) throws Exception { // 1.获取通道 ServerSocketChannel server = ServerSocketChannel.open(); // 2.切换成非阻塞模式 server.configureBlocking(false); // 3. 绑定连接 server.bind(new InetSocketAddress(6666)); // 4. 获取选择器 Selector selector = Selector.open(); // 4.1将通道注册到选择器上,指定接收“监听通道”事件 server.register(selector, SelectionKey.OP_ACCEPT); // 5. 轮训地获取选择器上已“就绪”的事件--->只要select()>0,说明已就绪 while (selector.select() > 0) { // 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件) Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); // 7. 获取已“就绪”的事件,(不同的事件做不同的事) while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); // 接收事件就绪 if (selectionKey.isAcceptable()) { // 8. 获取客户端的链接 SocketChannel client = server.accept(); // 8.1 切换成非阻塞状态 client.configureBlocking(false); // 8.2 注册到选择器上-->拿到客户端的连接为了读取通道的数据(监听读就绪事件) client.register(selector, SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { // 读事件就绪 // 9. 获取当前选择器读就绪状态的通道 SocketChannel client = (SocketChannel) selectionKey.channel(); // 9.1读取数据 ByteBuffer buffer = ByteBuffer.allocate(1024); // 9.2得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建) FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE); while (client.read(buffer) > 0) { // 在读之前都要切换成读模式 buffer.flip(); outChannel.write(buffer); // 读完切换成写模式,能让管道继续读取文件的数据 buffer.clear(); } } // 10. 取消选择键(已经处理过的事件,就应该取消掉了) iterator.remove(); } } } }
简单总结一下使用NIO时的要点:
将Socket通道注册到Selector中,监听感兴趣的事件
当感兴趣的时间就绪时,则会进去我们处理的方法进行处理
每处理完一次就绪事件,删除该选择键(因为我们已经处理完了)
DatagramChannel
发送方
public static void main(String[] args) throws IOException { // 获取通道 DatagramChannel dc = DatagramChannel.open(); // 非阻塞的 dc.configureBlocking(false); ByteBuffer buf = ByteBuffer.allocate(1024); Scanner scan = new Scanner(System.in); while(scan.hasNext()){ String str = scan.next(); buf.put((new Date().toString()+"\n"+str).getBytes()); buf.flip(); dc.send(buf, new InetSocketAddress("127.0.0.1", 6666)); buf.clear(); } dc.close(); }
接收方
public static void main(String[] args) throws Exception { // TODO Auto-generated method stub DatagramChannel dc = DatagramChannel.open(); dc.bind(new InetSocketAddress(6666)); dc.configureBlocking(false); // 获取选择器 Selector selector = Selector.open(); dc.register(selector, SelectionKey.OP_READ); while(selector.select() > 0){ Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while(it.hasNext()){ SelectionKey sk = it.next(); if(sk.isReadable()){ ByteBuffer buf = ByteBuffer.allocate(1024); dc.receive(buf); buf.flip(); System.out.println(new String(buf.array(),0,buf.limit())); buf.clear(); } } it.remove(); } }
管道(Pipe)
Java NIO 管道是2个线程之间的单向数据连接,Pipe有一个source通道和一个sink通道,数据会被写到sink通道,从source通道读取。
public static void main(String[] args) throws Exception { // 1.获取管道 Pipe pipe = Pipe.open(); // 2.将缓冲区数据写入管道 ByteBuffer buf = ByteBuffer.allocate(1024); // 3.获取SinkChannel对象 SinkChannel sinkChannel = pipe.sink(); buf.put("hello pipe".getBytes()); buf.flip(); sinkChannel.write(buf); // 4.读取缓冲区中的数据 SourceChannel source = pipe.source(); buf.flip(); int num = source.read(buf); System.out.println(new String(buf.array(),0,num)); source.close(); sinkChannel.close(); }