在前两节中,主要介绍了Bio、Nio实现时间查询服务的细节,本节将开始介绍Aio的相关知识,Nio是通过轮询的机制检查channel的事件响应,而Aio是真正意义上的事件触发,通过回调完成响应的操作。在Aio中不需要专门的IO线程。由于Aio设计到较多的回调,因此在学习改内容之前,希望大家多Future的相关知识有所了解,不了解的可以查看我的《线程池系列》的文章中的相关知识,了解了Future之后,再来学习Aio会比较轻松。Aio是java7提出的,我们也称之为Nio 2,它才是真正意义上的异步非阻塞IO
服务端实现
不管是哪一种IO,其实现的逻辑都是一样的。
- 绑定端口号
- 接收用户连接
Aio的处理逻辑从接收用户连接开始,就开始进入了各种回调, 回调接口为CompletionHandler
- 当用户连接时,会进入连接成功的回调方法中,在回调方法中,需要重新执行一下accept()方法,目的是可以让其他客户端能够连接到服务器。其次需要读取用户的请求
- 当读取数据完成后(不一定是真正的读取完,可能会存在读半包的问题,暂时不用考虑),会回调响应的方法,解析一些请求数据,并进行业务逻辑处理,整理好响应数据,发送响应数据
- 响应数据发送完成后,可以判断数据是否发送完成,如果没有则继续发送,保证数据发送完成。
服务端代码如下:
public class AioServer implements CompletionHandler{
private int port;
private AsynchronousServerSocketChannel channel;
public AioServer(int port) throws IOException {
this.port = port;
channel = AsynchronousServerSocketChannel.open();
}
public void start(){
try {
channel.bind(new InetSocketAddress(port));
channel.accept(this, this);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void completed(AsynchronousSocketChannel result, AioServer attachment) {
attachment.channel.accept(this, this);
//链接成功,读数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
result.read(byteBuffer, byteBuffer, new ReadCompletionHandler(result));
}
@Override
public void failed(Throwable exc, AioServer attachment) {
try {
channel.close();
exc.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
AioServer server = new AioServer(8080);
server.start();
Thread.sleep(1000000);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
该类同时作为accept链接的回调类,实现了CompletionHandler接口,其中结果为接收的Channel对象,即AsynchronousSocketChannel, 携带信息在本例中携带的是该类的对象。链接成功后执行completed()方法,在该方法中要再次调用一下accept(),否则后续的客户端就链接不上服务器了(准确的说是连接上服务器了,但服务器不会去处理该连接)。然后读取数据即可。在读取数据时,也需要注册回调result.read(byteBuffer, byteBuffer, new ReadCompletionHandler(result)), 其中,第一个参数为读取数据到的buffer,第二个参数为携带信息,第三个参数为回调对象。读回调的代码如下:
public class ReadCompletionHandler implements CompletionHandler{
private AsynchronousSocketChannel channel;
public ReadCompletionHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
}
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
byte[] bytes = new byte[attachment.remaining()];
attachment.get(bytes);
String message = null;
try {
message = new String(bytes, "utf-8");
System.out.println("获取到请求:" + message);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//处理业务逻辑
if("时间".equals(message)){
String response = (new Date()).toString();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(response.getBytes());
byteBuffer.flip();
channel.write(byteBuffer, byteBuffer, new WriterCompletionHandler(channel));
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
channel.close();
exc.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
当读取完成后,可以从Buffer中把收据取出来(但是这里读取出来的数据不一定是完整了,我们并没有做这个处理),解析请求,做出响应,并执行write(),进行响应,write()方法与read()类似,不在详细讲解。写回调的代码如下:
public class ReadCompletionHandler implements CompletionHandler{
private AsynchronousSocketChannel channel;
public ReadCompletionHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
}
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
byte[] bytes = new byte[attachment.remaining()];
attachment.get(bytes);
String message = null;
try {
message = new String(bytes, "utf-8");
System.out.println("获取到请求:" + message);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//处理业务逻辑
if("时间".equals(message)){
String response = (new Date()).toString();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(response.getBytes());
byteBuffer.flip();
channel.write(byteBuffer, byteBuffer, new WriterCompletionHandler(channel));
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
channel.close();
exc.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
写完成后,只是不断的判断有没有全部发送数据而已,没有做其他的操作,说明一次请求得到响应后,可以断就没法再和服务端交互了,目前的实现是这样的,因为现在的实现都是在accept的驱动下完成的,自己可以动手修改一下,这里只是简单的介绍原理。
客户端实现
客户端的实现与服务端实现的原理是一样的,稍有不同的是处理的信息流不一样,客户端的信息流如下:链接服务端-回调中发送数据-回调中读取响应。具体的细节与服务端一样,直接上代码:
public class AioClient implements CompletionHandler{
private int port;
private String hostname;
public AioClient(String hostname, int port) {
this.hostname = hostname;
this.port = port;
}
public void start(){
try {
AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
channel.connect(new InetSocketAddress(hostname, port), channel, this);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 链接成功执行的操作
* @param result
* @param attachment
*/
@Override
public void completed(Void result, AsynchronousSocketChannel attachment) {
String message = "时间";
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(message.getBytes());
byteBuffer.flip();
attachment.write(byteBuffer, byteBuffer, new WriteCompletionHanlder(attachment));
}
@Override
public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
try {
attachment.close();
exc.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
AioClient client = new AioClient("127.0.0.1", 8080);
client.start();
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
链接成功之后,执行写操作发起请求,写操作的回调代码如下:
public class WriteCompletionHanlder implements CompletionHandler {
AsynchronousSocketChannel channel;
public WriteCompletionHanlder(AsynchronousSocketChannel channel) {
this.channel = channel;
}
@Override
public void completed(Integer result, ByteBuffer attachment) {
if(attachment.hasRemaining()){
channel.write(attachment, attachment, this);
}else{
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
channel.read(byteBuffer, byteBuffer, new ReadCompletionHandler(channel));
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
channel.close();
exc.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在写会调用多了一个判断,数据有误发送完成,如果没有发送完成就继续发送,只有请求发送成功了才能获取到响应。发送完请求后,执行读操作,等待响应,读回调的实现如下:
public class ReadCompletionHandler implements CompletionHandler {
AsynchronousSocketChannel channel;
public ReadCompletionHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
}
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
byte[] message = new byte[attachment.remaining()];
attachment.get(message);
try {
String str = new String(message, "utf-8");
System.out.println("获取响应:"+ str);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
channel.close();
exc.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结
Aio会使用大量的回调,千万不要被回调给绕晕了,一定要梳理出其处理逻辑,了解数据是怎么流向的,了解了数据的流向后才能真正的用好Aio
在下一章节,将会介绍Netty实现时间查询服务的细节,从下一节开始,该系列将会介绍Netty的相关知识。