阻塞io和非阻塞io的区别:阻塞 非阻塞 多路复用
阻塞io和非阻塞io的区别:阻塞 非阻塞 多路复用同步非阻塞IO之NIO:服务器端保存一个Socket连接列表,然后对这个列表进行轮询,如果发现某个Socket端口上有数据可读时说明读就绪,则调用该socket连接的相应读操作。如果发现某个 Socket端口上有数据可写时说明写就绪,则调用该socket连接的相应写操作。如果某个端口的Socket连接已经中断,则调用相应的析构方法关闭该端口。这样能充分利用服务器资源,效率得到了很大提高,在进行IO操作请求时候再用个线程去处理,是一个请求一个线程。Java中使用Selector、Channel、Buffer来实现上述效果。// 开启服务监听线程 当收到连接请求后 开启新的线程进行处理 public class ServerThread implements Runnable{ @Override public void run() { try {
BIO特点:
- 使用一个独立的线程维护一个socket连接,随着连接数量的增多,对虚拟机造成一定压力。
- 使用流来读取数据,流是阻塞的,当没有可读/可写数据时,线程等待,会造成资源的浪费。
常量:
public class Constant {
public static final String HOST = "127.0.0.1";
public static final int PORT = 8080;
}
主类:
public class ClientMain {
public static void main(String[] args) {
//开启服务
System.out.println("开启服务 监听端口:" Constant.PORT);
new Thread(new ServerThread()).start();
//建立一个socket客户端 发起请求
System.out.println("客户端 请求连接 并发送数据");
try {
Socket socket = new Socket(Constant.HOST Constant.PORT);
//开启新的线程处理socket连接
new Thread(new ClientProcessThread(socket)).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端监听线程:
// 开启服务监听线程 当收到连接请求后 开启新的线程进行处理
public class ServerThread implements Runnable{
@Override
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(Constant.PORT);
while (true){
Socket socket = serverSocket.accept();
new Thread(new ServerProcessThread(socket)).start();
//开启新的线程进行连接请求的处理
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端处理线程:
import java.io.*;
import java.net.Socket;
/**
* 服务端收到连接请求后 处理请求的线程 阻塞式IO
*/
public class ServerProcessThread implements Runnable {
private Socket socket;
public ServerProcessThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//获取客户端的数据 并写回
//等待响应
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = "";
String requestStr = "";
System.out.println("来自客户端的数据:"); // 读取客户端数据
while((line = bufferedReader.readLine()) != null){
requestStr = line;
System.out.println(line);
}
// 从服务端发给客户端数据
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("data from server " requestStr "\r\n");
writer.flush();
writer.close();
bufferedReader.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端:
/**
* 维护客户端socket连接的线程 阻塞式IO
*/
public class ClientProcessThread implements Runnable {
private Socket socket;
public ClientProcessThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//写数据 等待响应 输出响应
String requestStr = "data from client \r\n";
try {
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write(requestStr);
writer.flush();
socket.shutdownOutput();
//等待响应
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
System.out.println("来自服务端的响应:");
while((line = bufferedReader.readLine()) != null){
System.out.println(line);
}
writer.close();
bufferedReader.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
同步非阻塞IO之NIO:服务器端保存一个Socket连接列表,然后对这个列表进行轮询,如果发现某个Socket端口上有数据可读时说明读就绪,则调用该socket连接的相应读操作。如果发现某个 Socket端口上有数据可写时说明写就绪,则调用该socket连接的相应写操作。如果某个端口的Socket连接已经中断,则调用相应的析构方法关闭该端口。这样能充分利用服务器资源,效率得到了很大提高,在进行IO操作请求时候再用个线程去处理,是一个请求一个线程。Java中使用Selector、Channel、Buffer来实现上述效果。
每个线程中包含一个Selector对象,它相当于一个通道管理器,可以实现在一个线程中处理多个通道的目的,减少线程的创建数量。远程连接对应一个channel,数据的读写通过buffer均在同一个channel中完成,并且数据的读写是非阻塞的。通道创建后需要注册在selector中,同时需要为该通道注册感兴趣事件(客户端连接服务端事件、服务端接收客户端连接事件、读事件、写事件),selector线程需要采用轮训的方式调用selector的select函数,直到所有注册通道中有兴趣的事件发生,则返回,否则一直阻塞。而后循环处理所有就绪的感兴趣事件。以上步骤解决BIO的两个瓶颈:
- 不必对每个连接分别创建线程。
- 数据读写非阻塞。
下面对以下三个概念做一个简单介绍,Java NIO由以下三个核心部分组成:
- selector:Selector 允许单线程处理多个Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。要使用Selector,得向Selector注册Channel,然后调用他的select方法,这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子入有新连接接进来,数据接收等。
- Channel:基本上所有的IO在NIO中都从一个Channel开始。Channel有点像流,数据可以从channel读到buffer,也可以从buffer写到channel。
- Buffer:缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松的使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变换情况,Channel提供从文件,网络读取数据的渠道,但是读取或者写入的数据都必须经由Buffer。
channel和buffer有好几种类型。下面是Java NIO中的一些主要channel的实现:
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel
正如你所看到的,这些通道涵盖了UDP和TCP网络IO,以及文件IO。以下是Java NIO里关键的buffer实现:
ByteBuffer
CharBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
在微服务阶段,一个请求可能涉及到多个不同服务之间的跨服务器调用,如果你想实现高性能的PRC框架来进行数据传输,那就可以基于Java NIO做个支持长连接、自定义协议、高并发的框架,比如Netty。Netty本身就是一个基于NIO的网络框架, 封装了Java NIO那些复杂的底层细节,给你提供简单好用的抽象概念来编程。比如Dubbo底层就是用的Netty。
3.3 AIOAIO是异步非阻塞IO,相比NIO更进一步,进程读取数据时只负责发送跟接收指令,数据的准备工作完全由操作系统来处理。
参考资料推荐一个零声教育C/C 后台开发的免费公开课程,个人觉得老师讲得不错,分享给大家:C/C 后台开发高级架构师,内容包括Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,C/C Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂 立即学习
原文:阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO 一锅端