两组概念:
阻塞与非阻塞
阻塞与非阻塞是描述进程在访问某个资源时,数据是否准备就绪的一种处理方式。当数据没有准备就绪时:
阻塞:线程持续等待资源中数据准备完成,直到返回相应结果。
非阻塞:线程直接返回结果,不会持续等待资源准备数据结束后才响应结果。
关注的是单个进程的执行状态;
不管你这个结果是否准备好,如果准备好这个结果才返回-->阻塞;
不管你这个结果是否准备好,先给我一个结果-->非阻塞;
同步与异步
同步与异步是指访问数据的机制。
同步一般指主动请求并等待IO操作完成的方式。
异步则指主动请求数据后边可以继续处理其他任务,随后等待IO操作完毕的通知。
针对的是应用程序,关注程序之间的协作状态;
同步请求一定有一个结果;
异步请求是不需要等待你的结果;
用户空间(用户态)与内核空间(内核态)之间的数据传递过程 :
异步IO模型:
IO模型
一、传统BIO模型
- 是一种同步阻塞IO,IO在进行读写时,该线程将被阻塞,线程无法操作其他操作。
- 服务端接收到客户端请求后,为每一个客户端创建一个线程。
- 在高性能服务器应用领域,无法满足高性能,高并发场景。
- BIO服务端通信模型,由一个独立的Acceptor线程负责监听客户端的状态,Acceptor接收到客户端连接请求后,为每一个客户端建立一个新的线程进行链路处理。处理完后,通过输出流返回应答给客户端,线程销毁。
BIO模型存在的问题:
- 性能问题:一连接一线程模型导致服务端的并发接入数和系统吞吐量受到极大限制。
- 可靠性问题:由于IO操作采用同步阻塞模式,当网络拥堵或通信对端处理缓慢会导致IO线程被阻塞,且阻塞时间无法预测。
- 可维护性问题:IO线程数无法有效控制、资源无法有效共享(多线程并发问题),系统可维护性差。
阻塞IO模型
public class BioServer {
public static void main(String[] args) {
int port = 8080;
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
System.out.println("serverSocket启动,端口为:" + port);
Socket socket = null;
while (true) {
//等待客户端连接
socket = serverSocket.accept();
//来一个连接,创建一个线程
new Thread(new BioServerHandler(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
System.out.println("serverSocket关闭");
try {
serverSocket.close();
serverSocket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
class BioServerHandler implements Runnable {
private Socket socket;
public BioServerHandler(Socket socket) {
this.socket = socket;
}
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
//获取socket客户端传过来的输入流
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);
String currentTime = null;
String body = null;
while (true) {
body = in.readLine();//会阻塞客户端发送内容
if (body == null) {
break;
}
System.out.println("BIO Server(Thread:" + Thread.currentThread() + ") receive order " + body);
currentTime = "CORRECT ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
out.print(currentTime);
out.println(currentTime);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
in = null;
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
out.close();
out = null;
}
}
}
}
public class BioClient {
public static void main(String[] args) {
int port = 8080;
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket("127.0.0.1", port);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
out.println("CORRECT ORDER");
System.out.println("发送命令到服务端");
String resp = in.readLine();
System.out.println("服务端相应:" + resp);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
out.close();
out = null;
}
if (in != null) {
try {
in.close();
in = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
二、伪异步IO模型
- 以传统BIO模型为基础,通过线程池的方式维护所有的IO线程,实现相对高效的线程开销及管理。
- 当有新的客户端连接时,将客户端的Socket封装为一个Task,投递到一个线程池中进行处理。
伪异步IO会由于某些客户端应答时间过长,引起一些事故:
- 服务端处理缓慢,返回应答消息耗费60s,平常只需要10ms。
- 采用伪异步IO的线程正在读取故障服务节点的响应,由于读取输入流是阻塞的,它将会被同步阻塞60s。
- 如果所有的可用线程都被服务器阻塞,后续所有的IO消息都将在队列中排队。
- 由于线程池采用阻塞队列实现,当队列积满后,后续入队列的操作将被阻塞。
- 由于只有一个Acceptor线程接收客户端接入,它被阻塞在线程池的同步阻塞队列后,新的客户端请求消息将被拒绝,客户端会发生大量的链接超时。
- 由于几乎所有的连接都超时,调用者会认为系统已崩溃,无法接收新的请求消息。
三、NIO模型
- NIO(JDK1.4)模型是一种同步非阻塞IO。
- 主要有三大核心部分:Channel(通道)、Buffer(缓冲区)、Selector(多路复用器)。
- 传统IO基于字节流和字符流进行操作,而NIO基于Channel和Selector进行操作,数据总是从Channerl读取到Buffer,或者从Buffer写入到Channel。Selector用于监听多个Channel的事件(如:数据到达、连接打开)。因此,单个线程可以监听多个通道。
优点:
- 客户端发起的连接操作是异步的。
- SocketChannel的读写操作也是异步的。
- 线程模型的优化。
缺陷:
- 跨平台和兼容性问题:NIO依赖于操作系统,在linux和windows系统上表现不同。
- 扩展ByteBuffer:ByteBuffer允许包装一个byte[]来获得实例,可以尽量减少内存拷贝。但是它不能被扩展,因为它构造函数式私有的。
- epoll bug:可能会导致无效的状态和100%CPU
public class TimeServer {
public static void main(String[] args) {
int port=8081; //服务端默认端口
TimeServerHandler timeServer=new TimeServerHandler(port);
new Thread(timeServer, "NIO-TimeServerHandler-001").start();
}
}
public class TimeServerHandler implements Runnable {
private Selector selector;
private ServerSocketChannel serverChannel;
private volatile boolean stop;
public TimeServerHandler(int port) {
try {
//打开ServerSocketChannel
serverChannel = ServerSocketChannel.open();
//设置为非阻塞模式
serverChannel.configureBlocking(false);
//绑定监听的端口地址
serverChannel.socket().bind(new InetSocketAddress(port), 2);
//创建Selector线程
selector = Selector.open(); // 你干啥的? Selector
System.out.println("selector:"+selector);
//将ServerSocketChannel注册到Selector,交给Selector监听
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("The time server is start in port:"+port);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
public void stop(){
this.stop = true;
}
public void run() {
while(!stop){
try {
//通过Selector循环准备就绪的Channel===>Key
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
SelectionKey selectionKey = null;
while(iterator.hasNext()){
selectionKey = iterator.next();
iterator.remove();
try {
handleInput(selectionKey);
} catch (Exception e) {
if(selectionKey!=null){
selectionKey.cancel();
if(selectionKey.channel()!=null){
selectionKey.channel().close();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
if(selector !=null){
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey selectionKey) throws IOException {
//判断key是否有效
if(selectionKey.isValid()){
if (selectionKey.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
//多路复用器监听到新的客户端连接,处理连接请求,完成TCP三次握手。
SocketChannel client = server.accept();
System.out.println("socketChannel:"+client);
//设置为非阻塞模式
client.configureBlocking(false);
// 将新连接注册到多路复用器上,监听其读操作,读取客户端发送的消息。
client.register(selector, SelectionKey.OP_READ);
}
if(selectionKey.isReadable()){
SocketChannel client = (SocketChannel) selectionKey.channel();
ByteBuffer receivebuffer = ByteBuffer.allocate(1024);
//读取客户端请求消息到缓冲区
int count = client.read(receivebuffer); //非阻塞
if (count > 0) {
receivebuffer.flip();
//remaining()方法返回Buffer中剩余的可读数据长度
byte[] bytes = new byte[receivebuffer.remaining()];
//从缓冲区读取消息
receivebuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("The time server(Thread:"+Thread.currentThread()+") receive order : "+body);
//将currentTime响应给客户端(客户端Channel)
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
doWrite(client, currentTime);
}else if(count < 0){
selectionKey.cancel();
client.close();
}else{
}
}
}
}
private void doWrite(SocketChannel client, String currentTime) throws IOException {
if(currentTime != null && currentTime.trim().length()>0){
ByteBuffer sendbuffer = ByteBuffer.allocate(1024);
sendbuffer.put(currentTime.getBytes());
sendbuffer.flip();
//将客户端响应消息写入到客户端Channel中。
client.write(sendbuffer);
System.out.println("服务器端向客户端发送数据--:" + currentTime);
}else{
System.out.println("没有数据");
}
}
}
public class TimeServerClient {
public static void main(String[] args) {
int port=8080; //服务端默认端口
new Thread(new TimeClientHandler("127.0.0.1", port), "NIO-TimeServerClient-001").start();
}
}
public class TimeClientHandler implements Runnable {
private String host;
private int port;
private SocketChannel socketChannel;
private Selector selector;
private volatile boolean stop;
public TimeClientHandler(String host, int port) {
this.host = host;
this.port = port;
try {
//打开SocketChannel
socketChannel = SocketChannel.open();
//创建Selector线程
selector = Selector.open();
System.out.println("selector"+selector);
//设置为非阻塞模式
socketChannel.configureBlocking(false);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
public void run() {
try {
doConnect();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
while(!stop){
//轮训通道的状态
try {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
SelectionKey selectionKey = null;
while(iterator.hasNext()){
selectionKey = iterator.next();
iterator.remove();
try {
handleInput(selectionKey);
} catch (Exception e) {
if(selectionKey!=null){
selectionKey.cancel();
if(selectionKey.channel()!=null){
selectionKey.channel().close();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
if(selector !=null){
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey selectionKey) throws Exception {
if(selectionKey.isValid()){
SocketChannel client = (SocketChannel) selectionKey.channel();
System.out.println("client:"+client);
if (selectionKey.isConnectable()){
if(client.finishConnect()){
client.register(selector, SelectionKey.OP_READ);
doWrite(client);
}else{
System.exit(1);
}
}
if (selectionKey.isReadable()) {
ByteBuffer receivebuffer = ByteBuffer.allocate(1024);
int count = client.read(receivebuffer);
if (count > 0) {
receivebuffer.flip();
byte[] bytes = new byte[receivebuffer.remaining()]; //remaining()方法
receivebuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("Now is "+body);
this.stop = true;
}else if(count < 0){
selectionKey.channel();
client.close();
}else{
}
}
}
}
private void doConnect() throws Exception {
//连接服务端
boolean connect = socketChannel.connect(new InetSocketAddress(host, port));
//判断是否连接成功,如果连接成功,则监听Channel的读状态。
if(connect){
//监听客户端Channel的可读事件
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("socketChannel:"+socketChannel);
//写数据 写给服务端
doWrite(socketChannel);
}else{
//如果没有连接成功,则向多路复用器注册Connect状态
socketChannel.register(selector, SelectionKey.OP_CONNECT);
System.out.println("socketChannel:"+socketChannel);
}
}
private void doWrite(SocketChannel channel) throws IOException {
ByteBuffer sendbuffer = ByteBuffer.allocate(1024);
sendbuffer.put("QUERY TIME ORDER".getBytes());
sendbuffer.flip();
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//向Channel中写入客户端的请求指令 写到服务端
channel.write(sendbuffer);
if(!sendbuffer.hasRemaining()){
System.out.println("Send order to server succeed.");
}
}
}