在Unix
网络编程领域中,IO
模型一直是十分重要的话题。而且在去学习Redis
、Nginx
、Netty
等底层原理时,对于高并发的处理,基本都用到了IO
模型的概念。java
IO
模型分为阻塞IO
、非阻塞IO
、多路复用IO
、信号驱动IO
以及异步IO
,本文就其中最基础的阻塞式IO
进行讲解。git
BIO
:Blocking IO
,阻塞IO
,对应java.io
包。github
在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
条件下,常采用一下编程模型:异步
为了保证主线程不被阻塞,服务器能正常接受请求,采用多线程方式解决。socket
主线程Acceptor
不负责具体请求的处理,只负责接受请求,并建立相应的Handler
线程进行请求处理,全部阻塞发生在Handler
线程中,不影响主线程接受其余任务。
不要在主进程/主线程中处理任务的设计理念是值得学习的,主进程/主线程一旦挂了,整个节点都崩溃了,代价很大。
以下图所示,Nginx
中分为主进程和工做进程,主进程负责任务分发,工做进程负责任务处理,若是工做进程崩溃了,主进程再从新fork
工做进程,进行任务处理,整个节点依然可用。
根据经典的BIO
编程模型,全部请求须要新建Handler
线程处理。
new Thread(new Handler(socket)).start();
该模型存在一个致命的问题,当高并发时,形成系统中存在太多的线程,线程运行时的上下文频繁切换形成额外开销,给系统形成严重负担。
学院换了副主任,答辩及论文格式规定有所调整,目前还在改格式。
之后会就NIO
、IO
多路复用等经常使用模型进行学习。
本文做者: 河北工业大学梦云智开发团队 - 张喜硕