在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
多路複用等經常使用模型進行學習。
本文做者: 河北工業大學夢雲智開發團隊 - 張喜碩