📚Linux系统IO线程模型
在Linnux系统级别,提供了5种IO线程模型,分别是同步阻塞IO、同步非阻塞IO、多路服务用IO、信号驱动IO和异步IO。
在了解IO模型之前,我们要对用户空间和内核空间有一定的了解,在进行IO的时候,用户线程它不是直接操作的数据,而是把请求交给了操作系统,由操作系统与磁盘或网卡进行交互获取数据,之后,数据会被操作系统放入内核缓冲区,然后用户线程会从内核缓冲区服务数据到用户缓冲区。
对于同步阻塞IO,在数据被从内核缓冲区成功复制到用户空间缓冲区前,用户线程是被阻塞的,也就是说,直到数据被准备好以前,用户线程什么也做不了。
对于同步非阻塞线程,当数据读取请求交给操作系统后,用户线程就会返回,并不会一直阻塞,一般在这种情况下,需要不断轮询来确定数据是否已经在内核空间了,如何是,则会同步的将数据从内核空间复制到用户空间缓冲区。
以上两种IO模型,都是一个线程对应一个读写的,在并发量比较大的情况下,会造成线程数量暴增,而IO多路复用技术就是为了解决这个问题,在这种模型下,一个线程可以监听多个线程对应的IO数据是否已经就绪,如果就绪再进行后续处理,一般被用在网络IO上,而磁盘IO并不会用到这种技术。
对于信号驱动IO,可以与多路服务用IO进行对比着理解,多路复用IO在检查IO状态时是通过轮询的方式实现的,这种方式在被监听线程数量比较多时,效率比较低,而信号驱动IO指的是,不需要用户线程不断轮询,而是当数据被读取到内核缓冲区后,系统会自动通知用户线程,这样用户线程就能被动的知道哪些数据已经准备好了,从而避免不断轮询,目前Java NIO底层就是基于这种信号驱动IO实现的IO多路复用。
而异步IO把数据从内核缓冲区到用户缓冲区也进行了异步化,就是,当数据已经从内核缓冲区到用户缓冲区时,系统才会通知用户线程,这是真正意义的异步IO。
Java中的NIO是采用多路复用的IO模型,底层基于调用的是基于信号驱动实现的多路服务用IO,也就是我们常说的epoll。
📚Java NIO概述
Java目前支持的IO模型有BIO、NIO和AIO,NIO是在jdk1.4中被引入的,主要为了解决BIO服务无法应对高性能网络编程的问题,Java NIO底层基于epoll模型实现,编程模型中主要涉及Buffer、Channel和Selector等概念,其中Buffer是数据的载体在Channel中传递,Channel类似于BIO中流的概念,但Channel是全双工的,Selector是选择器,主要用于监听注册在他上面的Channel,Selector只适用于网络IO,对于文件IO,并没有Selector的概念。
下面看两个基于BIO和NIO开发的Demo
- BIO
package test.netty.bio;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
public class TimeServer {
public static void main(String[] args) throws Exception {
int port = 8080;
ServerSocket serverSocket = null ;
try {
serverSocket = new ServerSocket(port) ;
System.out.println("Time server is started in port " + port);
Socket socket = null ;
while (true) {
socket = serverSocket.accept();
new Thread(new TimeServerHanler(socket)).start();
}
}finally {
}
}
private static class TimeServerHanler implements Runnable {
private Socket socket ;
public TimeServerHanler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream())) ;
out = new PrintWriter(socket.getOutputStream());
String currentTime ;
String body ;
//while (true) {
body = in.readLine() ;
/*if(Objects.isNull(body)) {
break;
}*/
//}
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? dateFormat.format(new Date()) : "BAD ORDER";
out.println(currentTime);
}catch (Exception ex) {
try {
in.close();
out.close();
socket.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
package test.netty.bio;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
public class TimeClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 8080);
//socket.bind(new InetSocketAddress(8080));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())) ;
PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
printWriter.write("QUERY TIME ORDER");
//printWriter.ne
printWriter.flush();
System.out.println("send order 2 server success.");
String response = bufferedReader.readLine();
System.out.println("time server response : " + response);
}
}
- NIO
package test.netty.nio;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
public class TimeServer2 {
/**
* NIO Server
* 1. 打开ServerSocketChannel,监听客户端连接
* 2. 绑定监听端口,设置为非阻塞
* 3. 创建Reactor线程,创建多路复用器,并启动线程
* 4. 将ServerSocketChannel注册到Reactor线程 多路复用器上,监听accept事件
* 5. 多路复用器在while中无限循环 accept事件
* 6. 拿到客户端连接对应的SocketChannel
* 7. 将SocketChannel也设置为非阻塞模式
* 8. 将SocketChannel注册到Reactor线程的Selector上,并监听read事件
* 9. 读取数据到bytebuffer
* 10. 把数据交给业务线程池进行处理
* 11. 将encode后的pojo通过SocketChannel写回给客户端
* */
public static void main(String[] args) {
MultiplexerTimeServer multiplexerTimeServer = new MultiplexerTimeServer(8080);
//该线程为Reactor线程
new Thread(multiplexerTimeServer,"MultiplexerServer").start();
}
private static class MultiplexerTimeServer implements Runnable {
//Reactor Selector
private Selector selector ;
//Server Main Thread
private ServerSocketChannel serverSocketChannel ;
private volatile boolean stop;
public MultiplexerTimeServer(int port) {
init(port);
}
//init server
public void init(int port) {
try {
/**
*
* */
serverSocketChannel = ServerSocketChannel.open();
selector = Selector.open();