JDK的BIO, NIO, AIO
1. BIO
JDK5之前, JDK的IO模式只有BIO(同步阻塞)
问题: 因为阻塞的存在, 需对每个请求开启一个线程. 过多的线程切换影响操作系统性能
解决: 使用线程池, 处理不过来的放入队列, 再处理不过来的会触发其他机制
问题: 超过线程池数量的请求需要等待
public class Client { final static String ADDRESS = "127.0.0.1";
final static int PORT = 8765; public static void main(String[] args) throws IOException {
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket(ADDRESS, PORT);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true); // true自动flush
//向服务器端发送数据
out.println("来自客户端的请求");
//从服务端接收数据
String response = in.readLine(); // 阻塞
System.out.println("Client获取数据: " + response);
} catch (Exception e) {
e.printStackTrace();
} finally {
out.close();
in.close();
socket.close();
}
}
}
服务端1: 一个请求~一个线程
public class Server {
final static int PROT = 8765;
public static void main(String[] args) throws IOException {
ServerSocket server = null;
try {
server = new ServerSocket(PROT);
System.out.println("server start");
while(true){
Socket socket = server.accept(); //监听 阻塞 , socket底层会新建线程处理与客户端的三次握手
//建立线程处理获取的 socket
new Thread(new ServerHandler(socket)).start();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
server.close();
}
}
} class ServerHandler implements Runnable {
private Socket socket;
public ServerHandler(Socket socket) {
this.socket = socket;
} @Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);
String body = null;
while (true) {
body = in.readLine(); // 阻塞
if (body == null)
break;
System.out.println("Server获取的请求: " + body);
out.println("来自服务器的响应");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
in.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务端2: 用线程池处理请求
public class Server { final static int PORT = 8765; public static void main(String[] args) throws IOException {
ServerSocket server = null;
try {
server = new ServerSocket(PORT);
System.out.println("server start");
HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000);
while(true){
Socket socket = server.accept();
executorPool.execute(new ServerHandler(socket));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
server.close();
}
}
} class HandlerExecutorPool {
private ExecutorService executor;
public HandlerExecutorPool(int maxPoolSize, int queueSize){
this.executor = new ThreadPoolExecutor( // 带阻塞队列的线程池
Runtime.getRuntime().availableProcessors(), // 初始线程数
maxPoolSize, // 线程数上限 如果要处理请求的Runnable对象装满了队列, 则提高现有线程数
120L, // 如在120个时间颗粒内某线程是空闲的, 将被回收
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(queueSize) // 存放处理请求的Runnable对象
);
}
public void execute(Runnable task){
this.executor.execute(task);
}
} class ServerHandler implements Runnable {
private Socket socket;
public ServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);
String body = null;
while (true) {
body = in.readLine();
if (body == null)
break;
System.out.println("Server获取的请求: " + body); // 阻塞
out.println("来自服务器的响应");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
in.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.NIO1.0
JDK5以后引入了NIO1.0(多路复用机制)
伴随多路复用在程序中引入了如下概念:
Channel(通道):TCP连接的抽象,一个TCP连接对应多个Channel,这样减少TCP的连接次数。
通道与BIO中socket类似
通道与BIO中的流类似, 不过channel是双向的而流是单向的
channel有多种状态位, 能被selector识别
Buffer(缓冲区):
缓冲区是一块内存区域(数组), 在NIO中被包装成Buffer对象. Buffer提供方法用来访问该内存。
BIO中,数据存储在流中,而NIO中,数据存储在缓冲区中。
除了boolean的其他java七种基本类型都有相应的Buffer类. 最常使用的是ByteBuffer
Selector(多路复用器):负责轮询所有注册通道,根据通道状态执行相关操作。状态包括:Connect,Accept,Read,Write。
在"四种常用IO模型"里提过用select系统调用实现IO多路复用. 除select外Linux还提供了poll/epoll函数, 其中select/poll函数按顺序扫描文件句柄是否就绪,支持的文件句柄数有限; 而epoll使用基于事件驱动方式替代顺序扫描,性能更高, 对文件句柄数没有数量限制. JDK的Selector使用了epoll, 只需要一个线程轮询, 就可以接入大量的客户端.
public class Client { public static void main(String[] args) throws IOException {
SocketChannel sc = null;
ByteBuffer writeBuf = ByteBuffer.allocate(1024);
ByteBuffer readBuf = ByteBuffer.allocate(1024);
try {
//创建通道
sc = SocketChannel.open();
//进行连接
sc.connect(new InetSocketAddress("127.0.0.1", 8765));
// 下面步骤可以用selector轮询代替
while(true){
//定义一个字节数组,然后使用系统录入功能:
byte[] bytes1 = new byte[1024];
System.in.read(bytes1); //阻塞
//把数据放到缓冲区中
writeBuf.put(bytes1);
//对缓冲区进行复位
writeBuf.flip();
//写出数据
sc.write(writeBuf);
//清空缓冲区
writeBuf.clear(); // 接收服务端响应
sc.read(readBuf);
readBuf.flip();
byte[] bytes2 = new byte[readBuf.remaining()];
readBuf.get(bytes2);
readBuf.clear();
String body = new String(bytes2);
System.out.println("Client获取数据: " + body);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
sc.close();
}
}
}
通过改变Selector监听Channel的状态位, 控制与客户端读写的先后顺序
public class Server implements Runnable{
private Selector seletor;
private ByteBuffer readBuf = ByteBuffer.allocate(1024);
private ByteBuffer writeBuf = ByteBuffer.allocate(1024); public Server(int port){
try {
//1 创建多路复用器selector
this.seletor = Selector.open();
//2 创建ServerSocket通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//3 设置通道是否阻塞, 决定了通道了read/write/accept/connect方法是否阻塞
ssc.configureBlocking(false);
//4 设置通道地址
ssc.bind(new InetSocketAddress(port));
//5 将ServerSocket通道注册到selector上, 指定监听其accept事件
ssc.register(this.seletor, SelectionKey.OP_ACCEPT);
System.out.println("Server start");
} catch (IOException e) {
e.printStackTrace();
}
} @Override
public void run() {
while(true){
try {
// select阻塞, 监听相关事件
this.seletor.select();
// 解除阻塞, 返回选择key, key含有通道, 状态等信息
Iterator<SelectionKey> keysIter = this.seletor.selectedKeys().iterator();
// 进行遍历
while(keysIter.hasNext()){
SelectionKey key = keysIter.next();
keysIter.remove();
if (key.isValid()) {
// 等待接收连接状态
if (key.isAcceptable()) {
accept(key);
}
// 可读状态
if (key.isReadable()) {
read(key);
}
if (key.isWritable()) {
write(key);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
} private void write(SelectionKey key) {
try {
// 获取通道
SocketChannel sc = (SocketChannel) key.channel();
// 写回给客户端数据
writeBuf.put("来自服务器的响应".getBytes());
writeBuf.flip();
sc.write(writeBuf);
writeBuf.clear();
// 修改监听的状态位, 如果保持OP_WRITE会导致重复写
key.interestOps(SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
} private void read(SelectionKey key) {
try {
// 获取通道
SocketChannel sc = (SocketChannel) key.channel();
// 读取数据, 读到buffer. 按程序运行顺序, 这里sc是否设置为阻塞效果都一样
int count = sc.read(this.readBuf); // readBuf写时会改变position的值
if (count == -1) {
key.channel().close();
key.cancel(); //取消该通道在selector的注册, 之后不会被select轮询到
return;
}
// 有数据则进行读取. 读取前需要将position和limit进行复位
readBuf.flip();
// 根据缓冲区的数据长度创建相应大小的byte数组, 接收缓冲区的数据
byte[] bytes = new byte[this.readBuf.remaining()];
// 接收缓冲区数据
readBuf.get(bytes);
readBuf.clear();
String body = new String(bytes).trim();
System.out.println("Server获取的请求: " + body);
// 如果保持OP_READ会导致重复读
sc.register(this.seletor, SelectionKey.OP_WRITE);
} catch (IOException e) {
e.printStackTrace();
}
} private void accept(SelectionKey key) {
try {
// 获取服务通道
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
// 获取客户端通道.
SocketChannel sc = ssc.accept();
// 设置非阻塞模式
sc.configureBlocking(false);
// 将客户端通道注册到多路复用器上,指定监听事件
sc.register(this.seletor, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
} catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
new Thread(new Server(8765)).start();;
}
}
BIO客户端与NIO服务端通信需注意的:
BIO服务端, 一次IO有明确的结束点, 客户端再次read会返回-1
NIO服务端一次IO结束后, 没有关闭通道, 它可能把通道从读状态转为写状态. 于是selector不监听读了, 客户端再次read什么都没返回, 就会阻塞.
3.NIO2.0
JDK7引入了NIO2.0(即AIO)
NIO1.0中, IO过程没有阻塞, 阻塞被转移到了Selector轮询上. Selector管理所有的Channel, 因此能把总阻塞时间缩到最短.
NIO2.0中, 供我们调用的IO API都是非阻塞的, 背后复杂的实现过程(肯定有阻塞)被转移到了JDK底层和操作系统上. 我们的程序的IO调用可以做到立即返回.
同样有Channel和Buffer, 但没有Selector
public class Server {
//线程池
private ExecutorService executorService;
//异步通道线程组
private AsynchronousChannelGroup threadGroup;
//服务器通道
public AsynchronousServerSocketChannel assc; public Server(int port){
try {
//创建一个线程池
executorService = Executors.newCachedThreadPool();
//使用线程池创建异步通道线程组, 该线程组在底层支持着我们的异步操作
threadGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);
//使用 异步通道线程组 创建服务器通道
assc = AsynchronousServerSocketChannel.open(threadGroup);
//给通道绑定端口
assc.bind(new InetSocketAddress(port));
System.out.println("server start");
// 下面的accept不会阻塞 , 一个accept只能接收一个连接请求
// accept第一个参数: 被绑定到IO操作的关联对象(子类), 第二个参数 CompletionHandler<AsynchronousSocketChannel, 关联对象(父类)>, 操作成功后执行的回调句柄
// 如果接受了一个新的连接, 其结果AsynchronousSocketChannel会被绑定与assc通道到相同的AsynchronousChannelGroup
assc.accept(this, new ServerCompletionHandler());
// 这里为了避免程序结束, 异步通道线程组结束就不会执行回调了
Thread.sleep(Integer.MAX_VALUE);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Server(8765);
} }
//第一个参数: IO操作结果; 第二个参数: 被绑定到IO操作的关联对象
public class ServerCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, Server> { // 以下两个重载参数与CompletionHander的模板参数一致, 回调时被传入IO结果和IO操作时设置的关联对象
@Override
public void completed(AsynchronousSocketChannel asc, Server attachment) {
// 完成当前连接时, 首先, 为下一个客户端能接入再次调用accept异步方法
attachment.assc.accept(attachment, this);
// 其次, 执行下一步的读操作
read(asc);
}
@Override
public void failed(Throwable exc, Server attachment) {
exc.printStackTrace();
} private void read(final AsynchronousSocketChannel asc) {
//读取数据
ByteBuffer buf = ByteBuffer.allocate(1024);
// 第一个参数: 读操作的Buffer, 第二个参数: IO关联对象, 第三个参数:CompletionHandler<Integer, IO管理对象父类>
asc.read(buf, buf, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer resultSize, ByteBuffer attachment) {
//进行读取之后,重置标识位
attachment.flip();
//获得读取的字节数
System.out.println("Server端" + "收到客户端的数据长度为:" + resultSize);
//获取读取的数据
String resultData = new String(attachment.array()).trim();
System.out.println("Server端" + "收到客户端的数据信息为:" + resultData);
String response = "From服务端To客户端: 于" + new Date() + "收到了请求数据"+ resultData;
write(asc, response);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
} private void write(AsynchronousSocketChannel asc, String response) {
try {
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put(response.getBytes());
buf.flip();
// 写操作, 异步
Future<Integer> future = asc.write(buf);
// 阻塞等待结果
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
public class Client { private AsynchronousSocketChannel asc ;
public Client() throws Exception {
asc = AsynchronousSocketChannel.open();
} public void connect() throws InterruptedException, ExecutionException{
// get()阻塞
asc.connect(new InetSocketAddress("127.0.0.1", 8765)).get();
} public void write(String request){
try {
// get()阻塞
asc.write(ByteBuffer.wrap(request.getBytes())).get();
read();
} catch (Exception e) {
e.printStackTrace();
}
} private void read() throws IOException {
ByteBuffer buf = ByteBuffer.allocate(1024);
try {
// get()阻塞
asc.read(buf).get();
buf.flip();
byte[] respByte = new byte[buf.remaining()];
buf.get(respByte);
System.out.println(new String(respByte,"utf-8").trim());
// 关闭
asc.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} public static void main(String[] args) throws Exception {
Client c1 = new Client();
Client c2 = new Client();
c1.connect();
c2.connect(); c1.write("aa");
c2.write("bbb");
}
}
JDK的BIO, NIO, AIO的更多相关文章
- (转)也谈BIO | NIO | AIO (Java版)
原文地址: https://my.oschina.net/bluesky0leon/blog/132361 关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一 ...
- 也谈BIO | NIO | AIO (Java版--转)
关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一个解释: BIO | NIO | AIO,本身的描述都是在Java语言的基础上的.而描述IO,我们需要从两个 ...
- Netty5序章之BIO NIO AIO演变
Netty5序章之BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使 ...
- I/O模型系列之三:IO通信模型BIO NIO AIO
一.传统的BIO 网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请 ...
- 【netty】(1)---BIO NIO AIO演变
BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使用的技术. Net ...
- Netty序章之BIO NIO AIO演变
Netty序章之BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使用 ...
- java BIO/NIO/AIO 学习
一.了解Unix网络编程5种I/O模型 1.1.阻塞式I/O模型 阻塞I/O(blocking I/O)模型,进程调用recvfrom,其系统调用直到数据报到达且被拷贝到应用进程的缓冲区中或者发生错误 ...
- 转载:BIO | NIO | AIO
http://my.oschina.net/bluesky0leon/blog/132361 也谈BIO | NIO | AIO (Java版) 转载自:zheng-lee博客 发布时间: 201 ...
- BIO,NIO,AIO总结
熟练掌握 BIO,NIO,AIO 的基本概念以及一些常见问题是你准备面试的过程中不可或缺的一部分,另外这些知识点也是你学习 Netty 的基础. BIO,NIO,AIO 总结 1. BIO (Bloc ...
随机推荐
- XDU 1037 倍流畅序列(字符串匹配+贪心)
题目描述 对于一个0,1串s, 从左端开始读取它的0获得序列s0,从右端开始读取它的1获得s1,如果s0与s1同构,则称s为倍流畅序列.例如:011001是一个倍流畅序列, 因为:s0 = 0__00 ...
- 『NiFi 节点本地流与集群流不一致导致集群加入失败』问题解决
一.概述 在某些极端情况下,某些 NiFi 节点信息会由于用户强行 disconnect from cluster ,而出现 local flow 与 cluster 的 flow 不同步的问题. 此 ...
- curl 命令返回json结构human readable
在curl命令后面添加 | python -m json.tool 不想显示curl的统计信息,添加 -s参数 例: curl https://news-at.zhihu.com/api/4/news ...
- Ubuntu16.04中用et对jmeter生成的数据统计成图表
在Ubuntu系统中,用ctrl+Alt+t 打开终端: 输入et,即打开wps: 整理需要形成图表的数据,如: 用excel生成图表,如下: 表得出的性能图表,方法: 1.工具栏中选择插入——二维折 ...
- ISO8583
最开始时,金融系统只有IBM这些大的公司来提供设备,象各种主机与终端等.在各个计算机设备之间,需要交换数据.我们知道数据是通过网络来传送的,而在网络上传送的数据都是基于0或1这样的二进制数据,如果没有 ...
- RabbitMQ 高级指南
1 RabbitMQ 简介 1.1 介绍 RabbitMQ 是一个由 erlang 开发的基于 AMQP(Advanced Message Queue)协议的开源实现.用于在分布式系统中存储转发消息, ...
- saltstack之haproxy的安装配置
使用saltstack编译安装haproxy: 由于编译安装haproxy,所以安装之前需要建立编译环境,将编译环境需要安装的包单独放置在一个目录中,当编译haproxy或其他时,直接include这 ...
- uboot dm9000驱动故障
手头有一块6410开发板,已经有别人提供的uboot代码(基于2011.06),但是在检测dm9000时显示下面的输出: Net: No ethernet found. 当然其他网络命令例如ping等 ...
- 【前端】jQuery选择器$()的实现原理
今天三七互娱技术面试的时候面试官问了我这个问题,当时一脸懵逼,于是好好总结一下. 当我们使用jquery选择器的时候,$(s).回默认去执行jquery内部封装好的一个init的构造函数每次申明一个j ...
- [BZOJ2599]Race
Description 给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000 Input 第一行 两个整数 n, k 第二 ...