我们常说的io是文件或字符的输入(input)和输出(output)
什么是BIO?
同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。
在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。
但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。
一个java BIO实例如下
服务端代码展示
public class BioServerDemo {
public static void main(String[] args) {
int port = 9999;
try {
// 创建 bio socket服务
ServerSocket serverSocket = new ServerSocket();
// 绑定服务端口号
serverSocket.bind(new InetSocketAddress(port));
while (true){
// 阻塞获得客户端socket
Socket socket = serverSocket.accept();
// 进入自定义的handle方法,处理客户端的socket
new Thread(()-> handle(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 自定义handle方法,处理服务端和客户端的socket
private static void handle(Socket socket) {
byte[] bytes = new byte[1024];
try {
// 获得输入流,读出客户端发送的数据
int len = socket.getInputStream().read(bytes);
System.out.println(new String(bytes,0,len));
// 获得输出流,回写给客户端数据
socket.getOutputStream().write("this is server".getBytes());
socket.getOutputStream().flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端代码展示
public class BioClientDemo {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 9999;
try {
Socket socket = new Socket(host,port);
// 获得输出流,向服务端写数据
socket.getOutputStream().write("hello,this is client".getBytes());
socket.getOutputStream().flush();
// 获得输入流,读取服务端给客户端的数据
byte[] bytes = new byte[1024];
int len = socket.getInputStream().read(bytes);
System.out.println(new String(bytes,0,len));
} catch (IOException e) {
e.printStackTrace();
}
}
}
优点: 模型简单,编码简单
缺点:性能较低
小结:
1 BIO 主要体现在 获得客户端连接socket的时候,在阻塞accept,在一个socket中进行读写时,也会一直阻塞,会一直等待这个线程处理完结束
2 每个连接都会创建一个线程,随着线程数量的增加,CPU切换线程上下文的消耗也随之增加,在高过某个阀值后,继续增加线程,性能不增反降
3 因为一个连接创建一个线程,模型比较简单,适用于客户端连接教少的场景,可以进行简单处理
什么是NIO?
NIO 称为 Non-Block IO, 它支持面向缓冲的,基于通道的I/O操作方法。
NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。
阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。
含有选择器,选择器用于使用单个线程处理多个通道。
它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。
为了提高系统效率选择器是有用的。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
线程模型示例
会有一个线程作为selector(选择器)进行处理所有的客户端,通过事件监听的形式处理socketChannel
java NIO 单线程实现
服务端代码
public class NioServerDemo {
public static void main(String[] args) throws IOException {
int port = 9999;
// 创建 nio server
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
System.out.println("server start");
// 获得selector
Selector selector = Selector.open();
// 把selector和accept事件注册到server中
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
selector.select();
// 获得selector管理的所有事件
Set<SelectionKey> selectors = selector.selectedKeys();
// 迭代selector
Iterator<SelectionKey> selectionKeyIterable = selectors.iterator();
while (selectionKeyIterable.hasNext()){
SelectionKey key = selectionKeyIterable.next();
// 处理每一个 selectionKey
handle(key);
// 处理完之后移除
selectionKeyIterable.remove();
}
}
}
private static void handle(SelectionKey key){
// 处理accept事件
if (key.isAcceptable()){
// 获得serversocket channel,处理对客户端连接的socket
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
try {
SocketChannel socketChannel = serverSocketChannel.accept();
serverSocketChannel.configureBlocking(false);
// 注册读事件
serverSocketChannel.register(key.selector(),SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}else if (key.isReadable()){
// 处理读事件
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
byteBuffer.clear();
int len = 0;
try {
len = socketChannel.read(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
if (len != -1){
System.out.println(new String(byteBuffer.array(),0,len));
}
// 向客户端写入数据
ByteBuffer bufferWrite = ByteBuffer.wrap("hello,this is server".getBytes());
try {
socketChannel.write(bufferWrite);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
多线程实现
public class NioThreadPoolServerDemo {
private ExecutorService executorService = Executors.newFixedThreadPool(20);
private Selector selector;
public static void main(String[] args) throws IOException {
int port = 9999;
NioThreadPoolServerDemo nioThreadPoolServerDemo = new NioThreadPoolServerDemo();
nioThreadPoolServerDemo.initServer(port);
nioThreadPoolServerDemo.listen();
}
private void initServer(int port) throws IOException {
// 创建bio 服务
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
// 开启selector 模型
this.selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
private void listen() throws IOException {
while (true){
selector.select();
Set<SelectionKey> selectionKeySet = selector.selectedKeys();
Iterator<SelectionKey> selectionKeyIterator = selectionKeySet.iterator();
if (selectionKeyIterator.hasNext()){
SelectionKey key = selectionKeyIterator.next();
selectionKeyIterator.remove();
// 处理accept事件
if (key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel sc = server.accept();
sc.configureBlocking(false);
// 注册read事件
sc.register(this.selector,SelectionKey.OP_READ);
}else if (key.isReadable()){
key.interestOps(SelectionKey.OP_READ);
// 有数据读写时,交给线程池处理,
executorService.execute(new ThreadHandlerChannel(key));
}
}
}
}
}
class ThreadHandlerChannel extends Thread{
private SelectionKey key;
ThreadHandlerChannel(SelectionKey key){
this.key = key;
}
@Override
public void run() {
// 处理读事件
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
byteBuffer.clear();
int len = 0;
try {
len = socketChannel.read(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
if (len != -1){
System.out.println(new String(byteBuffer.array(),0,len));
}
// 向客户端写入数据
ByteBuffer bufferWrite = ByteBuffer.wrap("hello,this is server".getBytes());
try {
socketChannel.write(bufferWrite);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
小结
1 通过选择器selector进行事件监听
2 需要主动把socket事件注册到选择器中
AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。
对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。
public class AioServerDemo {
public static void main(String[] args) throws IOException {
int port = 9999;
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open()
.bind(new InetSocketAddress(port));
// 当有客户端连接时,accept不再阻塞线程,交给CompletionHandler 进行处理
serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel client, Object attachment) {
serverSocketChannel.accept(null,this);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 读数据也不再阻塞执行,读完数据后交给CompletionHandler处理
client.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
client.write(ByteBuffer.wrap("this is server".getBytes()));
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {}
});
}
@Override
public void failed(Throwable exc, Object attachment) {}
});
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
小结:
1 NIO和AIO在linux底层都是用epoll实现
2 windows上AIO用的是系统实现
1 BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
2 NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
3 AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。