异步I/O (又称为 AIO )则是采用“订阅一通知”工作模式 : 即应用程序向操作系统注册I/O监听,然后继续做自己的事情。当操作系统发生I/O事件,并且准备好数据后 , 再主动通知应用程序,触发相应的函数。

异步I/O也必须由操作系统进行支持 。 微软Windows系统提供了一种异步I/O技术 : IOCP (I/O Completion Port, I/O完成端口〉; 多个Linux 操作系统版本(例如 CentOS 5等)下由于没有这种异步I/O技术,所以使用的是 epoll对异步I/O 进行模拟 。

但是在 Java AIO 框架中 , 由于应用程序不是“轮询”方式,而是“订阅一通知” 方式,所以不再需要“ Selector ” (选择器)了, 改由 Channel 通道直接到操作系统注册监昕 。在 Java AIO 框架中, 只实现了两种网络 I/O 通道“ AsynchronousServerSocketChannel ”(服务器监听通道)、 “ AsynchronousSocketChannel” (Socket 套接字通道)。但是无论哪种通道它们都有独立的 fileDescriptor (文件标识符)、 attachment (附件,可以是任意对象,类似“通道上下文”),并被独立的 SocketChannelReadHandle 类实例引用 。

服务端代码

1.SocketServer6NIO

package testBlockSocket;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; public class SocketServer6NIO {
private final static Logger LOGGER = LoggerFactory.getLogger(SocketServer6NIO.class);
private static final Object waitObject = new Object(); public static void main(String[] args) throws Exception {
/*
* 对于使用的线程池技术 : 1. Executors 是线程池生成工具,通过这个工具我们可以很轻松地生成“固定大 小的线程池”、 “调度池”、
* “可伸缩线程数量的池”。 2. 当然你也可以通过 ThreadPoolExecutor 直接实例化线程池 3. 这个线程池是用来得到操作系统的“
* I/O事件通知”的,不是用来进行“得到I/O数据后的业务处理的”。要进行后者的操作,你可以再使用另一个池(最好不要混用〉 4.
* 也可以不使用线程池〈不推荐),如果决定不使用线程池,那么直接调用 AsynchronousServerSocketChannel.open()就行了
*/ ExecutorService threadPool = Executors.newFixedThreadPool(20);
AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool(threadPool);
final AsynchronousServerSocketChannel serverSocket = AsynchronousServerSocketChannel.open(group); // 设置要监听的端口“0.0.0.0”代表本机所有 IP 设备
serverSocket.bind(new InetSocketAddress("0.0.0.0", 8888));
// 为 AsynchronousServerSocketChannel 注册监听,并不包括为随后客户端和服务器 socketChannel通道注册的监听
serverSocket.accept(null, new ServerSocketChannelHandle(serverSocket));
// 阻塞,以便 debug 时观察现象
synchronized (waitObject) {
waitObject.wait();
}
}
}

2.ServerSocketChannelHandle

// 这个处理器类,专门用来响应 ServerSocketChannel 的事件。ServerSocketChannel 只有一种事件 :接收容户端的连接
class ServerSocketChannelHandle implements CompletionHandler<AsynchronousSocketChannel, Void> {
private static final Logger LOGGER = LoggerFactory.getLogger(ServerSocketChannelHandle.class);
private AsynchronousServerSocketChannel serverSocketChannel; //
public ServerSocketChannelHandle(AsynchronousServerSocketChannel serverSocketChannel) {
this.serverSocketChannel = serverSocketChannel;
} // 注意,分别查看 this 、 socketChannel 、 attachment
// 三个对象的ID来观察不同客户端连接到达时,这三个对象的变化,以说明ServerSocketChannelHandle 的监昕模式
@Override
public void completed(AsynchronousSocketChannel socketChannel, Void attachment) {
ServerSocketChannelHandle.LOGGER.info("completed(AsynchronousSocketChannel result , ByteBuffer attachment)");
this.serverSocketChannel.accept(attachment, this);
// 为这个新的 SocketChannel
// 注册“read”事件,以便操作系统在收到数据并准备好后,主动通知应用程序在这里,由于我们要将这个客户端多次传输的数据累加起来一起处理,所以将一个StringBuffer对象作为一个
// “附件”依附在这个 Channel 上
ByteBuffer readBuffer = ByteBuffer.allocate(50);
socketChannel.read(readBuffer, new StringBuffer(), new SocketChannelReadHandle(socketChannel, readBuffer));
} @Override
public void failed(Throwable exc, Void attachment) {
ServerSocketChannelHandle.LOGGER.info("failed(Throwable exc,ByteBuffer attachment)");
}
}

3.SocketChannelReadHandle

/**
* 负责对每一个 SocketChannel 的数据获取事件进行监听 。 重要说明 : 每个 SocketChannel都会有一个独立工作的
* SocketChannelReadHandle 对象(CompletionHandler 接口的实现)
* 其中又都将独享一个“文件状态标识”对象FileDescriptor,还有一个独立的自程序员定义的 Buffer 缓存(这里我们使用的是
* ByteBuffer),所以不用担心在服务器端会出现“窃对象”这种情况,因为 Java AIO 框架已经帮你组织好了 另 一个重要的点是,用子生成
* Channel 的对象AsynchronousChannelProvider 是单例模式,无论在哪组 SocketChannel都是一个对象引用
*/
class SocketChannelReadHandle implements CompletionHandler<Integer, StringBuffer> {
private static final Logger LOGGER = LoggerFactory.getLogger(SocketChannelReadHandle.class); private AsynchronousSocketChannel socketChannel; /**
* 专门用于进行这个通道数据缓存操作的 ByteBuffer,你也可以作为 CompletionHandler的 attachment 形式传入
*/
private ByteBuffer byteBuffer; public SocketChannelReadHandle(AsynchronousSocketChannel socketChannel, ByteBuffer byteBuffer) {
this.socketChannel = socketChannel;
this.byteBuffer = byteBuffer;
} @Override
public void completed(Integer result, StringBuffer historyContext) {
// 如果条件成立,说明客户端主动终止了 TCP 套接字,这时服务器端终止就可以了
if (result == -1) {
try {
this.socketChannel.close();
} catch (IOException e) {
SocketChannelReadHandle.LOGGER.error(e.getMessage());
}
return;
}
SocketChannelReadHandle.LOGGER.info("completed (integer result ,Void attachment) :然后我们来取出通道中准备好的值"); /*
* 实际上,由于我们从 Integer result 知道了本次 Channel 从操作系统获取数据总长度,所以我们不需要切换成“读模式” ,
* 但是为了保证编码的规范性,还是建议进行切换 无论是 Java AIO 框架还是 Java NIO 框架,都会出现“Buffer
* 的总容量”小于“当前从操作系统获取到的总数据量”的情况, 但区别是, Java AIO 框架不需要专门考虑处理这样的情况,因为 Java AIO
* 框架已经帮我们做了处理
**/
this.byteBuffer.flip();
byte[] contexts = new byte[1024];
this.byteBuffer.get(contexts, 0, result);
this.byteBuffer.clear();
try {
String nowContent = new String(contexts, 0, result, "UTF-8");
historyContext.append(nowContent);
SocketChannelReadHandle.LOGGER.info("================目前的传输结果" + historyContext);
} catch (UnsupportedEncodingException e) {
SocketChannelReadHandle.LOGGER.error(e.getMessage());
} // 如果条件成立,则说明还没有接收到“结束标记”
if (historyContext.indexOf("over") == -1) {
return;
}
// 我们以“ over ”符号作为客户端完整信息的标记
SocketChannelReadHandle.LOGGER.info("==:收到完整信息,开始处理业务-==");
historyContext = new StringBuffer();
// 还要继续监听( 一次监昕一次通知)
this.socketChannel.read(this.byteBuffer, historyContext, this);
} @Override
public void failed(Throwable exc, StringBuffer historyContext) {
SocketChannelReadHandle.LOGGER.info("=====发现客户端异常关闭,服务器将关闭 TCP 通道 ");
try {
this.socketChannel.close();
} catch (IOException e) {
SocketChannelReadHandle.LOGGER.error(e.getMessage()); }
} }

服务器处理每一个客户端通道所使用的SocketChannelReadHandle (处理器)对象都是独立的,并且所引用的 SocketChannel 对象也都是独立的。 Java NIO 和 Java AIO 框架,除了因为操作系统的实现不一样而去掉了 Selector,其他的重要概念都是相似的 。实际上 Java NIO 和 Java AIO 框架各位读者可以看成是一套完整的“高并发 I/O 处理”的实现 。

网络I/O模型--06异步I/O的更多相关文章

  1. 简明网络I/O模型---同步异步阻塞非阻塞之惑

    转自:http://www.jianshu.com/p/55eb83d60ab1 网络I/O模型 人多了,就会有问题.web刚出现的时候,光顾的人很少.近年来网络应用规模逐渐扩大,应用的架构也需要随之 ...

  2. 网络I/O模型---同步异步阻塞非阻塞之惑

    网络I/O模型 人多了,就会有问题.web刚出现的时候,光顾的人很少.近年来网络应用规模逐渐扩大,应用的架构也需要随之改变.C10k的问题,让工程师们需要思考服务的性能与应用的并发能力. 网络应用需要 ...

  3. libgo协程库:网络性能完爆ASIO异步模型(-O3测试)

    在purecpp社区的github组织中有一个协程库:https://github.com/yyzybb537/libgo 近日有用户找到我,想要了解一下libgo库在网络方面的性能,于是选取已入选标 ...

  4. Linux 网络编程的5种IO模型:异步IO模型

    Linux 网络编程的5种IO模型:异步IO模型 资料已经整理好,但是还有未竟之业:复习多路复用epoll 阅读例程, 异步IO 函数实现 背景 上一讲< Linux 网络编程的5种IO模型:信 ...

  5. Linux网路编程系列-网络I/O模型

    应用程序从网络中拿数据,要经历两个阶段:1.等待数据准备好-分组到达,被拷贝到内核缓冲区,组装数据报:2.数据从内核缓冲区拷贝至用户态应用程序的缓冲区.Unix下五个I/O模型: 阻塞I/O: 进程调 ...

  6. 二.Windows I/O模型之异步选择(WSAAsyncSelect)模型

    1.基于windows消息为基础的网络事件io模型.因此我们必须要在窗口程序中使用该模型.该模型中的核心是调用WSAAsyncSelect函数实现异步I/O. 2.WSAAsyncSelect函数:注 ...

  7. Java 网络I/O模型

    网络I/O模型 人多了,就会有问题.web刚出现的时候,光顾的人很少.近年来网络应用规模逐渐扩大,应用的架构也需要随之改变.C10k的问题,让工程师们需要思考服务的性能与应用的并发能力. 网络应用需要 ...

  8. Linux 网络 I/O 模型简介(图文)(转载)

    Linux 网络 I/O 模型简介(图文)(转载) 转载:http://blog.csdn.net/anxpp/article/details/51503329 1.介绍 Linux 的内核将所有外部 ...

  9. Netty源码分析一<序一Unix网络I/O模型简介>

    Unix网络 I/O 模型   我们都知道,为了操作系统的安全性考虑,进程是无法直接操作I/O设备的,其必须通过系统调用请求内核来协助完成I/O动作,而内核会为每个I/O设备维护一个buffer.以下 ...

随机推荐

  1. iOS--MJRefresh的使用 上拉刷新和下拉加载

    1.一般使用MJRefresh 来实现上拉刷新和下拉加载功能 2.MJRefresh 下载地址:https://github.com/CoderMJLee/MJRefresh 3. MJRefresh ...

  2. 切割数组 - 将一个数组拆分成多个长度为n的数组

    有时候接口返回的数据很长,而前端显示需要分组显示这些数据,这个时候就需要将数组拆分: datas = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; var arrLen ...

  3. (转)MySQL优化笔记(八)--锁机制超详细解析(锁分类、事务并发、引擎并发控制)

    当一个系统访问量上来的时候,不只是数据库性能瓶颈问题了,数据库数据安全也会浮现,这时候合理使用数据库锁机制就显得异常重要了. 原文:http://www.jianshu.com/p/163c96983 ...

  4. Maven-pom.xml文件报错 Plugin execution not covered by lifecycle configuration

    问题: Eclipse中新导入的项目pom.xml文件报错: Plugin execution not covered by lifecycle configuration: org.jacoco:j ...

  5. 编写Android程序Eclipse连不上手机。

    主要问题有: 1.开发者选项没有开启 2.设备管理器中MTP有黄色小叹号 3.ADB异常. 问题1容易解决. 问题2,3困扰了我很长时间,网上的很多解决方法是下载安装MTP驱动,或者直接右击更新驱动. ...

  6. 09 - JavaSE之线程

    线程 线程的基本概念 线程是一个程序里面不同的执行路径. 进程与线程的区别 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换开销大. 线程可以看作轻量级的进程,同一类线程共享代码和数据空间 ...

  7. Redis笔记(七):Redis应用场景

    特性优势 1 支持持久化 Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用. 2 数据类型丰富 Redis不仅仅支持简单的key-value类型的数据,同时还 ...

  8. 使用httpClient连接池处理get或post请求

    以前有一个自己写的: http://www.cnblogs.com/wenbronk/p/6482706.html 后来发现一个前辈写的更好的, 再此感谢一下, 确实比我写的那个好用些 1, 创建一个 ...

  9. lucene源码分析(7)Analyzer分析

    1.Analyzer的使用 Analyzer使用在IndexWriter的构造方法 /** * Constructs a new IndexWriter per the settings given ...

  10. Beta阶段——Scrum 冲刺博客第五天

    一.当天站立式会议照片一张 二.每个人的工作 (有work item 的ID),并将其记录在码云项目管理中 昨天已完成的工作 完成部分answer界面的制作,将题目与用户输入的答案.正确答案依次列出来 ...