首先明确各自的含义:
BIO:同步阻塞
NIO:同步非阻塞
AIO:异步非阻塞
引文1中详细介绍了三者的含义,总结如下:
-
Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
-
Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
-
Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,
BIO、NIO、AIO适用场景分析:
-
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
-
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
-
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
以下为实例,主要参考2,3,4,每一个实例都跑通了,针对客户端断开等问题也做了相应的处理,适合初学者跟着敲一下感受一下,关于更多的IO处理,接下来有时间会再进行整理。
- BIO实例解析
客户端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class NormalClient {
final static String ADDRESS = "127.0.0.1";
final static int PORT = 8888;
public static void main(String[] args) {
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);
out.println("接收到客户端的请求数据...");
out.println("你好服务器我是客户端!!!");
String response = in.readLine();
System.out.println("Client: " + response);
in.close();
// out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* BIO
*/
public class NormalServer {
final static int PORT = 8888;
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(PORT);
System.out.println("服务器开启......");
while(true){
Socket socket = server.accept();
//开启一个线程操作
new Thread(new ServerHandler(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
引文2中给出了一个BIO的变形,即将每一个连接开启一个线程改成通过线程池的方式来管理连接的线程,注意这并不是一个NIO的例子,只是对BIO的一种改进。客户端同上。
服务端:
/**
* 加入了线程池管理新开启的线程,BIO的改进版
*/
public class NormalServer2 {
final static int PORT = 8888;
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(PORT);
HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000);
System.out.println("服务器开启......");
while(true){
Socket socket = server.accept();
//线程池操作
executorPool.execute(new ServerHandler(socket));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
线程池管理:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class HandlerExecutorPool {
private ExecutorService executor;
public HandlerExecutorPool(int maxPoolSize,int queueSize){
executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors()
,maxPoolSize,120L, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(queueSize));
}
public void execute(Runnable runnable){
executor.execute(runnable);
}
}
- NIO实例解析
NIO主要是采用了三个新的概念,Buffer 缓冲区 、Channel 通道 、Selector 多路复用选择器。
Java1.7支持,具体的解析可以看引文2中的描述,此处给出完整的代码:
客户端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;
public class NewClient {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
ByteBuffer result_buffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = null;
try
{
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
if(socketChannel.finishConnect())
{
int i=0;
while(true)
{
TimeUnit.SECONDS.sleep(1);
String info = "I'm "+i+++"-th information from client";
buffer.clear();
buffer.put(info.getBytes());
buffer.flip();
while(buffer.hasRemaining()){
System.out.println(buffer);
socketChannel.write(buffer);
}
buffer.clear();
Thread.sleep(1000);
int relength = socketChannel.read(buffer);
byte[] bytes = new byte[relength];
buffer.flip();
buffer.get(bytes);
java.lang.String body = new java.lang.String(bytes).trim();
buffer.clear();
System.out.println(body);
}
}
}
catch (IOException | InterruptedException e)
{
e.printStackTrace();
} finally{
try{
if(socketChannel!=null){
socketChannel.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
}
服务端:
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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class NewSocketServer implements Runnable{
public static void main(String[] args) {
new Thread(new NewSocketServer(8888)).start();
}
//多路复用器
private Selector selector;
//读写缓冲区
private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
private String agreement = "idea-0329";
public NewSocketServer(int port){
try {
//打开多路复用器
selector = Selector.open();
//打开服务器通道
ServerSocketChannel socketChannel = ServerSocketChannel.open();
//设置通道为非阻塞
socketChannel.configureBlocking(false);
//绑定
socketChannel.bind(new InetSocketAddress(port));
//注册服务器通道并设置监听阻塞事件
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器开启..."+port);
//将String附件写入write中
writeBuffer.put(agreement.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true){
try {
//多路复用器开始监听
selector.select();
//返回监听结果
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
//遍历得到的监听结果
while(iterator.hasNext()){
//取出一个结果
SelectionKey key = iterator.next();
//移除
iterator.remove();
//判断是否是有效的请求
if(key.isValid()){
if (key.isAcceptable())
accept(key);
if (key.isValid()&&key.isReadable()){
read(key);
}
if (key.isValid()&&key.isWritable()){
write(key);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void accept(SelectionKey key){
try {
//获取当前服务器通道
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
//切换到客户端通道
SocketChannel sc = channel.accept();
//设置非阻塞
sc.configureBlocking(false);
//注册一个读事件,留意该客户端是否有数据进来
sc.register(selector,SelectionKey.OP_READ,writeBuffer);
} catch (IOException e) {
e.printStackTrace();
}
}
private void write(SelectionKey key){
SocketChannel socketChannel = (SocketChannel) key.channel();
writeBuffer.clear();
writeBuffer.put("ok reply...".getBytes());
writeBuffer.flip();// 这一步必须有
try {
socketChannel.write(writeBuffer);
} catch (IOException e) {
//当客户端出现连接异常的时候,关闭当前客户端通道,取消缓存key
try {
socketChannel.close();
} catch (IOException e1) {
e1.printStackTrace();
}
key.cancel();
// e.printStackTrace();
}
}
private void read(SelectionKey key){
readBuffer.clear();
//accept方法中注册的客户端通道
SocketChannel channel = (SocketChannel) key.channel();
try {
//读取数据
int count = channel.read(readBuffer);
//判读是否有数据可读
if(count == -1){
key.channel().close();
key.cancel();
return;
}
//读取
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes).trim();
System.out.println("收到信息:" + body);
write(key);
} catch (IOException e) {
try {
//当客户端出现连接异常的时候,关闭当前客户端通道,取消缓存key
channel.close();
key.cancel();
} catch (IOException e1) {
e1.printStackTrace();
}
//e.printStackTrace();
}
}
}
后记:作为一篇实例记录,关于概念的问题这里不再赘述,引文中都给出了比较详细的解释,但是在代码方面,引文中给出的例子比较片面,也存在一定的问题,本文实例经过测试,可以使用,作为一种记录,供后者来查询。
JAVA BIO与NIO、AIO的区别,https://blog.csdn.net/ty497122758/article/details/78979302 ↩︎
BIO,NIO与AIO的区别,https://www.cnblogs.com/barrywxx/p/8430790.html ↩︎ ↩︎ ↩︎
java 中AIO,BIO,NIO的区别,https://www.cnblogs.com/jinjian91/p/9069901.html ↩︎
reactor和proactor模式(epoll和iocp),https://blog.csdn.net/zccracker/article/details/38686339 ↩︎