引言
在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
多路复用等常用模型进行学习。
版权声明
本文作者:河北工业大学梦云智开发团队 - 张喜硕
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。