一、概述
IO模型是指使用什么样的通道进行数据的发送和接收,其很大程度上决定了程序通讯的性能。Java共支持三种网络编程模型/IO模型:BIO、NIO、AIO。
1.1 BIO
BIO是传统阻塞IO模型(同步并阻塞),采用BIO模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,其在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,示意图如下:

该模型最大的问题是,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问呈1:1的关系,因此如果与客户端的连接不做任何事情,会造成不必要的线程开销。由于线程是非常宝贵的系统资源,当线程数膨胀之后,系统性能会急剧下降。该问题一定程度上可以通过线程池机制改善(实现多个客户连接),但是治标不治本。
1.2 NIO
NIO是同步非阻塞模型;服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器(Selector选择器)上,多路复用器轮询到连接有I/O请求就进行处理。

1.3 AIO
AIO模型为异步非阻塞模型,其没有被广泛使用。
二、BIO实例
2.1 实例
public class BIOServer {
//创建一个线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5,
10,
1,
TimeUnit.MINUTES,
new ArrayBlockingQueue<Runnable>(3),
new ThreadPoolExecutor.DiscardOldestPolicy());
/**
* 开启服务
* @param address
* @throws IOException
*/
public void init(int address) throws IOException {
ServerSocket serverSocket = new ServerSocket(address);
System.out.println("服务器启动!");
//开始监听客户端的连接
while(true){
//监听,等待客户端的连接
final Socket socket = serverSocket.accept();
System.out.println("连接到一个客户端!");
//创建一个线程,用于服务器与客户端之间的通讯
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
//与客户端进行交互
handler(socket);
}
});
}
}
/**
* 与客户端通讯
*/
public void handler(Socket socket){
byte[] bytes = new byte[1024];
//通过socket,获取一个输入流
try {
InputStream inputStream = socket.getInputStream();
//循环读取客户端发送的数据
while(true){
int len = inputStream.read(bytes);
if(len!=-1){
System.out.println(new String(bytes,0,len)); //输出客户端发送的数据
}else{
//读取完毕
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
System.out.println("关闭和client的连接");
try{
socket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
BIOServer myServer = new BIOServer();
try {
myServer.init(8001);
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2 问题分析
- 每个请求都需要创建独立的线程,与对应的客户端进行数据交互(Read和Write)
- 当并发数较大时,需要创建大量的线程,影响性能
- 连接建立之后,如果当前线程暂时没有数据可读,则线程就阻塞在Read操作上,造成线程浪费。
三、NIO简介
NIO指同步非阻塞I/O,NIO相关类都被放在Java.nio包及子包下。NIO有三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)。
NIO是面向缓冲区编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。
NIO的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
通俗理解:NIO 是可以做到用一个线程来处理多个操作的。假设有 10000 个请求过来,根据实际情况,可以分配 50 或者 100 个线程来处理。不像之前的阻塞 IO 那样,非得分配 10000 个。
示例:缓冲区Buffer:
flip() 读写转换!
import java.nio.IntBuffer;
public class BasicBuffer {
public static void main(String[] args) {
//举例说明Buffer的使用
//创建一个Buffer
IntBuffer intBuffer = IntBuffer.allocate(5);
//向Buffer中存放数据
for(int i=0;i<intBuffer.capacity();i++){
intBuffer.put(i*2);
}
//如何从Buffer中读取数据
/**
* 将buffer转换,读写切换
*/
intBuffer.flip();
while(intBuffer.hasRemaining()){
System.out.println(intBuffer.get());
}
}
}
四、NIO和BIO的比较
- BIO以流的方式处理数据,而NIO以块(缓冲)的方式处理数据,块I/O的效率比流I/O高很多
- BIO是阻塞的,NIO是非阻塞的
- BIO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区中写入到通道中。Selector(选择器)用于监听多个通道的事件(如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。
Selector(选择器)、Channel(通道)、Buffer(缓冲区)之间的关系:

1276

被折叠的 条评论
为什么被折叠?



