天天看點

Java網絡程式設計從入門到精通(31):非阻塞I/O簡介

本文為原創,如需轉載,請注明作者和出處,謝謝!

    在網絡應用中,一般可以采用同步I/O(阻塞I/O)和非阻塞I/O兩種方式進行資料通訊。這兩種方式并非互相排斥和互相取代。我們可以在平時的應用中單獨采用其中一種通訊方式,也可以混合使用這兩種通訊方式。在本文中就什麼是非阻塞I/O以及為什麼要使用這種通訊方式進行了介紹,在下一篇文章中給出了一個簡單的例子來示範在網絡應用中如何使用非阻塞I/O進行通訊。

一、什麼是非阻塞I/O

我們可以将同步I/O稱為阻塞I/O,非阻塞I/O稱為異步I/O。在本書中采用了比較常用的叫法:同步I/O和非阻塞I/O。雖然它們的叫法不同,但含義是一樣的。讀者在閱讀其他書時應注意這一點。

在講解什麼是非阻塞I/O之前,首先應了解什麼是同步I/O,這裡的同步指的是什麼。同步這個概念在程式設計中主要是指代碼按順序執行的過程。如在Java程式中的main方法,如果不使用多線程,這個方法中的代碼一定是從前往後按順序執行的。這就叫做同步。如果使用了多線程,從宏觀角度來看會有不同的代碼段同時執行,這就叫做異步。在同步I/O中的同步概念也類似,也就是說,在I/O通訊過程中,隻要是某一步的通訊未結束,就無法進行其他的通訊。那麼這裡的同步指的是什麼呢?要回答這個問題之前,首先讓我們先回憶一下,在網絡通訊中有哪些地方可能會被阻塞。如果讀者看了前面的章節就會知道答案。對于用戶端來說,有兩個地方可能會被阻塞:連接配接伺服器(調用connect方法時)和讀寫資料。而在服務端也有兩個地方可能會被阻塞:等待用戶端請求(調用accept方法時)和讀寫資料(在一般情況下,寫資料不會被阻塞,但如果網絡環境比較差的時候,用戶端和服務端的寫資料操作也可能發生阻塞現象)。也就是說,可以設定逾時時間的地方就可能被阻塞。而同步I/O中的同步就是指除了以下兩種情況外程式會一直處于等待狀态:

1. 連接配接伺服器、讀寫資料或等待用戶端請求正常地執行。

2. 在等待逾時時間後,抛出了逾時異常。

在上面我們了解了什麼是同步I/O。而非阻塞I/O和同步I/O最明顯的不同就是同步I/O所有可能被阻塞的位址在非阻塞I/O中都不會被阻塞。如在讀取資料時,如果資料暫時無法被讀取。那麼在非阻塞I/O中會立刻傳回,以便程式可以執行其他的代碼,然後系統會不斷偵測這個未完成的讀取操作,直到可以繼續讀資料時再來完成這個操作。

Java在JDK1.4及以後版本中提供了一套API來專門操作非阻塞I/O,我們可以在java.nio包及其子包中找到相關的類和接口。由于這套API是JDK新提供的I/O API,是以,也叫New I/O,這就是包名nio的由來。這套API由三個主要的部分組成:緩沖區(Buffers)、通道(Channels)和非阻塞I/O的核心類組成。這三部分的詳細内容将在本章的後面介紹。

二、為什麼要使用非阻塞I/O

在使用同步I/O的網絡應用中,如果要同時處理多個用戶端請求,或是在用戶端要同時和多個伺服器進行通訊,就必須使用多線程來處理。也就是說,将每一個用戶端請求配置設定給一個線程來單獨處理。這樣做雖然可以達到我們的要求,但同時又會帶來另外一個問題。由于每建立一個線程,就要為這個線程配置設定一定的記憶體空間(也叫工作存儲器),而且作業系統本身也對線程的總數有一定的限制。如果用戶端的請求過多,服務端程式可能會因為不堪重負而拒絕用戶端的請求,甚至伺服器可能會是以而癱瘓。

當然,可以使用線程池(将在第三部分講解)來緩解伺服器的壓力,但這并不能解決用戶端因通路過于密集而造成的伺服器拒絕響應的問題。雖然在服務端還有請求緩沖區作為保障,但這個緩沖區的大小是有限的(一般為50),如果用戶端的請求數遠超過這個數,用戶端還是會收到拒絕服務的資訊。

在這種情況下,使用非阻塞I/O就可以解決這個問題。由于使用非阻塞I/O的程式一般是單線程的(有時可能将使用非阻塞I/O的程式段放到一個單獨的線程裡,而主線程負責處理使用者的輸入),是以,服務端接收的用戶端請求數并不随着工作線程數的增加而增加。是以使用非阻塞I/O模式就不會受到作業系統對線程總數的限制,也不會占用大量的伺服器資源。

非阻塞I/O雖然可以到達在處理大量用戶端請求的同時,又不占用大量的伺服器資源的目的。但這種通訊方式并不能完全取代同步I/O。如非阻塞I/O并不适合象FTP伺服器那樣需要保持連接配接狀态的應用(原因将在以後的章節中說明)。非阻塞I/O一般應用在服務端比較多一些,因為用戶端一般并不需要處理大量的連接配接(但某些應用除外,如象百度、Google的Web Spider,需要同時下載下傳多個網頁,這時就需要在用戶端建立大量的連接配接來滿足需求),而服務端程式一般需要接收并處理大量的用戶端請求,是以,就需要使用多線程(使用同步I/O)或非阻塞I/O來達到這個目的。如果某個服務端應用處理的用戶端請求沒那麼多時,使用多線程和同步I/O可能會更好一點,因為這種方式要比非阻塞I/O方式更靈活。

在前面一直将非阻塞I/O和網絡應用放到一起講。其實非阻塞I/O并不等于網絡。我們也可以将非阻塞I/O應用到非網絡的應用中,如檔案複制。由于同步I/O是基于位元組流的,而非阻塞I/O是基于緩沖區和通道的。是以,從理論上,所操作的檔案越大,非阻塞I/O的優勢越能展現出來。而對于比較小的檔案操作,這兩種方式的效率差不多。根據實驗得知,複制一個4G左右的檔案,一般情況下,非阻塞I/O方式比同步I/O方式快大約15%左右。

<a href="http://www.eoeandroid.com/forumdisplay.php?fid=4">國内最棒的Google Android技術社群(eoeandroid),歡迎通路!</a>

繼續閱讀