共计 1533 个字符,预计需要花费 4 分钟才能阅读完成。
引言
在 Unix
网络编程领域中,IO
模型一直是十分重要的话题。并且在去学习 Redis
、Nginx
、Netty
等底层原理时,对于高并发的处理,基本都用到了 IO
模型的概念。
IO
模型分为阻塞 IO
、非阻塞IO
、多路复用IO
、信号驱动IO
以及异步 IO
,本文就其中最基础的阻塞式IO
进行讲解。
BIO
BIO
:Blocking IO
,阻塞 IO
,对应java.io
包。
在 Java 1.4
之前,提供了 java.io
包,阻塞 IO
编程模型。
假设我们需要在 Socket
(运输层TCP
) 的基础上实现一个 HTTP
服务器,HTTP
服务器网络编程采用阻塞 IO
实现,这里使用常用的输入输出流 BufferedReader
与BufferedWriter
。
请看如下示例代码,输入输出流采用经典的装饰器模式:
ServerSocket serverSocket = null;
try {serverSocket = new ServerSocket(SocketConstant.DEFAULT_PORT);
System.out.println("服务器启动于:" + SocketConstant.DEFAULT_PORT);
while (true) {Socket socket = serverSocket.accept();
System.out.println("客户端 [" + socket.getPort() + "] 发起连接");
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String msg;
while ((msg = reader.readLine()) != null) {// 处理网络请求}
}
} catch (IOException e) {e.printStackTrace();
} finally {// 回收资源}
问题出在 reader
读取数据时,代码如下:
String msg;
while ((msg = reader.readLine()) != null) {// 处理网络请求}
因为采用了阻塞 IO
类BufferedReader
进行数据读取,所以假设网络拥塞的情况下,该 TCP
连接迟迟没有数据发送,线程会一直被阻塞,因示例代码采用单线程模型,任务变成串行处理,无法继续处理其他请求。
所以在 BIO
条件下,常采用一下编程模型:
为了保证主线程不被阻塞,服务器能正常接受请求,采用多线程方式解决。
主线程 Acceptor
不负责具体请求的处理,只负责接受请求,并创建相应的 Handler
线程进行请求处理,所有阻塞发生在 Handler
线程中,不影响主线程接受其他任务。
不要在主进程 / 主线程中处理任务的设计理念是值得学习的,主进程 / 主线程一旦挂了,整个节点都崩溃了,代价很大。
如下图所示,Nginx
中分为主进程和工作进程,主进程负责任务分发,工作进程负责任务处理,如果工作进程崩溃了,主进程再重新 fork
工作进程,进行任务处理,整个节点依然可用。
根据经典的 BIO
编程模型,所有请求需要新建 Handler
线程处理。
new Thread(new Handler(socket)).start();
该模型存在一个致命的问题,当高并发时,造成系统中存在太多的线程,线程运行时的上下文频繁切换造成额外开销,给系统造成严重负担。
总结
学院换了副主任,答辩及论文格式规定有所调整,目前还在改格式。
以后会就 NIO
、IO
多路复用等常用模型进行学习。
版权声明
本文作者:河北工业大学梦云智开发团队 – 张喜硕