一. 什么是阻塞?
阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。
我自己是这样理解(同步/异步、阻塞/非阻塞)的:
所谓同步就是当一个进程发起一个函数(任务)调用的时候,一直会到函数(任务)完成。进程继续往下执行。而异步这不会这样,异步情况下是当一个进程发起一个函数(任务)调用的时候,不会等函数返回,而是继续往下执行当,函数返回的时候通过状态、通知、事件。等方式通知进程任务完成。
而阻塞和非阻塞的概念相对明了多了。阻塞是当请求不能满足的时候就试进程挂起,非阻塞则是直接返回。
二.NIO包
java.nio包是Java在1.4之后增加的,用来提高I/O操作的效率。在nio包中主要包括以下几个类或接口:
* Buffer:缓冲区,用来临时存放输入或输出数据。
* Charset:用来把Unicode字符编码和其它字符编码互转。
* Channel:数据传输通道,用来把Buffer中的数据写入到数据源,或者把数据源中的数据读入到Buffer。
* Selector:用来支持异步I/O操作,也叫非阻塞I/O操作。
nio包中主要通过下面两个方面来提高I/O操作效率:
* 通过Buffer和Channel来提高I/O操作的速度。
* 通过Selector来支持非阻塞I/O操作。
三. 代码示例(文件读写 & socket通讯)
以下是 BIO,和NIO的 文件读写、socket通讯,大家可以对比下有什么不一样
BIO:
public class FileIODemo {
public static void main(String[] args) {
//获取文件
File fileRead = new File("fileIORead.txt");
File fileWrite = new File("fileIOWrite.txt");
//字节流
try {
FileInputStream input=new FileInputStream(fileRead);
FileOutputStream out=new FileOutputStream(fileWrite);
//缓冲数组
byte[] buffer=new byte[10];
int i=0;
while(input.read(buffer)!=-1){
System.out.println("i:"+i++);
out.write(buffer);
}
input.close();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class FileNIODemo {
public static void main(String[] args) {
//获取文件
File fileRead = new File("fileIORead.txt");
File fileWrite = new File("fileIOWrite.txt");
try {
FileInputStream input = new FileInputStream(fileRead);
FileOutputStream out=new FileOutputStream(fileWrite);
FileChannel inputChannel= input.getChannel();
FileChannel outputChannel=out.getChannel();
ByteBuffer buffer=ByteBuffer.allocate(10);
int i=0;
while(true){
buffer.clear();
int res=inputChannel.read(buffer);
if(res==-1){
break;
}
buffer.flip();
outputChannel.write(buffer);
System.out.println("i:"+i++);
}
inputChannel.close();
input.close();
outputChannel.close();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
====================================================================================================================================
咱们再来看看 socket;
BIO:
//客户端
public class ClientIODemo {
public static void main(String[] args) {
try {
Socket socket=new Socket("127.0.0.1",12345);
InputStream in=socket.getInputStream();
OutputStream out=socket.getOutputStream();
//客户端发送消息
String message="[client]: send a message:发送消息!";
byte[] context=message.getBytes();
out.write(context);
out.flush();
System.out.println(message);
//客户端接受消息
StringBuilder feedback = new StringBuilder();
byte[] buffer=new byte[1000];
in.read(buffer);
feedback.append(new String(buffer,Charset.defaultCharset()));
System.out.println("[client]: receive a message:"+feedback.toString());
in.close();
out.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//服务端
public class ServerIODemo {
public static void main(String[] args) {
try {
ServerSocket server=new ServerSocket(12345);
while(true){
Socket socket = server.accept();//阻塞
InputStream in=socket.getInputStream();
OutputStream out=socket.getOutputStream();
StringBuilder message=new StringBuilder();
byte[] buffer=new byte[10];
int i=0;
while(true){
i++;
in.read(buffer); //当发现没有值read的时候,就阻塞住了
message.append(new String(buffer,Charset.defaultCharset()));
if(i>10){
break;
}
}
System.out.println("[server]:"+message);
String messages="[server]: send a message:发送消息!";
byte[] context=messages.getBytes();
out.write(context);
out.flush();
in.close();
out.close();
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
NIO:
//客户端
public class ClientNIODemo {
public static void main(String[] args) {
try {
//创建客户端通道
SocketChannel client=SocketChannel.open();
Selector selector= Selector.open();
//设置成非阻塞
client.configureBlocking(false);
client.connect(new InetSocketAddress("127.0.0.1", 12345));
//注册监听Connect
client.register(selector, SelectionKey.OP_CONNECT);
boolean complate=true;
while(complate){
try {
selector.select();
} catch (IOException e) {
e.printStackTrace();
break;
}
Set keys= selector.selectedKeys();
Iterator iterator = keys.iterator();
while(iterator.hasNext()){
SelectionKey key=iterator.next();
iterator.remove();
if(key.isConnectable()&&client.finishConnect()){
System.out.println("=============connectable");
key.interestOps(SelectionKey.OP_WRITE);
}
if(key.isReadable()){
System.out.println("=============readable");
SocketChannel channel=(SocketChannel) key.channel();
ByteBuffer buffer=ByteBuffer.allocate(10);
StringBuilder feedback=new StringBuilder();
while(true){
int res=channel.read(buffer);
if(res==-1||res==0){
break;
}else if(res>0){
buffer.flip();
feedback.append(new String(buffer.array(),Charset.defaultCharset()));
buffer.clear();
}
}
System.out.println("[client]:"+feedback);
complate=false;
}
if(key.isWritable()){
System.out.println("=============writeable");
SocketChannel channel=(SocketChannel) key.channel();
ByteBuffer buffer=ByteBuffer.allocate(100);
String message="send a message:客户端发消息了!";
byte[] context=message.getBytes();
buffer.put(context);
buffer.flip();
channel.write(buffer);
key.interestOps(SelectionKey.OP_READ);
}
}
}
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//服务端
public class ServerNIODemo {
public static void main(String[] args) {
ServerSocketChannel serverChannel;
Selector selector;
try{
//服务端的准备
serverChannel= ServerSocketChannel.open();
ServerSocket serverSocket=serverChannel.socket();
serverSocket.bind(new InetSocketAddress(12345));
serverChannel.configureBlocking(false);
selector=Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e){
e.printStackTrace();
return;
}
while(true){
try {
selector.select();
} catch (IOException e) {
e.printStackTrace();
break;
}
Set readKeys = selector.selectedKeys();
Iterator iterator = readKeys.iterator();
while(iterator.hasNext()){
SelectionKey key=iterator.next();
iterator.remove();
try {
if(key.isAcceptable()){
System.out.println("=============accept");
ServerSocketChannel server = (ServerSocketChannel) key.channel();
//接受客户端的请求
SocketChannel client=server.accept();
//客户端 服务端 全都设置为 非阻塞
client.configureBlocking(false);
client.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocate(100));
}
if(key.isReadable()){
System.out.println("=============read");
SocketChannel client=(SocketChannel) key.channel();
ByteBuffer buffer=ByteBuffer.allocate(10);
WritableByteChannel out = Channels.newChannel(System.out);
while(true){
int res=client.read(buffer);
if(res==-1||res==0){
break;
}else if(res>0){
buffer.flip();
out.write(buffer);
buffer.clear();
}
}
System.out.println();
key.interestOps(SelectionKey.OP_WRITE);
}
if(key.isWritable()){
System.out.println("=============write");
SocketChannel client=(SocketChannel) key.channel();
ByteBuffer buffer=(ByteBuffer) key.attachment();
String message="send a message:服务端发消息了!";
byte[] context=message.getBytes();
buffer.clear();
buffer.put(context);
buffer.flip();
client.write(buffer);
key.cancel();
System.out.println("=============send");
try {
//取消键后仍可以得到键的通道
key.channel().close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
从以上2个列子可以可以看出:
对于BIO如果服务端读取客户端发来的信息,如果用while(true)进行循环读取,读到-1时截止,那么必须要将客服端进行关闭,否则客户端和服务端就都等待对方进行发送消息,或者关闭。也就卡死在那了。
对于NIO来说,不管服务端or客户端,只要没有数据读取直接进行返回,返回为0,如果关闭则返回-1。从而可以进行其他的逻辑。 这也就是非阻塞的概念。
对比发现,NIO多了 Channel 和 Buffer 这2个重要的类!后续我们将着重进行介绍 “通道”“缓存区”