posts - 56,  comments - 12,  trackbacks - 0

          阻塞,還是非阻塞?這就是問(wèn)題所在。無(wú)論在程序員的頭腦中多么高貴……當(dāng)然這不是莎士比亞,本文提出了任何程序員在編寫(xiě) Internet 客戶(hù)程序時(shí)都應(yīng)該考慮的一個(gè)重要問(wèn)題。通信操作應(yīng)該是阻塞的還是非阻塞的?

          許多程序員在使用 Java 語(yǔ)言編寫(xiě) Internet 客戶(hù)程序時(shí)并沒(méi)有考慮這個(gè)問(wèn)題,主要是因?yàn)樵谝郧爸挥幸环N選擇――阻塞通信。但是現(xiàn)在 Java 程序員有了新的選擇,因此我們編寫(xiě)的每個(gè)客戶(hù)程序也許都應(yīng)該考慮一下。

          非阻塞通信在 Java 2 SDK 的 1.4 版被引入 Java 語(yǔ)言。如果您曾經(jīng)使用該版本編過(guò)程序,可能會(huì)對(duì)新的 I/O 庫(kù)(NIO)留下了印象。在引入它之前,非阻塞通信只有在實(shí)現(xiàn)第三方庫(kù)的時(shí)候才能使用,而第三方庫(kù)常常會(huì)給應(yīng)用程序引入缺陷。

          NIO 庫(kù)包含了文件、管道以及客戶(hù)機(jī)和服務(wù)器套接字的非阻塞功能。庫(kù)中缺少的一個(gè)特性是安全的非阻塞套接字連接。在 NIO 或者 JSSE 庫(kù)中沒(méi)有建立安全的非阻塞通道類(lèi),但這并不意味著不能使用安全的非阻塞通信。只不過(guò)稍微麻煩一點(diǎn)。

          要完全領(lǐng)會(huì)本文,您需要熟悉:

          • Java 套接字通信的概念。您也應(yīng)該實(shí)際編寫(xiě)過(guò)應(yīng)用程序。而且不只是打開(kāi)連接、讀取一行然后退出的簡(jiǎn)單應(yīng)用程序,應(yīng)該是實(shí)現(xiàn) POP3 或 HTTP 之類(lèi)協(xié)議的客戶(hù)機(jī)或通信庫(kù)這樣的程序。
          • SSL 基本概念和加密之類(lèi)的概念。基本上就是知道如何設(shè)置一個(gè)安全連接(但不必?fù)?dān)心 JSSE ――這就是關(guān)于它的一個(gè)“緊急教程”)。
          • NIO 庫(kù)。
          • 在您選擇的平臺(tái)上安裝 Java 2 SDK 1.4 或以后的版本。(我是在 Windows 98 上使用 1.4.1_01 版。)

          如果需要關(guān)于這些技術(shù)的介紹,請(qǐng)參閱 參考資料部分。

          那么到底什么是阻塞和非阻塞通信呢?

          阻塞和非阻塞通信

          阻塞通信意味著通信方法在嘗試訪(fǎng)問(wèn)套接字或者讀寫(xiě)數(shù)據(jù)時(shí)阻塞了對(duì)套接字的訪(fǎng)問(wèn)。在 JDK 1.4 之前,繞過(guò)阻塞限制的方法是無(wú)限制地使用線(xiàn)程,但這樣常常會(huì)造成大量的線(xiàn)程開(kāi)銷(xiāo),對(duì)系統(tǒng)的性能和可伸縮性產(chǎn)生影響。java.nio 包改變了這種狀況,允許服務(wù)器有效地使用 I/O 流,在合理的時(shí)間內(nèi)處理所服務(wù)的客戶(hù)請(qǐng)求。

          沒(méi) 有非阻塞通信,這個(gè)過(guò)程就像我所喜歡說(shuō)的“為所欲為”那樣。基本上,這個(gè)過(guò)程就是發(fā)送和讀取任何能夠發(fā)送/讀取的東西。如果沒(méi)有可以讀取的東西,它就中止 讀操作,做其他的事情直到能夠讀取為止。當(dāng)發(fā)送數(shù)據(jù)時(shí),該過(guò)程將試圖發(fā)送所有的數(shù)據(jù),但返回實(shí)際發(fā)送出的內(nèi)容。可能是全部數(shù)據(jù)、部分?jǐn)?shù)據(jù)或者根本沒(méi)有發(fā)送 數(shù)據(jù)。

          阻塞與非阻塞相比確實(shí)有一些優(yōu)點(diǎn),特別是遇到錯(cuò)誤控制問(wèn)題的時(shí)候。在阻塞套接字通信中,如果出現(xiàn)錯(cuò)誤,該訪(fǎng)問(wèn)會(huì)自動(dòng)返回標(biāo)志錯(cuò)誤的代碼。錯(cuò)誤可能是由于網(wǎng)絡(luò)超時(shí)、套接字關(guān)閉或者任何類(lèi)型的 I/O 錯(cuò)誤造成的。在非阻塞套接字通信中,該方法能夠處理的唯一錯(cuò)誤是網(wǎng)絡(luò)超時(shí)。為了檢測(cè)使用非阻塞通信的網(wǎng)絡(luò)超時(shí),需要編寫(xiě)稍微多一點(diǎn)的代碼,以確定自從上一次收到數(shù)據(jù)以來(lái)已經(jīng)多長(zhǎng)時(shí)間了。

          哪種方式更好取決于應(yīng)用程序。如果使用的是同步通信,如果數(shù)據(jù)不必在讀取任何數(shù)據(jù)之前處理的話(huà),阻塞通信更好一些,而非阻塞通信則提供了處理任何已經(jīng)讀取的數(shù)據(jù)的機(jī)會(huì)。而異步通信,如 IRC 和聊天客戶(hù)機(jī)則要求非阻塞通信以避免凍結(jié)套接字。





          回頁(yè)首


          創(chuàng)建傳統(tǒng)的非阻塞客戶(hù)機(jī)套接字

          Java NIO 庫(kù)使用通道而非流。通道可同時(shí)用于阻塞和非阻塞通信,但創(chuàng)建時(shí)默認(rèn)為非阻塞版本。但是所有的非阻塞通信都要通過(guò)一個(gè)名字中包含 Channel 的類(lèi)完成。在套接字通信中使用的類(lèi)是 SocketChannel, 而創(chuàng)建該類(lèi)的對(duì)象的過(guò)程不同于典型的套接字所用的過(guò)程,如清單 1 所示。


          清單 1. 創(chuàng)建并連接 SocketChannel 對(duì)象
          												
                      
          SocketChannel sc = SocketChannel.open();
          sc.connect("www.ibm.com",80);
          sc.finishConnect();

          必須聲明一個(gè) SocketChannel 類(lèi)型的指針,但是不能使用 new 操作符創(chuàng)建對(duì)象。相反,必須調(diào)用 SocketChannel 類(lèi)的一個(gè)靜態(tài)方法打開(kāi)通道。打開(kāi)通道后,可以通過(guò)調(diào)用 connect() 方法與它連接。但是當(dāng)該方法返回時(shí),套接字不一定是連接的。為了確保套接字已經(jīng)連接,必須接著調(diào)用 finishConnect()

          當(dāng)套接字連接之后,非阻塞通信就可以開(kāi)始使用 SocketChannel 類(lèi)的 read()write() 方法了。也可以把該對(duì)象強(qiáng)制轉(zhuǎn)換成單獨(dú)的 ReadableByteChannelWritableByteChannel 對(duì)象。無(wú)論哪種方式,都要對(duì)數(shù)據(jù)使用 Buffer 對(duì)象。因?yàn)?NIO 庫(kù)的使用超出了本文的范圍,我們不再對(duì)此進(jìn)一步討論。

          當(dāng)不再需要套接字時(shí),可以使用 close() 方法將其關(guān)閉:

          												
                      
          sc.close();

          這樣就會(huì)同時(shí)關(guān)閉套接字連接和底層的通信通道。





          回頁(yè)首


          創(chuàng)建替代的非阻塞的客戶(hù)機(jī)套接字

          上述方法比傳統(tǒng)的創(chuàng)建套接字連接的例程稍微麻煩一點(diǎn)。不過(guò),傳統(tǒng)的例程也能用于創(chuàng)建非阻塞套接字,不過(guò)需要增加幾個(gè)步驟以支持非阻塞通信。

          SocketChannel 對(duì)象中的底層通信包括兩個(gè) Channel 類(lèi): ReadableByteChannelWritableByteChannel。 這兩個(gè)類(lèi)可以分別從現(xiàn)有的 InputStreamOutputStream 阻塞流中使用 Channels 類(lèi)的 newChannel() 方法創(chuàng)建,如清單 2 所示:


          清單 2. 從流中派生通道
          												
                      
          ReadableByteChannel rbc = Channels.newChannel(s.getInputStream());
          WriteableByteChannel wbc = Channels.newChannel(s.getOutputStream());

          Channels 類(lèi)也用于把通道轉(zhuǎn)換成流或者 reader 和 writer。這似乎是把通信切換到阻塞模式,但并非如此。如果試圖讀取從通道派生的流,讀方法將拋出 IllegalBlockingModeException 異常。

          相反方向的轉(zhuǎn)換也是如此。不能使用 Channels 類(lèi)把流轉(zhuǎn)換成通道而指望進(jìn)行非阻塞通信。如果試圖讀從流派生的通道,讀仍然是阻塞的。但是像編程中的許多事情一樣,這一規(guī)則也有例外。

          這種例外適合于實(shí)現(xiàn) SelectableChannel 抽象類(lèi)的類(lèi)。 SelectableChannel 和它的派生類(lèi)能夠選擇使用阻塞或者非阻塞模式。 SocketChannel 就是這樣的一個(gè)派生類(lèi)。

          但是,為了能夠在兩者之間來(lái)回切換,接口必須作為 SelectableChannel 實(shí)現(xiàn)。對(duì)于套接字而言,為了實(shí)現(xiàn)這種能力必須使用 SocketChannel 而不是 Socket

          回顧一下,要?jiǎng)?chuàng)建套接字,首先必須像通常使用 Socket 類(lèi)那樣創(chuàng)建一個(gè)套接字。套接字連接之后,使用 清單 2中的兩行代碼把流轉(zhuǎn)換成通道。


          清單 3. 創(chuàng)建套接字的另一種方法
          												
                      
          Socket s = new Socket("www.ibm.com", 80);
          ReadableByteChannel rbc = Channels.newChannel(s.getInputStream());
          WriteableByteChannel wbc = Channels.newChannel(s.getOutputStream());

          如前所述,這樣并不能實(shí)現(xiàn)非阻塞套接字通信――所有的通信仍然在阻塞模式下。在這種情況下,非阻塞通信必須模擬實(shí)現(xiàn)。模擬層不需要多少代碼。讓我們來(lái)看一看。

          從模擬層讀數(shù)據(jù)

          模擬層在嘗試讀操作之前首先檢查數(shù)據(jù)的可用性。如果數(shù)據(jù)可讀則開(kāi)始讀。如果沒(méi)有數(shù)據(jù)可用,可能是因?yàn)樘捉幼直魂P(guān)閉,則返回表示這種情況的代碼。在清單 4 中要注意仍然使用了 ReadableByteChannel 讀,盡管 InputStream 完全可以執(zhí)行這個(gè)動(dòng)作。為什么這樣做呢?為了造成是 NIO 而不是模擬層執(zhí)行通信的假象。此外,還可以使模擬層與其他通道更容易結(jié)合,比如向文件通道內(nèi)寫(xiě)入數(shù)據(jù)。


          清單 4. 模擬非阻塞的讀操作
          												
                      
          /* The checkConnection method returns the character read when
          determining if a connection is open.
          */

          y = checkConnection();
          if(y <= 0) return y;

          buffer.putChar((char ) y);
          return rbc.read(buffer);

          向模擬層寫(xiě)入數(shù)據(jù)

          對(duì)于非阻塞通信,寫(xiě)操作只寫(xiě)入能夠?qū)懙臄?shù)據(jù)。發(fā)送緩沖區(qū)的大小和一次可以寫(xiě)入的數(shù)據(jù)多少有很大關(guān)系。緩沖區(qū)的大小可以通過(guò)調(diào)用 Socket 對(duì)象的 getSendBufferSize() 方法確定。在嘗試非阻塞寫(xiě)操作時(shí)必須考慮到這個(gè)大小。如果嘗試寫(xiě)入比緩沖塊更大的數(shù)據(jù),必須拆開(kāi)放到多個(gè)非阻塞寫(xiě)操作中。太大的單個(gè)寫(xiě)操作可能被阻塞。


          清單 5. 模擬非阻塞的寫(xiě)操作
          												
                      
          int x, y = s.getSendBufferSize(), z = 0;
          int expectedWrite;
          byte [] p = buffer.array();
          ByteBuffer buf = ByteBuffer.allocateDirect(y);

          /* If there isn't any data to write, return, otherwise flush the stream */

          if(buffer.remaining() == 0) return 0;
          os.flush()

          for(x = 0; x < p.length; x += y)
          {
          if(p.length - x < y)
          {
          buf.put(p, x, p.length - x);
          expectedWrite = p.length - x;
          }
          else
          {
          buf.put(p, x, y);
          expectedWrite = y;
          }

          /* Check the status of the socket to make sure it's still open */

          if(!s.isConnected()) break;

          /* Write the data to the stream, flushing immediately afterward */

          buf.flip();
          z = wbc.write(buf); os.flush();
          if(z < expectedWrite) break;
          buf.clear();

          }
          if(x > p.length) return p.length;
          else if(x == 0) return -1;
          else return x + z;

          與讀操作類(lèi)似,首先要檢查套接字是否仍然連接。但是如果把數(shù)據(jù)寫(xiě)入 WritableByteBuffer 對(duì)象,就像清單 5 那樣,該對(duì)象將自動(dòng)進(jìn)行檢查并在沒(méi)有連接時(shí)拋出必要的異常。在這個(gè)動(dòng)作之后開(kāi)始寫(xiě)數(shù)據(jù)之前,流必須立即被清空,以保證發(fā)送緩沖區(qū)中有發(fā)送數(shù)據(jù)的空間。任何 寫(xiě)操作都要這樣做。發(fā)送到塊中的數(shù)據(jù)與發(fā)送緩沖區(qū)的大小相同。執(zhí)行清除操作可以保證發(fā)送緩沖不會(huì)溢出而導(dǎo)致寫(xiě)操作被阻塞。

          因?yàn)榧俣▽?xiě)操作只能寫(xiě)入能夠?qū)懙膬?nèi)容,這個(gè)過(guò)程還必須檢查套接字保證它在每個(gè)數(shù)據(jù)塊寫(xiě)入后仍然是打開(kāi)的。如果在寫(xiě)入數(shù)據(jù)時(shí)套接字被關(guān)閉,則必須中止寫(xiě)操作并返回套接字關(guān)閉之前能夠發(fā)送的數(shù)據(jù)量。

          BufferedOutputReader 可用于模擬非阻塞寫(xiě)操作。如果試圖寫(xiě)入超過(guò)緩沖區(qū)兩倍長(zhǎng)度的數(shù)據(jù),則直接寫(xiě)入緩沖區(qū)整倍數(shù)長(zhǎng)度的數(shù)據(jù)(緩沖余下的數(shù)據(jù))。比如說(shuō),如果緩沖區(qū)的長(zhǎng)度是 256 字節(jié)而需要寫(xiě)入 529 字節(jié)的數(shù)據(jù),則該對(duì)象將清除當(dāng)前緩沖區(qū)、發(fā)送 512 字節(jié)然后保存剩下的 17 字節(jié)。

          對(duì)于非阻塞寫(xiě)而言,這并非我們所期望的。我們希望分次把數(shù)據(jù)寫(xiě)入同樣大小的緩沖區(qū)中,并最終把全部數(shù)據(jù)都寫(xiě)完。如果發(fā)送的大塊數(shù)據(jù)留下一些數(shù)據(jù)被緩沖,那么在所有數(shù)據(jù)被發(fā)送的時(shí)候,寫(xiě)操作就會(huì)被阻塞。

          模擬層類(lèi)模板

          整個(gè)模擬層可以放到一個(gè)類(lèi)中,以便更容易和應(yīng)用程序集成。如果要這樣做,我建議從 ByteChannel 派生這個(gè)類(lèi)。這個(gè)類(lèi)可以強(qiáng)制轉(zhuǎn)換成單獨(dú)的 ReadableByteChannelWritableByteChannel 類(lèi)。

          清單 6 給出了從 ByteChannel 派生的模擬層類(lèi)模板的一個(gè)例子。本文后面將一直使用這個(gè)類(lèi)表示通過(guò)阻塞連接執(zhí)行的非阻塞操作。


          清單 6. 模擬層的類(lèi)模板
          												
                      
          public class nbChannel implements ByteChannel
          {
          Socket s;
          InputStream is; OutputStream os;
          ReadableByteChannel rbc;
          WritableByteChannel wbc;

          public nbChannel(Socket socket);
          public int read(ByteBuffer dest);
          public int write(ByteBuffer src);
          public void close();

          protected int checkConnection();
          }

          使用模擬層創(chuàng)建套接字

          使用新建的模擬層創(chuàng)建套接字非常簡(jiǎn)單。只要像通常那樣創(chuàng)建 Socket 對(duì)象,然后創(chuàng)建 nbChannel 對(duì)象就可以了,如清單 7 所示:


          清單 7. 使用模擬層
          												
                      
          Socket s = new Socket("www.ibm.com", 80);
          nbChannel socketChannel = new nbChannel(s);
          ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
          WritableByteChannel wbc = (WritableByteChannel)socketChannel;





          回頁(yè)首


          創(chuàng)建傳統(tǒng)的非阻塞服務(wù)器套接字

          服務(wù)器端的非阻塞套接字和客戶(hù)端上的沒(méi)有很大差別。稍微麻煩一點(diǎn)的只是建立接受輸入連接的套接字。套接字必須通過(guò)從服務(wù)器套接字通道派生一個(gè)阻塞的服務(wù)器套接字綁定到阻塞模式。清單 8 列出了需要做的步驟。


          清單 8. 創(chuàng)建非阻塞的服務(wù)器套接字(SocketChannel)
          												
                      
          ServerSocketChannel ssc = ServerSocketChannel.open();
          ServerSocket ss = ssc.socket();
          ss.bind(new InetSocketAddress(port));
          SocketChannel sc = ssc.accept();

          與客戶(hù)機(jī)套接字通道相似,服務(wù)器套接字通道也必須打開(kāi)而不是使用 new 操作符或者構(gòu)造函數(shù)。在打開(kāi)之后,必須派生服務(wù)器套接字對(duì)象以便把套接字通道綁定到一個(gè)端口。一旦套接字被綁定,服務(wù)器套接字對(duì)象就可以丟棄了。

          通道使用 accept() 方法接收到來(lái)的連接并把它們轉(zhuǎn)給套接字通道。一旦接收了到來(lái)的連接并轉(zhuǎn)給套接字通道對(duì)象,通信就可以通過(guò) read()write() 方法開(kāi)始進(jìn)行了。





          回頁(yè)首


          創(chuàng)建替代的非阻塞服務(wù)器套接字

          實(shí)際上,并非真正的替代。因?yàn)榉?wù)器套接字通道必須使用服務(wù)器套接字對(duì)象綁定,為何不完全繞開(kāi)服務(wù)器套接字通道而僅使用服務(wù)器套接字對(duì)象呢?不過(guò)這里的通信不使用 SocketChannel ,而要使用模擬層 nbChannel。


          清單 9. 建立服務(wù)器套接字的另一種方法
          												
                      
          ServerSocket ss = new ServerSocket(port);
          Socket s = ss.accept();
          nbChannel socketChannel = new nbChannel(s);
          ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
          WritableByteChannel wbc = (WritableByteChannel)socketChannel;





          回頁(yè)首


          創(chuàng)建 SSL 連接

          創(chuàng)建SSL連接,我們要分別從客戶(hù)端和服務(wù)器端考察。

          從客戶(hù)端

          創(chuàng)建 SS L連接的傳統(tǒng)方法涉及到使用套接字工廠(chǎng)和其他一些東西。我將不會(huì)詳細(xì)討論如何創(chuàng)建SSL連接,不過(guò)有一本很好的教程,“Secure your sockets with JSSE”(請(qǐng)參閱 參考資料),從中您可以了解到更多的信息。

          創(chuàng)建 SSL 套接字的默認(rèn)方法非常簡(jiǎn)單,只包括幾個(gè)很短的步驟:

          1. 創(chuàng)建套接字工廠(chǎng)。
          2. 創(chuàng)建連接的套接字。
          3. 開(kāi)始握手。
          4. 派生流。
          5. 通信。

          清單 10 說(shuō)明了這些步驟:


          清單 10. 創(chuàng)建安全的客戶(hù)機(jī)套接字
          												
                      
          SSLSocketFactory sslFactory =
          (SSLSocketFactory)SSLSocketFactory.getDefault();
          SSLSocket ssl = (SSLSocket)sslFactory.createSocket(host, port);
          ssl.startHandshake();
          InputStream is = ssl.getInputStream();
          OutputStream os = ssl.getOutputStream();

          默認(rèn)方法不包括客戶(hù)驗(yàn)證、用戶(hù)證書(shū)和其他特定連接可能需要的東西。

          從服務(wù)器端

          建立SSL服務(wù)器連接的傳統(tǒng)方法稍微麻煩一點(diǎn),需要加上一些類(lèi)型轉(zhuǎn)換。因?yàn)檫@些超出了本文的范圍,我將不再進(jìn)一步介紹,而是說(shuō)說(shuō)支持SSL服務(wù)器連接的默認(rèn)方法。

          創(chuàng)建默認(rèn)的 SSL 服務(wù)器套接字也包括幾個(gè)很短的步驟:

          1. 創(chuàng)建服務(wù)器套接字工廠(chǎng)。
          2. 創(chuàng)建并綁定服務(wù)器套接字。
          3. 接受傳入的連接。
          4. 開(kāi)始握手。
          5. 派生流。
          6. 通信。

          盡管看起來(lái)似乎與客戶(hù)端的步驟相似,要注意這里去掉了很多安全選項(xiàng),比如客戶(hù)驗(yàn)證。

          清單 11 說(shuō)明這些步驟:


          清單 11. 創(chuàng)建安全的服務(wù)器套接字
          												
                      
          SSLServerSocketFactory sslssf =
          (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
          SSLServerSocket sslss = (SSLServerSocket)sslssf.createServerSocket(port);
          SSLSocket ssls = (SSLSocket)sslss.accept();
          ssls.startHandshake();
          InputStream is = ssls.getInputStream();
          OutputStream os = ssls.getOutputStream();





          回頁(yè)首


          創(chuàng)建安全的非阻塞連接

          要精心實(shí)現(xiàn)安全的非阻塞連接,也需要分別從客戶(hù)端和服務(wù)器端來(lái)看。

          從客戶(hù)端

          在客戶(hù)端建立安全的非阻塞連接非常簡(jiǎn)單:

          1. 創(chuàng)建并連接 Socket 對(duì)象。
          2. Socket 對(duì)象添加到模擬層上。
          3. 通過(guò)模擬層通信。

          清單 12 說(shuō)明了這些步驟:


          清單 12. 創(chuàng)建安全的客戶(hù)機(jī)連接
          												
                      
          /* Create the factory, then the secure socket */

          SSLSocketFactory sslFactory =
          (SSLSocketFactory)SSLSocketFactory.getDefault();
          SSLSocket ssl = (SSLSocket)sslFactory.createSocket(host, port);

          /* Start the handshake. Should be done before deriving channels */

          ssl.startHandshake();

          /* Put it into the emulation layer and create separate channels */

          nbChannel socketChannel = new nbChannel(ssl);
          ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
          WritableByteChannel wbc = (WritableByteChannel)socketChannel;

          利用前面給出的 模擬層類(lèi) 就可以實(shí)現(xiàn)非阻塞的安全連接。因?yàn)榘踩捉幼滞ǖ啦荒苁褂? SocketChannel 類(lèi)打開(kāi),而 Java API 中又沒(méi)有完成這項(xiàng)工作的類(lèi),所以創(chuàng)建了一個(gè)模擬類(lèi)。模擬類(lèi)可以實(shí)現(xiàn)非阻塞通信,無(wú)論使用安全套接字連接還是非安全套接字連接。

          列出的步驟包括默認(rèn)的安全設(shè)置。對(duì)于更高級(jí)的安全性,比如用戶(hù)證書(shū)和客戶(hù)驗(yàn)證, 參考資料 部分提供了說(shuō)明如何實(shí)現(xiàn)的文章。

          從服務(wù)器端

          在服務(wù)器端建立套接字需要對(duì)默認(rèn)安全稍加設(shè)置。但是一旦套接字被接收和路由,設(shè)置必須與客戶(hù)端的設(shè)置完全相同,如清單 13 所示:


          清單 13. 創(chuàng)建安全的非阻塞服務(wù)器套接字
          												
                      
          /* Create the factory, then the socket, and put it into listening mode */

          SSLServerSocketFactory sslssf =
          (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
          SSLServerSocket sslss = (SSLServerSocket)sslssf.createServerSocket(port);
          SSLSocket ssls = (SSLSocket)sslss.accept();

          /* Start the handshake on the new socket */

          ssls.startHandshake();

          /* Put it into the emulation layer and create separate channels */

          nbChannel socketChannel = new nbChannel(ssls);
          ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
          WritableByteChannel wbc = (WritableByteChannel)socketChannel;

          同樣,要記住這些步驟使用的是默認(rèn)安全設(shè)置。





          回頁(yè)首


          集成安全的和非安全的客戶(hù)機(jī)連接

          多數(shù) Internet 客戶(hù)機(jī)應(yīng)用程序,無(wú)論使用 Java 語(yǔ)言還是其他語(yǔ)言編寫(xiě),都需要提供安全和非安全連接。Java Secure Socket Extensions 庫(kù)使得這項(xiàng)工作非常容易,我最近在編寫(xiě)一個(gè) HTTP 客戶(hù)庫(kù)時(shí)就使用了這種方法。

          SSLSocket 類(lèi)派生自 Socket。 您可能已經(jīng)猜到我要怎么做了。所需要的只是該對(duì)象的一個(gè) Socket 指針。如果套接字連接不使用SSL,則可以像通常那樣創(chuàng)建套接字。如果要使用 SSL,就稍微麻煩一點(diǎn),但此后的代碼就很簡(jiǎn)單了。清單 14 給出了一個(gè)例子:


          清單 14. 集成安全的和非安全的客戶(hù)機(jī)連接
          												
                      
          Socket s;
          ReadableByteChannel rbc;
          WritableByteChannel wbc;
          nbChannel socketChannel;

          if(!useSSL) s = new Socket(host, port);
          else
          {
          SSLSocketFactory sslsf = SSLSocketFactory.getDefault();
          SSLSocket ssls = (SSLSocket)SSLSocketFactory.createSocket(host, port);
          ssls.startHandshake();
          s = ssls;
          }

          socketChannel = new nbChannel(s);
          rbc = (ReadableByteChannel)socketChannel;
          wbc = (WritableByteChannel)socketChannel;

          ...

          s.close();

          創(chuàng)建通道之后,如果套接字使用了SSL,那么就是安全通信,否則就是普通通信。如果使用了 SSL,關(guān)閉套接字將導(dǎo)致握手中止。

          這種設(shè)置的一種可能是使用兩個(gè)單獨(dú)的類(lèi)。一個(gè)類(lèi)負(fù)責(zé)處理通過(guò)套接字沿著與非安全套接字的連接進(jìn)行的所有通信。一個(gè)單獨(dú)的類(lèi)應(yīng)該負(fù)責(zé)創(chuàng)建安全的連接,包括安全連接的所有必要設(shè)置,無(wú)論是否是默認(rèn)的。安全類(lèi)應(yīng)該直接插入通信類(lèi),只有在使用安全連接時(shí)被調(diào)用。





          回頁(yè)首


          最簡(jiǎn)單的集成方法

          本文提出的方法是我所知道的把 JSSE 和 NIO 集成到同一代碼中以提供非阻塞安全通信的最簡(jiǎn)單方法。盡管還有其他方法,但是都需要準(zhǔn)備實(shí)現(xiàn)這一過(guò)程的程序員花費(fèi)大量的時(shí)間和精力。

          一種可能是使用 Java Cryptography Extensions 在 NIO 上實(shí)現(xiàn)自己的 SSL 層。另一種方法是修改現(xiàn)有的稱(chēng)為 EspreSSL (以前稱(chēng)為 jSSL)的定制 SSL 層, 把它改到 NIO 庫(kù)中。我建議只有在您有很充裕的時(shí)間時(shí)才使用這兩種方法。

          參考資料 部分的可下載 zip 文件提供了示例代碼,幫助您實(shí)踐本文所述的技術(shù),其中包括:

          • nbChannel,清單 7 所介紹的模擬層的源代碼。
          • 連接到 Verisign's Web 站點(diǎn)并下載主頁(yè)的示例 HTTPS 客戶(hù)程序。
          • 一個(gè)簡(jiǎn)單的非阻塞安全服務(wù)器 (Secure Daytime Server)。
          • 集成的安全和非安全客戶(hù)程序。




          回頁(yè)首


          參考資料





          回頁(yè)首


          關(guān)于作者


          Kenneth Ballard 目前在內(nèi)布拉斯加州珀魯州立大學(xué)學(xué)習(xí)計(jì)算機(jī)科學(xué)。他還是校內(nèi)的學(xué)生報(bào)紙 Peru State Times 的職業(yè)作者。他從愛(ài)荷華州 Creston 的 Southwestern Community College 獲得了計(jì)算機(jī)編程 Associate of Science 學(xué)位,在那里作為 PC 技術(shù)人員參加了一個(gè)工作效率研究項(xiàng)目。他的研究范圍包括 Java 技術(shù)、C++、COBOL、Visual Basic 和網(wǎng)絡(luò)。可以通過(guò) kenneth.ballard@ptk.org 與 Kenneth 聯(lián)系。

          posted on 2007-01-19 00:02 苦笑枯 閱讀(334) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): Java
          收藏來(lái)自互聯(lián)網(wǎng),僅供學(xué)習(xí)。若有侵權(quán),請(qǐng)與我聯(lián)系!

          <2007年1月>
          31123456
          78910111213
          14151617181920
          21222324252627
          28293031123
          45678910

          常用鏈接

          留言簿(2)

          隨筆分類(lèi)(56)

          隨筆檔案(56)

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 新营市| 安乡县| 普兰县| 桃江县| 闽侯县| 手机| 开原市| 保山市| 正定县| 宁陵县| 稻城县| 巫山县| 巴楚县| 定襄县| 金乡县| 信丰县| 增城市| 岳阳县| 安仁县| 龙海市| 烟台市| 阿克| 孟州市| 余干县| 古交市| 习水县| 肇东市| 营山县| 和平区| 桂东县| 新昌县| 施甸县| 永和县| 海丰县| 太白县| 布尔津县| 莱芜市| 张家界市| 达尔| 柳林县| 定兴县|