NIO
跳转到NIO
-不是真异步,是一个同步的通讯模式
举例:假如你去饭店吃饭
- 同步阻塞:当你下完单,你只能等在饭店里面,啥事也不能做,等待厨师完成,并亲自和饭店完成交接。
- 同步非阻塞:你下完单以后,可以外出,不用一直等待。但是会采用定期轮询的方法,随时来看饭菜是否完成。如果已制作完成,也需要亲自和饭店完成交接。
- 异步非阻塞:下完单后,可以外出,也不要定期查看厨师是否做好了(不用定期轮询)。而是交代下来,制作完成后,自动送到家里。(即制作完成后,自动进行一个回调函数执行(自动送达操作))
并发编程的同步和网络通讯的同步两者的区别:
- 并发编程同步:是指多个线程需要以一种同步的方式来访问一个数据结构。这里同步的反义词是非同步的,即线程不安全的。
- 网络编程同步:是指客户端和服务端直接的通讯等待方式。这里的同步的反义词是异步,即无需等待另一端操作完成。
AIO
- Asynchronous I/O,异步I/O
- JDK1.7引入,主要在java.nio包中
- 异步I/O,采用 回调方法 进行处理读写操作
- 主要类:
- AsynchronousServerSocketChannel 服务器接受客户端请求通道
- bind绑定在某一个端口 accept 接受客户端请求
- AsynchronousSocketChannel Socket通讯通道
- read 读数据 write 写数据
- AsynchronousServerSocketChannel 服务器接受客户端请求通道
- CompletionHandler 异步处理类
- completed 操作完成后异步调用方法 failed 操作失败后异步调用方法
- 每一个匿名的CompletionHandler 对象都要实现一个failed方法
示例:
先运行服务器端程序
package aio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AioServer {
public static void main(String[] args) throws IOException {
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();
server.bind(new InetSocketAddress("localhost", 8001));
System.out.println("服务器在8001端口守候");
//开始等待客户端连接,一旦有连接,做26行任务
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override //complete方法是指连接被服务器接收以后,当通道建立好以后,应该做什么
public void completed(AsynchronousSocketChannel channel, Object attachment) {
server.accept(null, this); //持续接收新的客户端请求
ByteBuffer buffer = ByteBuffer.allocate(1024); //准备读取空间
//开始读取客户端内容,一旦读取结束,做33行任务
channel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override //此complete方法,是指通道里面读取数据,读取完毕以后应该做什么
public void completed(Integer result_num, ByteBuffer attachment) {
attachment.flip(); //反转此Buffer
CharBuffer charBuffer = CharBuffer.allocate(1024);
CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
decoder.decode(attachment,charBuffer,false);
charBuffer.flip();
String data = new String(charBuffer.array(),0, charBuffer.limit());
System.out.println("client said: " + data);
channel.write(ByteBuffer.wrap((data + " 666").getBytes())); //返回结果给客户端
try{
channel.close();
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("read error "+exc.getMessage());
}
});
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.print("failed: " + exc.getMessage());
}
});
while(true){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
在运行客户端程序
package aio;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.UUID;
public class AioClient {
public static void main(String[] a) {
try
{ //用于连接到服务器的通道
AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
//18行连接成功后,自动做20行任务
channel.connect(new InetSocketAddress("localhost", 8001), null, new CompletionHandler<Void, Void>() {
//此complete方法是指connect成功调用以后才会执行
public void completed(Void result, Void attachment) {
String str = UUID.randomUUID().toString(); //创建一个随机字符串
//24行向服务器写数据成功后,自动做28行任务
channel.write(ByteBuffer.wrap(str.getBytes()), null,
new CompletionHandler<Integer, Object>() {
@Override //此complete方法,在write成功以后才会执行
public void completed(Integer result, Object attachment) {
try {
System.out.println("write " + str + ", and wait response");
//等待服务器响应
ByteBuffer buffer = ByteBuffer.allocate(1024); //准备读取空间
//开始读取服务器反馈内容,一旦读取结束,做39行任务
channel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override //此complete方法,是在read 成功以后才会执行
public void completed(Integer result_num, ByteBuffer attachment) {
attachment.flip(); //反转此Buffer
CharBuffer charBuffer = CharBuffer.allocate(1024);
CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
decoder.decode(attachment,charBuffer,false);
charBuffer.flip();
String data = new String(charBuffer.array(),0, charBuffer.limit());
System.out.println("server said: " + data);
try{
channel.close();
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("read error "+exc.getMessage());
}
});
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("write error");
}
});
}
public void failed(Throwable exc, Void attachment) {
System.out.println("fail");
}
});
Thread.sleep(10000);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Server 收到客户端Clientfa过来一个随机字符串
服务端Server 回复了一个666的附加内容给客户端。客户端向Server输出一个随机字符串,收到Server返回的内容为原来随机字符串加上一个666。
以上程序和步骤就代表完成了一次客户端和服务端之间的AIO的交互
注意:程序中用到的回调方法,每一个操作都可以设置它的回调方法
channel.connect(new InetSocketAddress("localhost", 8001), null, new CompletionHandler<Void, Void>() {
//此complete方法是指connect成功调用以后才会执行
public void completed(Void result, Void attachment)
如上代码块,当connect成功了,我就设置一个回调方法。一旦connect成功就执行
public void completed(Void result, Void attachment)
方法。以前写代码我们手动(如用if…else)去判断connect是否成功。现在可以采用异步的方式,直接把要处理的代码,直接放在connect里面,当作回调参数放进来,什么时候connect成功,什么时候执行这个completed方法。
connect什么时候结束我不知道,但是,我给它了一个指令,connect成功以后,就执行24行的completed方法( 这里好比前面举的例子,你在饭店里下了一个单,你外出了,厨师什么时候弄好,你不用知道,厨师弄好了以后能通知到你,并且把饭送到你手上。)。同理其他的调函数也一样。
回调函数写起来很方便,但若嵌套很多,代码阅读和维护会变得比较困难。
三种I/O的区别: