java中BIO、NIO、AIO都有啥区别

网友投稿 222 2022-10-28


java中BIO、NIO、AIO都有啥区别

一、BIO(Blocking IO,也被称作old IO)

同步阻塞模型,一个客户端连接对应一个处理线程

对于每一个新的网络连接都会分配给一个线程,每隔线程都独立处理自己负责的输入和输出, 也被称为Connection Per Thread模式

缺点:

1、IO代码里read操作是阻塞操作,如果连接不做数据读写操作会导致线程阻塞,浪费资源

2、如果线程很多,会导致服务器线程太多,压力太大,比如C10K问题

所谓c10k问题,指的是服务器同时支持成千上万个客户端的问题,也就是concurrent 10 000 connection

应用场景: BIO 方式适用于连接数目比较小且固定的架构, 这种方式对服务器资源要求比较高, 但程序简单易理解。

示例代码如下:

Bio服务端

import java.io.IOException;

import java.net.ServerSocket;

import java.net.Socket;

/**

* @Title:BIO的服务端

* @Author:wangchenggong

* @Date 2021/4/13 9:41

* @Description

* @Version

*/

public class SocketServer {

public static void main(String[] args) throws IOException {

ServerSocket serverSocket = new ServerSocket(9000);

while (true){

System.out.println("等待连接...");

Socket clientSocket = serverSocket.accept();

System.out.println("客户端"+clientSocket.getRemoteSocketAddress()+"连接了!");

handle(clientSocket);

}

}

private static void handle(Socket clientSocket) throws IOException{

byte[] bytes = new byte[1024];

int read = clientSocket.getInputStream().read(bytes);

System.out.println("read 客户端"+clientSocket.getRemoteSocketAddress()+"数据完毕");

if(read != -1){

System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));

}

clientSocket.getOutputStream().write("HelloClient".getBytes());

clientSocket.getOutputStream().flush();

}

}

Bio客户端

import java.io.IOException;

import java.net.Socket;

/**

* @Title:BIO的客户端

* @Author:wangchenggong

* @Date 2021/4/13 9:49

* @DescripqrEvAzNSdtion

* @Version

*/

public class SocketClient {

public static void main(String[] args) throws IOException {

Socket socket = new Socket("localhost", 9000);

//向服务端发送数据

socket.getOutputStream().write("HelloServer".getBytes());

socket.getOutputStream().flush();

System.out.println("向服务端发送数据结束");

byte[] bytes = new byte[1024];

//接收服务端回传的数据

socket.getInputStream().read(bytes);

System.out.println("接收到服务端的数据:" + new String(bytes));

socket.close();

}

}

二、NIO(Non Blocking IO,本意也作new IO)

同步非阻塞,服务器实现模式为 一个线程可以处理多个连接请求(连接),客户端发送的连接请求都会注册到多路复用器selector上,多路复用器轮询到连接有IO请求就进行处理,是在JDK1.4开始引入的。

应用场景:NIO方式适合连接数目多且连接比较短(轻操作)的架构,比如聊天服务器、弹幕系统、服务器之间通讯,编程相对复杂。

NIO 有三大核心组件: Channel(通道), Buffer(缓冲区),Selector(多路复用器)

1.channel类似于流,每个channel对应一个buffer缓冲区,buffer底层就是个数组

2.channel 会注册到selector上,由selector根据channel读写事件的发生将其交由某个空闲的线程处理

3.NIO的Buffer和Channel都是可读也可写的。

NIO的代码示例有两个

没有引入多路复用器的NIO

服务端

import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.ServerSocketChannel;

import java.nio.channels.SocketChannel;

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

/**

* @Title:Nio服务端

* @Author:wangchenggong

* @Date 2021/4/14 11:04

* @Description

* @Version

*/

public class NioServer {

/**

* 保存客户端连接

*/

static List channelList = new ArrayList<>();

public static void main(String[] args) throws IOException {

//创建Nio ServerSocketChannel

ServerSocketChannel serverSocket = ServerSocketChannel.open();

serverSocket.socket().bind(new InetSocketAddress(9000));

//设置ServerSocketChannel为非阻塞

serverSocket.configureBlocking(false);

System.out.println("Nio服务启动成功");

while(true){

//非阻塞模式accept方法不会阻塞

/// NIO的非阻塞是由操作系统内部实现的,底层调用了linux内核的accept函数

SocketChannel socketChannel = serverSocket.accept();

if(socketChannel != null){

System.out.println("连接成功");

socketChannel.configureBlocking(false);

channelList.add(socketChannel);

}

Iterator iterator = channelList.iterator();

while(iterator.hasNext()){

http:// SocketChannel sc = iterator.next();

ByteBuffer byteBuffer = ByteBuffer.allocate(128);

//非阻塞模式read方法不会阻塞

int len = sc.read(byteBuffer);

if(len > 0){

System.out.println("接收到消息:" + new String(byteBuffer.array()));

}else if(len == -1){

iterator.remove();

System.out.println("客户端断开连接");

}

}

}

}

}

客户端

import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.SocketChannel;

/**

* @Title:Nio客户端

* @Author:wangchenggong

* @Date 2021/4/14 11:36

* @Description

* @Version

*/

public class NioClient {

public static void main(String[] args) throws IOException {

SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("localhost", 9000));

socketChannel.configureBlocking(false);

ByteBuffer writeBuffer=ByteBuffer.wrap("HelloServer1".getBytes());

socketChannel.write(writeBuffer);

System.out.println("向服务端发送数据1结束");

writeBuffer = ByteBuffer.wrap("HelloServer2".getBytes());

socketChannel.write(writeBuffer);

System.out.println("向服务端发送数据2结束");

writeBuffer = ByteBuffer.wrap("HelloServer3".getBytes());

socketChannel.write(writeBuffer);

System.out.println("向服务端发送数据3结束");

}

}

引入了多路复用器的NIO

服务端

import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.*;

import java.util.Iterator;

import java.util.Set;

/**

* @Title:引入多路复用器后的NIO服务端

* @Author:wangchenggong

* @Date 2021/4/14 13:57

* @Description

* SelectionKey.OP_ACCEPT —— 接收连接继续事件,表示服务器监听到了客户连接,服务器可以接收这个连接了

* SelectionKey.OP_CONNECT —— 连接就绪事件,表示客户与服务器的连接已经建立成功

* SelectionKey.OP_READ —— 读就绪事件,表示通道中已经有了可读的数据,可以执行读操作了(通道目前有数据,可以进行读操作了)

* SelectionKey.OP_WRITE —— 写就绪事件,表示已经可以向通道写数据了(通道目前可以用于写操作)

*

* 1.当向通道中注册SelectionKey.OP_READ事件后,如果客户端有向缓存中write数据,下次轮询时,则会 isReadable()=true;

*

* 2.当向通道中注册SelectionKey.OP_WRITE事件后,这时你会发现当前轮询线程中isWritable()一直为true,如果不设置为其他事件

* @Version

*/

public class NioSelectorServer {

public static void main(String[] args) throws IOException {

/**

* 创建server端,并且向多路复用器注册,让多路复用器监听连接事件

*/

//创建ServerSocketChannel

ServerSocketChannel serverSocket = ServerSocketChannel.open();

serverSocket.socket().bind(new InetSocketAddress(9000));

//设置ServerSocketChannel为非阻塞

serverSocket.configureBlocking(false);

//打开selector处理channel,即创建epoll

Selector selector = Selector.open();

//把ServerSocketChannel注册到selector上,并且selector对客户端的accept连接操作感兴趣

serverSocket.register(selector, SelectionKey.OP_ACCEPT);

System.out.println("NioSelectorServer服务启动成功");

while(true){

//阻塞等待需要处理的事件发生

selector.select();

//获取selector中注册的全部事件的SelectionKey实例

Set selectionKeys = selector.selectedKeys();

Iterator iterator = selectionKeys.iterator();

//遍历selectionKeys,对事件进行处理

while (iterator.hasNext()){

SelectionKey key = iterator.next();

//如果是OP_ACCEPT事件,则进行连接和事件注册

if(key.isAcceptable()){

ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();

//接受客户端的连接

SocketChannel socketChannel = serverSocketChannel.accept();

socketChannel.configureBlocking(false);

//把SocketChannel注册到selector上,并且selector对客户端的read操作(即读取来自客户端的消息)感兴趣

socketChannel.register(selector, SelectionKey.OP_READ);

System.out.println("客户端"+socketChannel.getRemoteAddress()+"连接成功!");

}else if(key.isReadable()){

SocketChannel socketChannel = (SocketChannel) key.channel();

ByteBuffer byteBuffer = ByteBuffer.allocate(128);

int len = socketChannel.read(byteBuffer);

if(len > 0){

System.out.println("接收到客户端"+socketChannel.getRemoteAddress()+"发来的消息,消息内容为:"+new String(byteBuffer.array()));

}else if(len == -1){

System.out.println("客户端断开连接");

//关闭该客户端

socketChannel.close();

}

}

//从事件集合里删除本次处理的key,防止下次select重复处理

iterator.remove();

}

}

/**

* NioSelectorServer服务启动成功

* 客户端/127.0.0.1:57070连接成功!

* 接收到客户端/127.0.0.1:57070发来的消息,消息内容为:HelloServer

* 客户端/127.0.0.1:57121连接成功!

* 接收到客户端/127.0.0.1:57121发来的消息,消息内容为:HelloServer

*/

}

}

客户端

import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.SocketChannel;

import java.util.Iterator;

import java.util.Set;

/**

* @Title:引入多路复用器后的NIO客户端

* @Author:wangchenggong

* @Date 2021/4/14 14:39

* @Description

* @Version

*/

public class NioSelectorClient {

public static void main(String[] args) throws IOException {

SocketChannel socketChannel = SocketChannel.open();

socketChannel.configureBlocking(false);

Selector selector = Selector.open();

//要先向多路复用器注册,然后才可以跟服务端进行连接

socketChannel.register(selector, SelectionKey.OP_CONNECT);

socketChannel.connect(new InetSocketAddress("localhost", 9000));

while (true){

selector.select();

Set keys = selector.selectedKeys();

Iterator iterator = keys.iterator();

while (iterator.hasNext()){

SelectionKey key = iterator.next();

iterator.remove();

if (key.isConnectable()){

SocketChannel sc = (SocketChannel) key.channel();

if (sc.finishConnect()){

System.out.println("服务器连接成功");

ByteBuffer writeBuffer=ByteBuffer.wrap("HelloServer".getBytes());

sc.write(writeBuffer);

System.out.println("向服务端发送数据结束");

}

}

}

}

/**

* 服务器连接成功

* 向服务端发送数据结束

*/

}

}

三、AIO(Asynchronous IO) 即NIO2.0

异步非阻塞,由操作系统完成后回调通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。

应用场景:AIO方式适用于连接数目多且连接时间较长(重操作)的架构(应用),JDK7开始支持。

著名的异步网络通讯框架netty之所以废弃了AIO,原因是:在Linux系统上,NIO的底层实现使用了Epoll,而AIO的底层实现仍使用Epoll,没有很好实现AIO,因此在性能上没有明显的优势,而且被JDK封装了一层不容易深度优 化,Linux上AIO还不够成熟

AIO示例代码如下:

服务端

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.AsynchronousServerSocketChannel;

import java.nio.channels.AsynchronousSocketChannel;

import java.nio.channels.CompletionHandler;

/**

* @Title:Aio服务端

* @Author:wangchenggong

* @Date 2021/4/14 17:05

* @Description

* @Version

*/

public class AioServer {

public static void main(String[] args) throws Exception {

final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000));

serverChannel.accept(null, new CompletionHandler() {

@Override

public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {

try{

System.out.println("2--"+Thread.currentThread().getName());

//接收客户端连接

serverChannel.accept(attachment,this);

System.out.println("客户端"+socketChannel.getRemoteAddress()+"已连接");

ByteBuffer buffer = ByteBuffer.allocate(128);

socketChannel.read(buffer, null, new CompletionHandler() {

@Override

public void completed(Integer result, Object attachment) {

System.out.println("3--"+Thread.currentThread().getName());

//flip方法将Buffer从写模式切换到读模式

//如果没有,就是从文件最后开始读取的,当然读出来的都是byte=0时候的字符。通过buffer.flip();这个语句,就能把buffer的当前位置更改为buffer缓冲区的第一个位置

buffer.flip();

System.out.println(new String(buffer.array(), 0, result));

socketChannel.write(ByteBuffer.wrap("hello Aio Client!".getBytes()));

}

@Override

public void failed(Throwable exc, Object attachment) {

exc.printStackTrace();

}

});

}catch(Exception e){

e.printStackTrace();

}

}

@Override

public void failed(Throwable exc, Object attachment) {

}

});

System.out.println("1‐‐main"+Thread.currentThread().getName());

Thread.sleep(Integer.MAX_VALUE);

}

/**

* 1‐‐mainmain

* 2--Thread-9

* 客户端/127.0.0.1:54821已连接

* 3--Thread-8

* hello AIO server !

* 2--Thread-9

* 客户端/127.0.0.1:54942已连接

* 3--Thread-7

* hello AIO server !

*/

}

客户端

import java.net.InetSocketAddress;

import java.nio.Byhttp://teBuffer;

import java.nio.channels.AsynchronousSocketChannel;

/**

* @Title:Aio客户端

* @Author:wangchenggong

* @Date 2021/4/14 16:56

* @Description

* @Version

*/

public class AioClient {

public static void main(String[] args) throws Exception {

//创建Aio客户端

AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();

socketChannel.connect(new InetSocketAddress("localhost", 9000)).get();

//发送消息

socketChannel.write(ByteBuffer.wrap("hello AIO server !".getBytes()));

//接收消息

ByteBuffer buffer = ByteBuffer.allocate(128);

Integer len = socketChannel.read(buffer).get();

if(len != -1){

//客户端收到消息:hello Aio Client!

System.out.println("客户端收到消息:"+new String(buffer.array(), 0, len));

}

}

}

四、总结

BIO

NIO

AIO

IO模型

同步阻塞

同步非阻塞

异步非阻塞

编程难度

简单

复杂

复杂

可靠性 好

吞吐量


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:zabbix利用sendEmail发邮件
下一篇:cron计划任务
相关文章

 发表评论

暂时没有评论,来抢沙发吧~