java非阻塞NIO和阻塞IO

1         非阻塞NIO和阻塞IO

1.1           定义

阻塞IO:线程被阻塞,去处理一个读取和写入,中间如果有等待时间,则线程被占用,也不能处理其他任务;

非阻塞IO(new IO):引入了通道的概念,一个连接对应一个通道,为每个通道配置缓冲区,线程去轮询查看的通道的状态,如果某个通道上有数据准备好了,则通道状态会发生改变,线程就去处理这个读写操作;

1.2           非阻塞实现的核心Selector

Selector 一般称为选择器 ,或者多路复用器。在通道上建立socket连接,然后调用channel.register(selector, Selectionkey.OP_READ);将通道注册到Selector上,并且申明这个连接是读(READ)、写(WRITE)、接受(ACCEPT)、连接(     CONNECT),或者四个功能都包括;这样就可以用一个线程,一个Selector去管理多个连接;selector会去循环查询各个通道的状态,如果通道状态发生改变,会去判断通道的类型(读、写、接受、连接),然后再对应去做处理;

(1)Selector的创建

通过调用Selector.open()方法创建一个Selector对象,如下:

Selector selector = Selector.open();

(2)注册Channel到Selector

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, Selectionkey.OP_READ);

第二个参数是通道的类型,有四个值,方便位运算;

读 : SelectionKey.OP_READ ( 1)

写 : SelectionKey.OP_WRITE ( 4)

连接 : SelectionKey.OP_CONNECT ( 8)

接收 : SelectionKey.OP_ACCEPT ( 16)

若注册时不止监听一个事件,则可以使用“位或”操作符连接。

1.3           SelectableChannle通道上建立连接

(1)非阻塞通道

AbstractSelectableChannel抽象类继承了SelectableChannle接口, SocketChannel、ServerSocketChannel、DatagramChannel都是直接继承了 AbstractSelectableChannel抽象类 。SocketChannel是用于socket客户端,ServerSocketChannel是用于socket服务器;

(2)阻塞通道

FileChannel还是不能实现非阻塞,对文件的读写IO,不能同时写入一个文件。

1.4           SelectionKey通道状态

SelectionKey 代表各个通道的状态信号,通过调用选择器的函数来获取到所有有信号的通道的状态,Set<SelectionKey> keys = selector.selectedKeys();然后在通过SelectionKey的方法去判断是哪种信号。

(1)key.attachment(); //返回SelectionKey的attachment,attachment可以在注册channel的时候指定。

(2)key.channel(); // 返回该SelectionKey对应的channel。

(3)key.selector(); // 返回该SelectionKey对应的Selector。

(4)key.interestOps(); //返回代表需要Selector监控的IO操作的bit mask

我们可以通过与操作来判断Selector是否对Channel的某种事件感兴趣,感兴趣的boolean为1;

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;

boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;

boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;

boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;

(5)key.readyOps(); // 返回一个bit mask,代表在相应channel上可以进行的IO操作。可以通过与运算去判断是否准备好;

(6)直接调用SelectionKey对象的函数判断信号状态

boolean isReadable() 检测 Channal 中读事件是否就绪

boolean isWritable() 检测 Channal 中写事件是否就绪

boolean isConnectable() 检测 Channel 中连接是否就绪

boolean isAcceptable() 检测 Channel 中接收是否就绪

1.5           阻塞IO使用实例

客户端向服务器端发送一张图片
package com.happybks.nio.nio;
 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
 
import org.junit.Test;
 
public class TestBlockingNIO {
 
         //客户端
         @Test
         public void client() throws IOException{
                         //1、获取通道(open这种方法是jdk1.7之后才引入的)
                         SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
                         
                         //2、分配指定大小的缓冲区
                         ByteBuffer buf=ByteBuffer.allocate(1024);
                         
                         //3、从本地读取文件,并发送到服务端
                         FileChannel inFileChannel=FileChannel.open(Paths.get("D:/Test/NIO/1.jpg"), StandardOpenOption.READ);
                         while(inFileChannel.read(buf)!=-1){
                                        buf.flip();
                                        socketChannel.write(buf);
                                        buf.clear();
                         }
                         
                         //在阻塞IO下,如果关闭socketChannel,那么服务端不知道客户端是否已经把所有数据发完,所以会一直阻塞。
                         socketChannel.shutdownOutput();
                         //另一种方法就是把这个线程切换成非阻塞模式
                         
                         
                         //接收服务端反馈
                         int len = 0;
                         while((len = socketChannel.read(buf)) !=-1){
                                        buf.flip();
                                        System.out.println(new String(buf.array(),0,len));
                         }
                         
                         //4、关闭通道
                         inFileChannel.close();
                         socketChannel.close();
         }
         
         //服务端
         @Test
         public void server() throws IOException{
                         //1、获取端口
                         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
         
                         //2、绑定连接
                         serverSocketChannel.bind(new InetSocketAddress(8888));
                         
                         //3、获取客户端连接的通道
                         SocketChannel socketChannel = serverSocketChannel.accept();
                         
                         //4、接收客户端的数据,保存到本地。(提到本地,就要弄个FileChannel)
                         FileChannel outFileChannel = FileChannel.open(Paths.get("D:/Test/NIO/2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
                         ByteBuffer buf=ByteBuffer.allocate(1024);
                         while(socketChannel.read(buf)!=-1){
                                        buf.flip();
                                        outFileChannel.write(buf);
                                        buf.clear();
                         }
                         
                         //发送反馈给客户端
                         buf.put("服务端接收数据成功!".getBytes());
                         buf.flip();
                         socketChannel.write(buf);
                         
                         socketChannel.close();
                         outFileChannel.close();
                         serverSocketChannel.close();
                         
         }
}

1.6           非阻塞IO使用实例

package com.happybks.nio.nio;

import java.io.IOException;

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;

import org.junit.Test;

public class TestNonBlockingNIO {

       //客户端

       @Test

       public  void client() throws IOException{

              //1、获取通道

              SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));

              //2、切换非阻塞模式

              socketChannel.configureBlocking(false);

             

              //3、分配指定大小的缓冲区

              ByteBuffer buf=ByteBuffer.allocate(1024);

             

              //4、发送数据服务器

              buf.put(new Date().toString().getBytes());

              buf.flip();

              socketChannel.write(buf);

              buf.clear();

             

              //5、关闭通道

              socketChannel.close();

       }

      

       //服务端

       @Test

       public  void server() throws IOException{

              //1、获取通道

              ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

              //2、切换非阻塞模式

              serverSocketChannel.configureBlocking(false);

             

              //3、绑定连接

              serverSocketChannel.bind(new InetSocketAddress(8888));

             

              //4、获取选择器

              Selector selector = Selector.open();

      

              //5、将通道注册到选择器上(第二个选项参数叫做选择键,用于告诉选择器需要监控这个通道的什么状态或者说什么事件(读、写、连接、接受))

              //选择键是整型值,如果需要监控该通道的多个状态或事件,可以将多个选择键用位运算符“或”“|”来连接

              //这里服务端首先要监听客户端的接受状态

              serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

             

              //6、轮询式地获取选择器上已经“准备就绪”的事件

              while(selector.select() > 0){

                    

                     //7、获取当前选择中所有注册的“选择键(已就绪的监听事件)”

                     Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

                    

                    

                     while(iterator.hasNext()){

                            //8、获取准备“就绪”的是事件

                            SelectionKey sk=iterator.next();

                           

                            //9、判断是什么事件准备就绪

                            if(sk.isAcceptable()){

                                   //10、若接受就绪,获取客户端连接

                                   SocketChannel socketChannel = serverSocketChannel.accept();

                                  

                                   //11、客户端连接socketChannel也需要切换非阻塞模式

                                   socketChannel.configureBlocking(false);

                                  

                                   //12、将该通道注册到选择器上,监控客户端socketChannel的读就绪事件

                                   socketChannel.register(selector, SelectionKey.OP_READ);

                            }

                            else if(sk.isReadable()){

                                   //13、获取当前选择器上“读就绪”状态的通道

                                   SocketChannel socketChannel = (SocketChannel) sk.channel();

                                  

                                   //14、读取数据

                                   ByteBuffer buf=ByteBuffer.allocate(1024);

                                  

                                   int len=0;

                                   while((len=socketChannel.read(buf))>0){

                                          buf.flip();

                                          System.out.println(new String(buf.array(),0,len));

                                          buf.clear();

                                   }

                            }

                           

                            //15、取消选择键SelectionKey

                            iterator.remove();

                     }

              }

       }

}

猜你喜欢

转载自www.cnblogs.com/bclshuai/p/10734348.html
今日推荐