# 重要概念
阻塞IO
和非阻塞IO
这两个概念是 程序级别
的。主要描述的是程序请求操作系统 IO 操作后,如果 IO 资源没有准备好,那么程序该如何处理的问题:前者等待;后者继续执行 (并且使用线程一直轮询,直到有 IO 资源准备好了)。
程序级别的差异表现在开发人员如何编写代码来实现不同功能。
同步IO
和非同步IO
这两个概念是 操作系统级别
的。主要描述的是操作系统在收到程序请求 IO 操作后,如果 IO 资源没有准备好,该如何响应程序的问题:前者不响应,直到 IO 资源准备好以后;后者返回一个标记 (好让程序和自己知道以后的数据往哪里通知),当 IO 资源准备好以后,再用事件机制返回给程序。
# 传统的 BIO 通信简介
我们这里讲的 IO
是以套接字为数据载体的 BS
架构层面,以前大多数网络通信是阻塞模式:
客户端向服务器端发出请求后,客户端会一直等待 (不会再做其他事情),直到服务器端返回结果或者网络出现问题。
服务器端同样的,当在处理某个客户端 A 发来的请求时,另一个客户端 B 发来的请求会等待,直到服务器端的这个处理线程完成上一个处理。
尽管可以使用多线程,主线程接收请求,其他线程来处理请求。但是仍然存在局限,数据报文的接收仍然需要一个一个的来,并且在 Linux
系统中,可以创建的线程是有限的。每个线程都是较大的资源消耗, JVM
创建一个线程时,回味起分配堆栈空间,默认为 128K
。如果应用程序大量使用长连接,线程不会关闭,系统资源消耗容易失控(此处可以联想 QQ,如果不发消息,该线程也会被占用)。
# 代码实践
我们通过 socket
模拟 BIO
的实现逻辑
建立 Server
,建立 ServerSocket
对象,绑定端口,等待连接,如果连接成功就新建一个线程去处理连接
public class server { | |
private static Socket socket=null; | |
public static void main(String[] args) { | |
try { | |
// 绑定端口 | |
ServerSocket serverSocket = new ServerSocket(); | |
serverSocket.bind(new InetSocketAddress(8080)); | |
// 死循环一直等待客户端发送请求 | |
while (true){ | |
// 等待连接 阻塞 | |
System.out.println("等待连接"); | |
socket = serverSocket.accept(); | |
System.out.println("连接成功"); | |
// 连接成功后新开一个线程去处理这个连接 | |
new Thread(new Runnable() { | |
@Override | |
public void run() { | |
byte[] bytes = new byte[1024]; | |
try { | |
System.out.println("等待读取数据"); | |
// 等待读取数据 阻塞 | |
int length = socket.getInputStream().read(bytes); | |
System.out.println(new String(bytes,0,length)); | |
System.out.println("数据读取成功"); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
}).start(); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
建立 Client
-- 客户端代码
public class Client { | |
public static void main(String[] args) { | |
Socket socket= null; | |
try { | |
socket = new Socket("127.0.0.1",8080); | |
socket.getOutputStream().write("一条数据".getBytes()); | |
socket.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
从代码可以看到,当服务端接收到一个客户端请求时,会新开一个线程来执行任务,在 IO
时会被阻塞。从前面我们对各种 IO
讲解时知道, read
方法,如果 IO
没有准备好,就会阻塞。不论是阻塞 IO,非阻塞 IO,多路复用,信号驱动,都会在 read
方法存在阻塞的情况。
如果操作系统没有发现有套接字从指定的端口 X 来,那么操作系统就会等待。这样 serverSocket.accept()
方法就会一直等待。这就是为什么 accept()
方法为什么会阻塞:它内部的实现是使用的操作系统级别的同步 IO。
# 参考
掘金:https://juejin.cn/post/6974191624331100174
Java 全栈知识体系:https://pdai.tech/md/java/io/java-io-bio.html