一,I/O模型初识
所谓I/O模型,其实就是用什么样的通道对数据进行发送和接收,其很大的 程度上决定了当前陈程序通信的性能。
常见的有:
BIO: 同步并阻塞,具体实现模式: 一个请求连接对应一个线程,即客户端发送一个请求,服务器监听到之后,会给该请求创建独立的线程来进行处理。
NIO:同步非阻塞,具体实现模式:一个线程处理多个客户端请求,即客户端发送的请求会被注册到selector选择器上,通过selector选择器的select方法,多路复用轮询与客户端建立的连接通道,有事件响应就进行处理
AIO:异步非阻塞,相比前面两个引入了异步的概念,采用了Proactor模式,简化了程序编写,只有在有效的请求才启动线程去处理,特点: 首先由操作系统完成后,才通知服务端程序启动线程去处理,
注:(本篇主要讲解BIO模式,BIO,AIO就不延伸了)
二,应用场景
针对上面三种不同的模型,有不同的应用场景:
BIO:连接数较少且固定的架构,由于线程与请求一对一的关系,就导致对服务器的资源要求高,而且并发只能局限在应用中,JDK1.4之前的唯一选择
NIO:连接数多且连接时间短的架构(轻操作),比如,聊天服务器,弹幕系统及服务器之间的通讯(dubble的底层采用了netty组件,而netty就是基于NIO的模型),jdk 1.4开始支持
AIO:连接数目多且连接时间长的架构(重操作),比如相册服务器,文件下载等jdk7开始支持,但目前的应用市场小
三,BIO模型实例
为了加深理解,我们接下来分别写两个例子:
BIO实例:
要求:
1 使用BIO模型完成一个服务端,并监听指定端口,当有客户端进行连接时,就启动一个线程与之通信
2 优化,使用线程池机制优化,可以连接多个客户端
3 进阶,服务端可以接收客户端发送的数据并输出
package com.clcl.xxx.demo.优快云.BIO;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
public class BIODemo {
public static void main(String args[]) throws IOException {
//创建线程池,用来处理客户端的请求
ThreadPoolExecutor executor= new ThreadPoolExecutor(3,5,5, TimeUnit.SECONDS,new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
//创建服务端的网络通信对象
ServerSocket serverSocket=new ServerSocket();
//绑定监听端口
serverSocket.bind(new InetSocketAddress(6667));
System.out.println("服务器启动了。。。");
while(true){
//等待客户端的连接
//有连接,就会触发该方法,与客户端建立通信连接
// ----如果没有链接就会阻塞,so 该方法为阻塞方法
Socket socket = serverSocket.accept();
//与客户端进行通信
executor.execute(()->{
//另写一个方法用来处理业务逻辑
try {
System.out.println("线程"+Thread.currentThread().getName()+"与客户端"+socket.getRemoteSocketAddress()+"建立了连接");
handler(socket);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
public static void handler(Socket socket) throws IOException {
//BIO以数据流的形式交换数据
//通过socket获取输入流
byte[] bytes=new byte[1024];
InputStream stream = socket.getInputStream();
try {
//循环读取输入流
while (true) {
//将输入流的数据读取到字节数组中
int read = stream.read(bytes);
//判断当前读取是否为空
if (read == -1) {
break;
}
//输出读取的数据
System.out.println("客户端发送的数据是:" + new String(bytes, 0, read));
}
}catch (Exception e){
e.printStackTrace();
}
finally {
stream.close();
socket.close();
}
}
}
场景校验:
通过客户端发送一个消息给服务端,查看服务端是否会打印数据?
客户端
服务端:
结果:
可以看到上面,客户端发送数据之后,服务端立马就接收到了数据,但是我们要如何证明在BIO模型下,一个请求是对应一个独立的线程呢?
代码解析BIO原理:
原理: 一个请求对应一个独立的线程。
场景: 启动多个客户端,并打印出每次与客户端通信的线程名称
客户端2:
客户端3:
服务端:
结果: 可以发现,每来一个客户端请求,服务端都会启动一个独立的线程去处理,验证了 BIO模式下,一个请求对应一个独立的线程
再深入
BIO模型下的一些阻塞方法:
accept()
场景: 启动服务端,不做任何操作,可以发现,服务会一直阻塞在accept方法处
代码编辑: 在accept方法上下,添加标识
System.out.println("执行accept方法前,,,");
Socket socket = serverSocket.accept();
System.out.println("执行accept方法后,,,");
启动服务端:
结果: 可以发现,服务会一直阻塞在执行accept方法前,所以accept是个阻塞方法
read()
场景: 启动服务端,并创建一个客户端与之建立连接,不发送数据,查看服务是否会阻塞在read()方法
代码编辑:和上面一样,在read()方法上下添加标识
System.out.println("执行read方法之前");
int read = stream.read(bytes);
System.out.println("执行read方法之后");
服务端:
结果: 可以发现,服务又阻塞在了read方法,于是可以得到read也是个阻塞方法。
BIO缺陷:
看到这里可以发现BIO模型的一些缺陷:
1 每来一个客户端请求,服务端就要启动一个线程去执行读写事件,在客户端较多的情况下,对服务器的资源要求很高
2 在未与客户端建立请求时,主线程会一直阻塞在accept方法。
3 在与客户端建立请求之后,如果客户端未发送数据,那么该线程会一直阻塞在read方法,不能去执行其他任务,资源未被充分利用
好了,BIO模型到这里就算是解析完成了,后续下一篇会对NIO模型原理讲解,以上都是个人的一些看法,有不同滴看法或更好的意见,欢迎交流O(∩_∩)O