3.5.3 SocketChannel 下面開始學(xué)習(xí)SocketChannel,它是使用最多的socket通道類: public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel { // 這里僅列出部分API public static SocketChannel open() throws IOException public static SocketChannel open(InetSocketAddress remote) throws IOException public abstract Socket socket(); public abstract boolean connect (SocketAddress remote) throws IOException; public abstract boolean isConnectionPending(); public abstract boolean finishConnect() throws IOException; public abstract boolean isConnected(); public final int validOps() } Socket和SocketChannel類封裝點(diǎn)對(duì)點(diǎn)、有序的網(wǎng)絡(luò)連接,類似于我們所熟知并喜愛的TCP/IP網(wǎng)絡(luò)連接。SocketChannel扮演客戶端發(fā)起同一個(gè)監(jiān)聽服務(wù)器的連接。直到連接成功,它才能收到數(shù)據(jù)并且只會(huì)從連接到的地址接收。(對(duì)于 ServerSocketChannel,由于涉及到validOps()方法,我們將在第四章檢查選擇器時(shí)進(jìn)行討論。通用的 read/write 方法也未在此列出,詳情請(qǐng)參考 3.1.2節(jié)。) 每個(gè)SocketChannel對(duì)象創(chuàng)建時(shí)都是同一個(gè)對(duì)等的java.net.Socket對(duì)象串聯(lián)的。靜態(tài)的open()方法可以創(chuàng)建一個(gè)新的SocketChannel對(duì)象,而在新創(chuàng)建的SocketChannel上調(diào)用socket()方法能返回它對(duì)等的Socket對(duì)象;在該Socket上調(diào)用getChannel()方法則能返回最初的那個(gè)SocketChannel。 雖然每個(gè)SocketChannel對(duì)象都會(huì)創(chuàng)建一個(gè)對(duì)等的Socket對(duì)象,反過來卻不成立。直接創(chuàng)建的Socket對(duì)象不會(huì)關(guān)聯(lián)SocketChannel對(duì)象,它們的getChannel()方法只返回null。 新創(chuàng)建的SocketChannel雖已打開卻是未連接的。在一個(gè)未連接的SocketChannel對(duì)象上嘗試一個(gè)I/O操作會(huì)導(dǎo)致NotYetConnectedException異常。我們可以通過在通道上直接調(diào)用connect()方法或在通道關(guān)聯(lián)的Socket對(duì)象上調(diào)用connect()來將該 socket 通道連接。一旦一個(gè) socket 通道被連接,它將保持連接狀態(tài)直到被關(guān)閉。您可以通過調(diào)用布爾型的isConnected()方法來測(cè)試某個(gè)SocketChannel當(dāng)前是否已連接。 第二種帶InetSocketAddress參數(shù)形式的open()是在返回之前進(jìn)行連接的便捷方法。這段代碼: SocketChannel socketChannel = SocketChannel.open (new InetSocketAddress ("somehost", somePort)); 等價(jià)于下面這段代碼: SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect (new InetSocketAddress ("somehost", somePort)); 如果您選擇使用傳統(tǒng)方式進(jìn)行連接——通過在對(duì)等Socket對(duì)象上調(diào)用connect()方法,那么傳統(tǒng)的連接語義將適用于此。線程在連接建立好或超時(shí)過期之前都將保持阻塞。如果您選擇通過在通道上直接調(diào)用connect()方法來建立連接并且通道處于阻塞模式(默認(rèn)模式),那么連接過程實(shí)際上是一樣的。 在SocketChannel上并沒有一種connect()方法可以讓您指定超時(shí)(timeout)值,當(dāng)connect()方法在非阻塞模式下被調(diào)用時(shí)SocketChannel提供并發(fā)連接:它發(fā)起對(duì)請(qǐng)求地址的連接并且立即返回值。如果返回值是true,說明連接立即建立了(這可能是本地環(huán)回連接);如果連接不能立即建立,connect()方法會(huì)返回false且并發(fā)地繼續(xù)連接建立過程。 面向流的的socket建立連接狀態(tài)需要一定的時(shí)間,因?yàn)閮蓚€(gè)待連接系統(tǒng)之間必須進(jìn)行包對(duì)話以建立維護(hù)流socket所需的狀態(tài)信息??缭介_放互聯(lián)網(wǎng)連接到遠(yuǎn)程系統(tǒng)會(huì)特別耗時(shí)。假如某個(gè)SocketChannel上當(dāng)前正由一個(gè)并發(fā)連接,isConnectPending()方法就會(huì)返回true值。 調(diào)用finishConnect()方法來完成連接過程,該方法任何時(shí)候都可以安全地進(jìn)行調(diào)用。假如在一個(gè)非阻塞模式的SocketChannel對(duì)象上調(diào)用finishConnect()方法,將可能出現(xiàn)下列情形之一: connect()方法尚未被調(diào)用。那么將產(chǎn)生NoConnectionPendingException異常。 連接建立過程正在進(jìn)行,尚未完成。那么什么都不會(huì)發(fā)生,finishConnect()方法會(huì)立即返回false值。 在非阻塞模式下調(diào)用connect()方法之后,SocketChannel又被切換回了阻塞模式。那么如果有必要的話,調(diào)用線程會(huì)阻塞直到連接建立完成,finishConnect()方法接著就會(huì)返回true值。 在初次調(diào)用connect()或最后一次調(diào)用finishConnect()之后,連接建立過程已經(jīng)完成。那么SocketChannel對(duì)象的內(nèi)部狀態(tài)將被更新到已連接狀態(tài),finishConnect()方法會(huì)返回true值,然后SocketChannel對(duì)象就可以被用來傳輸數(shù)據(jù)了。 連接已經(jīng)建立。那么什么都不會(huì)發(fā)生,finishConnect()方法會(huì)返回true值。 當(dāng)通道處于中間的連接等待(connection-pending)狀態(tài)時(shí),您只可以調(diào)用finishConnect()、isConnectPending()或isConnected()方法。一旦連接建立過程成功完成,isConnected()將返回true值。 InetSocketAddress addr = new InetSocketAddress(host, port); SocketChannel sc = SocketChannel.open(); sc.configureBlocking (false); sc.connect (addr); while ( !sc.finishConnect()) { doSomethingElse(); } doSomethingWithChannel(sc); sc.close(); 例 3-8 是一段用來管理異步連接的可用代碼。 /* *例 3-8 建立并發(fā)連接 */ package com.ronsoft.books.nio.channels; import java.nio.channels.SocketChannel; import java.net.InetSocketAddress; /** * Demonstrate asynchronous connection of a SocketChannel. * @author Ron Hitchens (ron@ronsoft.com) */ public class ConnectAsync { public static void main (String [] argv) throws Exception { String host = "localhost"; int port = 80; if (argv.length == 2) { host = argv[0]; port = Integer.parseInt (argv[1]); } InetSocketAddress addr = new InetSocketAddress(host, port); SocketChannel sc = SocketChannel.open(); sc.configureBlocking (false); System.out.println ("initiating connection"); sc.connect (addr); while ( !sc.finishConnect()) { doSomethingUseful(); } System.out.println ("connection established"); // Do something with the connected socket // The SocketChannel is still nonblocking sc.close(); } private static void doSomethingUseful() { System.out.println ("doing something useless"); } } 如果嘗試異步連接失敗,那么下次調(diào)用finishConnect()方法會(huì)產(chǎn)生一個(gè)適當(dāng)?shù)慕?jīng)檢查的異常以指出問題的性質(zhì)。通道然后就會(huì)被關(guān)閉并將不能被連接或再次使用。 與連接相關(guān)的方法使得我們可以對(duì)一個(gè)通道進(jìn)行輪詢并在連接進(jìn)行過程中判斷通道所處的狀態(tài)。第四章中,我們將了解到如何使用選擇器來避免進(jìn)行輪詢并在異步連接建立之后收到通知。 Socket通道是線程安全的。并發(fā)訪問時(shí)無需特別措施來保護(hù)發(fā)起訪問的多個(gè)線程,不過任何時(shí)候都只有一個(gè)讀操作和一個(gè)寫操作在進(jìn)行中。請(qǐng)記住,sockets 是面向流的而非包導(dǎo)向的。它們可以保證發(fā)送的字節(jié)會(huì)按照順序到達(dá)但無法承諾維持字節(jié)分組。某個(gè)發(fā)送器可能給一個(gè)socket寫入了20 個(gè)字節(jié)而接收器調(diào)用read()方法時(shí)卻只收到了其中的3個(gè)字節(jié)。剩下的17個(gè)字節(jié)還是傳輸中。由于這個(gè)原因,讓多個(gè)不配合的線程共享某個(gè)流 socket 的同一側(cè)絕非一個(gè)好的設(shè)計(jì)選擇。 connect()和finishConnect()方法是互相同步的,并且只要其中一個(gè)操作正在進(jìn)行,任何讀或?qū)懙姆椒ㄕ{(diào)用都會(huì)阻塞,即使是在非阻塞模式下。如果此情形下您有疑問或不能承受一個(gè)讀或?qū)懖僮髟谀硞€(gè)通道上阻塞,請(qǐng)用isConnected()方法測(cè)試一下連接狀態(tài)。 Java nio入門教程詳解(二十七)
|