文章目錄
- 五種IO模型
- Linux設計哲學
- BIO
- NIO
- AIO
- SIO
- IO多路復用
五種IO模型
Linux設計哲學
在linux系統中,實際上所有的I/O設備都被抽象為了文件這個概念,一切皆文件,磁盤、網絡數據、終端,甚至進程間通信工具管道pipe等都被當做文件對待。
在了解多路復用
select、poll、epoll實現之前,我們先簡單回憶復習以下兩個概念:
多路: 指的是多個socket網絡連接;
復用: 指的是復用一個線程、使用一個線程來檢查多個文件描述符(Socket)的就緒狀態
多路復用主要有三種技術:select,poll,epoll。epoll是最新的, 也是目前最好的多路復用技術;
- blockingIO - 阻塞IO
- nonblockingIO - 非阻塞IO
- signaldrivenIO - 信號驅動IO
- asynchronousIO - 異步IO
- IOmultiplexing - IO多路復用
其中Java實現了BIO、NIO和AIO
BIO
BIO 是 Java 最早提供的 IO 模型,屬于同步阻塞式 IO。其特點是,在進行讀寫操作時,線程會被阻塞,直到操作完成。
工作原理:每處理一個連接,就需要創建一個獨立的線程。當有大量連接時,會消耗大量的系統資源,導致性能下降。
適用場景:適用于連接數目較少且固定的場景。
示例:
// 服務器端示例
ServerSocket serverSocket = new ServerSocket(8080);
while(true) {// 阻塞等待客戶端連接Socket socket = serverSocket.accept();// 為每個連接創建一個新線程處理new Thread(() -> {// 處理輸入流和輸出流// ...}).start();
}
進程/線程在從調用recvfrom開始到它返回的整段時間內是被阻塞的
recvfrom成功返回后,應用進程/線程開始處理數據報。
主要特點是進程阻塞掛起不消耗CPU資源,能及時響應每個操作;
實現難度低,適用并發量小的網絡應用開發,不適用并發量大的應用,因為一個請求IO會阻塞進程,所以每請求分配一個處理進程(線程)去響應,系統開銷大。
NIO
NIO 是 Java 1.4 引入的新 IO 模型,屬于同步非阻塞式 IO。它的核心組件有 Channel(通道)、Buffer(緩沖區)和 Selector(選擇器)。
工作原理:
Channel:可以進行雙向數據傳輸,類似于傳統 IO 中的流,但功能更強大。
Buffer:數據的讀寫都要通過緩沖區進行,這是一種雙向操作。
Selector:單個線程可以通過選擇器監控多個通道的事件,從而實現非阻塞 IO。
適用場景:適用于連接數目多且連接比較短(輕操作)的場景,例如聊天服務器。
代碼示例:
// 服務器端示例
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(8080));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);while (true) {// 非阻塞等待事件int readyChannels = selector.selectNow();if (readyChannels > 0) {Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();// 處理不同事件...keyIterator.remove();}}
}
進程發起IO系統調用后,如果內核緩沖區沒有數據,需要到IO設備中讀取,進程返回一個錯誤而不會被阻塞;進程發起IO系統調用后,如果內核緩沖區有數據,內核就會把數據返回進程。
進程輪詢(重復)調用,消耗CPU的資源;
實現難度低、開發應用相對阻塞IO模式較難;
適用并發量較小、且不需要及時響應的網絡應用開發;
AIO
AIO 是 Java 7 引入的異步 IO 模型,也被稱為 NIO 2.0。它基于事件和回調機制,實現了真正的異步非阻塞。
工作原理:應用程序只需發起 IO 操作,然后繼續執行其他任務。當 IO 操作完成后,系統會通過回調函數通知應用程序。
適用場景:適用于連接數目多且連接比較長(重操作)的場景,例如相冊服務器。
代碼示例
// 服務器端示例
AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8080));serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {@Overridepublic void completed(AsynchronousSocketChannel client, Void attachment) {// 接受下一個連接serverChannel.accept(null, this);// 處理客戶端連接...}@Overridepublic void failed(Throwable exc, Void attachment) {// 處理失敗情況}
});
當進程發起一個IO操作,進程返回(不阻塞),但也不能返回果結;內核把整個IO處理完后,會通知進程結果。如果IO操作成功則進程直接獲取到數據。
特點:
不阻塞,數據一步到位;Proactor模式;
需要操作系統的底層支持,LINUX 2.5 版本內核首現,2.6 版本產品的內核標準特性;
實現、開發應用難度大;
非常適合高性能高并發應用;
SIO
signaldrivenIO - 信號驅動IO
當進程發起一個IO操作,會向內核注冊一個信號處理函數,然后進程返回不阻塞;當內核數據就緒時會發送一個信號給進程,進程便在信號處理函數中調用IO讀取數據。
特點:回調機制,實現、開發應用難度大;
IO多路復用
大多數文件系統的默認IO操作都是緩存IO。在Linux的緩存IO機制中,操作系統會將IO的數據緩存在文件系統的頁緩存
(page cache)。也就是說,數據會先被拷貝到操作系統內核的緩沖區中,然后才會從操作系統內核的緩存區拷貝到應用程序的地址空間中。
這種做法的缺點就是,需要在應用程序地址空間和內核進行多次拷貝,這些拷貝動作所帶來的CPU以及內存開銷是非常大的。
至于為什么不能直接讓磁盤控制器把數據送到應用程序的地址空間中呢?最簡單的一個原因就是應用程序不能直接操作底層硬件。
總的來說,IO分兩階段:
1)數據準備階段
2)內核空間復制回用戶進程緩沖區階段