轉(zhuǎn)一篇socket的文章,很有用:

Socket類表現(xiàn)了客戶端套接字,它是屬于一臺或兩臺計算機(jī)的兩個TCP通訊端口之間的通訊通道.

一:Socket類
  Socket類表現(xiàn)了客戶端套接字,它是屬于一臺或兩臺計算機(jī)的兩個TCP通訊端口之間的通訊通道。端口可以連接到本地系統(tǒng)的另一個端口,這樣可以避免使用另一臺計算機(jī),但是大多數(shù)網(wǎng)絡(luò)軟件將使用兩臺計算機(jī)。但是TCP套接字不能與兩臺以上的計算機(jī)通訊。如果需要這種功能,客戶端應(yīng)用程序必須建立多個套接字連接,每臺計算機(jī)一個套接字。

構(gòu)造函數(shù)

  java.net.Socket類有幾個構(gòu)造函數(shù)。其中兩個構(gòu)造函數(shù)允許使用布爾型參數(shù)指定是否使用UDP或TCP套接字,我們不贊成使用它們。這兒沒有使用這兩個構(gòu)造函數(shù),并且沒有列舉在此處--如果需要UDP功能,請使用DatagramSocket。

try
{
// 連接到指定的主機(jī)和端口
Socket mySocket = new Socket ( "www.awl.com", 80);

// ......
}
catch (Exception e)
{
System.err.println ("Err - " + e);
}

  但是還有很多構(gòu)造函數(shù)可以用于不同的情形。除非特別指出,所有的構(gòu)造函數(shù)都是公共的。

  · protected Socket ()-使用當(dāng)前套接字產(chǎn)生組件提供的默認(rèn)實現(xiàn)建立不連接的套接字。開發(fā)者一般不應(yīng)該使用這個方法,因為它不允許指定主機(jī)名稱和端口。

  · Socket (InetAddress address, int port)產(chǎn)生 java.io.IOException異常。

  · java.lang.SecurityException-建立連接到指定的IP地址和端口的套接字。如果不能建立連接,或連接到主機(jī)違反了安全性約束條件(例如某個小的服務(wù)程序試圖連接到某臺計算機(jī)而不是載入它的計算機(jī)時),就產(chǎn)生這種異常。

  · Socket (InetAddress address, int port, InetAddress localAddress, int localPort)產(chǎn)生java.io.IOException、java.lang.SecurityException異常-建立連接到指定的地址和端口的套接字,并把它綁定到特定的本地地址和本地端口。默認(rèn)情況下,使用一個自由(空)的端口,但是在多地址主機(jī)環(huán)境(例如本地主機(jī)有兩個或多個的計算機(jī))中,該方法也允許你指定一個特定的端口號、地址。

  · protected Socket (SocketImpl implementation)--使用特定的套接字的實現(xiàn)(implementation)建立未連接的套接字。通常情況下開發(fā)者不應(yīng)該使用這個方法,因為它允許指定主機(jī)名稱和端口。

  · Socket (String host, int port)產(chǎn)生java.net.UnknownHostException、java.io.IOException、java.lang.SecurityException異常--建立連接到特定主機(jī)和端口的套接字。這個方法允許指定一個字符串而不是一個InetAddress。如果指定的主機(jī)名稱不能夠解析,就不能建立連接,如果違反了安全性約束條件就產(chǎn)生異常。

  · Socket (String host, int port, InetAddress localAddress, int localPort)產(chǎn)生java.net.UnknownHostException、java.io.IOException、java.lang.SecurityException異常--建立連接到特定主機(jī)和端口的套接字,并綁定到特定的本地端口和地址。它允許指定字符串形式的主機(jī)名稱,而不是指定InetAddress實例,同時它允許指定一個將綁定的本地地址和端口。這些本地參數(shù)對于多地址主機(jī)(如果可以通過兩個或更多IP地址訪問的計算機(jī))是有用的。如果主機(jī)名稱不能解析,就不能建立連接,如果違反了安全性約束條件會產(chǎn)生異常。

1、建立套接字

  在正常環(huán)境下,建立套接字的時候它就連接了某臺計算機(jī)和端口。盡管有一個空的構(gòu)造函數(shù),它不需要主機(jī)名稱或端口,但是它是受保護(hù)的(protected),在正常的應(yīng)用程序中不能夠調(diào)用它。此外,不存在用于在以后指定這些細(xì)節(jié)信息的connect()方法,因此在正常的環(huán)境下建立套接字的時候就應(yīng)該連接了。如果網(wǎng)絡(luò)是好的,在建立連接的時候,調(diào)用套接字構(gòu)造函數(shù)將立即返回,但是如果遠(yuǎn)程計算機(jī)沒有響應(yīng),構(gòu)造函數(shù)方法可能會阻塞一段時間。這是隨著系統(tǒng)的不同而不同的,它依賴于多種因素,例如正在使用的操作系統(tǒng)和默認(rèn)的網(wǎng)絡(luò)超時設(shè)置(例如本地局域網(wǎng)中的一些計算機(jī)一般比Internet上的計算機(jī)響應(yīng)得快)。你甚至不能肯定套接字將阻塞多長的時間,但是這是非正常的行為,并且它不會頻繁出現(xiàn)。即使如此,在關(guān)鍵事務(wù)系統(tǒng)中把此類調(diào)用放在第二個線程中或許更合適,這樣可以防止應(yīng)用程序停止。

  注意

  在較低的層次,套接字是由套接字產(chǎn)生組件(socket factory)產(chǎn)生的,它是一個負(fù)責(zé)建立適當(dāng)?shù)奶捉幼謱崿F(xiàn)的特殊的類。在正常環(huán)境下,將會產(chǎn)生標(biāo)準(zhǔn)的java.net.Socket,但是在一些特殊的情形中,例如使用自定義套接字的特殊的網(wǎng)絡(luò)環(huán)境(例如通過使用特殊的代理服務(wù)器穿透防火墻),套接字產(chǎn)生組件實際上可能返回一個套接字子類(subclass)。對于錯綜復(fù)雜的Java網(wǎng)絡(luò)編程比較熟悉,明確為了建立自定義套接字和套接字產(chǎn)生組件的有經(jīng)驗的開發(fā)者可以去了解套接字產(chǎn)生組件的細(xì)節(jié)信息。對于這個主題的更多信息,你可以查看java.net.SocketFactory和java.net.SocketImplFactory類的Java API文檔。

2、使用套接字

  套接字可以執(zhí)行大量的事務(wù),例如讀取信息、發(fā)送數(shù)據(jù)、關(guān)閉連接、設(shè)置套接字選項等等。此外,下面提供的方法可以獲取套接字的信息(例如地址和端口位置):

  方法

  · void close()產(chǎn)生java.io.IOException異常--關(guān)閉套接字連接。關(guān)閉連接可能允許也可能不允許繼續(xù)發(fā)送剩余的數(shù)據(jù),這依賴于SO_LINGER套接字選項的設(shè)定。我們建議開發(fā)者在關(guān)閉套接字連接之前清除所有的輸出流。

  · InetAddress getInetAddress()--返回連接到套接字的遠(yuǎn)程主機(jī)的地址。

  · InputStream getInputStream()產(chǎn)生java.io.IOException異常--返回一個輸入流,它從該套接字連接到的應(yīng)用程序讀取信息。

  · OutputStream getOutputStream()產(chǎn)生java.io.IOException異常--返回一個輸出流,它向套接字連接到的應(yīng)用程序?qū)懭胄畔ⅰ?br />
  · boolean getKeepAlive()產(chǎn)生java.net.SocketException異常--返回SO_KEEPALIVE套接字選項的狀態(tài)。

  · InetAddress getLocalAddress()--返回與套接字關(guān)聯(lián)的本地地址(在多地址計算機(jī)中有用)。

  · int getLocalPort()--返回該套接字綁定在本地計算機(jī)上的端口號。

  · int getPort()--返回套接字連接到的遠(yuǎn)程服務(wù)的端口號。

  · int getReceiveBufferSize()產(chǎn)生java.net.SocketException異常--返回套接字使用的接收緩沖區(qū)大小,由SO_RCVBUF套接字選項的值決定。

  · int getSendBufferSize()產(chǎn)生java.net.SocketException異常--返回套接字使用的發(fā)送緩沖區(qū)大小,由SO_SNDBUF套接字選項的值決定。

  · int getSoLinger()產(chǎn)生java.net.SocketException異常--返回SO_LINGER套接字選項的值,它控制連接終止的時候未發(fā)送的數(shù)據(jù)將排隊多長時間。
 
  · int getSoTimeout()產(chǎn)生java.net.SocketException異常--返回SO_TIMEOUT套接字選項的值,它控制讀取操作將阻塞多少毫秒。如果返回值為0,計時器就被禁止了,該線程將無限期阻塞(直到數(shù)據(jù)可以使用或流被終止)。

  · boolean getTcpNoDelay()產(chǎn)生java.net.SocketException異常--如果TCP_NODELAY套接字選項的設(shè)置打開了返回"true",它控制是否允許使用Nagle算法。

  · void setKeepAlive(boolean onFlag)產(chǎn)生java.net.SocketException異常--允許或禁止SO_KEEPALIVE套接字選項。

  · void setReceiveBufferSize(int size)產(chǎn)生java.net.SocketException異常--修改SO_RCVBUF套接字選項的值,它為操作系統(tǒng)的網(wǎng)絡(luò)代碼推薦用于接收輸入的數(shù)據(jù)的緩沖區(qū)大小。并不是每種系統(tǒng)都支持這種功能或允許絕對控制這個特性。如果你希望緩沖輸入的數(shù)據(jù),我們建議你改用BufferedInputStream或BufferedReader。

  · void setSendBufferSize(int size)產(chǎn)生java.net.SocketException異常--修改SO_SNDBUF套接字選項的值,它為操作系統(tǒng)的網(wǎng)絡(luò)代碼推薦用于發(fā)送輸入的數(shù)據(jù)的緩沖區(qū)大小。并不是每種系統(tǒng)都支持這種功能或允許絕對控制這個特性。如果你希望緩沖輸入的數(shù)據(jù),我們建議你改用BufferedOutputStream或Buffered Writer。

  · static void setSocketImplFactory (SocketImplFactory factory)產(chǎn)生java.net.SocketException、java.io.IOException、java. lang.SecurityException異常--為JVM指定一個套接字實現(xiàn)的產(chǎn)生組件,它可以已經(jīng)存在,也可能違反了安全性約束條件,無論是哪種情況都會產(chǎn)生異常。只能指定一個產(chǎn)生組件,當(dāng)建立套接字的時候都會使用這個產(chǎn)生組件。

  · void setSoLinger(boolean onFlag, int duration)產(chǎn)生java.net. SocketException、java.lang.IllegalArgumentException異常--激活或禁止SO_LINGER套接字選項(根據(jù)布爾型參數(shù)onFlag的值),并指定按秒計算的持續(xù)時間。如果指定負(fù)值,將產(chǎn)生異常。

  · void setSoTimeout(int duration)產(chǎn)生java.net.SocketException異常--修改SO_TIMEOUT套接字選項的值,它控制讀取操作將阻塞多長時間(按毫秒計)。0值會禁止超時設(shè)置,引起無限期阻塞。如果發(fā)生了超時,當(dāng)套接字的輸入流上發(fā)生讀取操作的時候,會產(chǎn)生java.io.IOInterruptedException異常。這與內(nèi)部的TCP計時器是截然不同的,它觸發(fā)未知報文包的重新發(fā)送過程。

  · void setTcpNoDelay(boolean onFlag)產(chǎn)生java.net.SocketException異常--激活或禁止TCP_NODELAY套接字選項,它決定是否使用Nagle算法。

  · void shutdownInput()產(chǎn)生java.io.IOException異常--關(guān)閉與套接字關(guān)聯(lián)的輸入流,并刪除所有發(fā)送的更多的信息。對輸入流的進(jìn)一步的讀取將會遭遇流的結(jié)束標(biāo)識符。

  · void shutdownOutput()產(chǎn)生java.io.IOException異常--關(guān)閉與套接字關(guān)聯(lián)的輸出流。前面寫入的、但沒有發(fā)送的任何信息將被清除,緊接著是TCP連接終止,它通知應(yīng)用程序沒有更多的數(shù)據(jù)可以使用了(在Java應(yīng)用程序中,這樣就到達(dá)了流的末尾)。向套接字進(jìn)一步寫入信息將引起IOException異常。

 3、 向TCP套接字讀取和寫入信息

  在Java中使用TCP建立用于通訊的客戶端軟件極其簡單,無論使用哪種操作系統(tǒng)都一樣。Java網(wǎng)絡(luò)API提供了一致的、平臺無關(guān)的接口,它允許客戶端應(yīng)用程序連接到遠(yuǎn)程服務(wù)。一旦建立了套接字,它就已經(jīng)連接了并準(zhǔn)備使用輸入和輸出流讀取/寫入信息了。這些流都不需要建立,它們是Socket. getInputStream()和Socket.getOutputStream()方法提供的。

  為了簡化編程,過濾器可以很容易地連接到套接字流。下面的代碼片斷演示了一個簡單的TCP客戶端,它把BufferedReader連接到套接字輸入流,把PrintStream連接到套接字輸出流。

try
{
// 把套接字連接到某臺主機(jī)和端口
Socket socket = new Socket ( somehost, someport );

// 連接到被緩沖地讀取程序
BufferedReader reader = new BufferedReader (
new InputStreamReader ( socket.getInputStream() ) );

// 連接到打印流
PrintStream pstream =
new PrintStream( socket.getOutputStream() );
}
catch (Exception e)
{
System.err.println ("Error - " + e);
}

4、套接字選項

  套接字選項是改變套接字工作方式的設(shè)置,并且它們能影響(正反兩方向)應(yīng)用程序的性能。對于套接字選項的支持是在Java 1.1中引入的,在后面的一些版本中對其中一些做了改進(jìn)(例如在Java 2 和Java 3中支持SO_KEEPALIVE選項)。通常情況下,不應(yīng)該修改套接字選項,除非有很必要的原因,因為這種改變可能反面影響應(yīng)用程序和網(wǎng)絡(luò)的性能(例如,激活Nagle算法可能提高telnet類型應(yīng)用程序的性能,但是會降低可以使用地網(wǎng)絡(luò)帶寬)。唯一的例外是SO_TIMEOUT選項--事實上,如果套接字連接的應(yīng)用程序傳輸數(shù)據(jù)出現(xiàn)失敗的時候,它都應(yīng)該溫和地處理超時問題,而不應(yīng)該因此延遲速度。

  ⑴SO_KEEPALIVE套接字操作

  Keepalive(保持活動)套接字選項是很有爭議的,一些開發(fā)者認(rèn)為使用它會很強大。在默認(rèn)情況下,兩個連接的套接字之間沒有數(shù)據(jù)發(fā)送,除非應(yīng)用程序有需要發(fā)送的數(shù)據(jù)。這意味著在長期存活的進(jìn)程中空閑地的接字可能幾分鐘、幾小時、甚至于幾天不會提交數(shù)據(jù)。但是,假設(shè)某個客戶端崩潰了,并且連接終結(jié)序號沒有發(fā)送給TCP服務(wù)器。貴重的資源(例如CPU時間和內(nèi)存)將會浪費在哪個永遠(yuǎn)不會響應(yīng)的客戶端上。如果允許keepalive套接字選項,套接字的另一端可以探測以驗證它是否仍然是活動的。但是,應(yīng)用程序不能控制keepalive探測器的發(fā)送頻率。為了激活keepalive,需要調(diào)用Socket.setSoKeepAlive(boolean)方法,參數(shù)的值為"true"("false"值將禁止它)。例如,為了在某個套接字上允許keepalive,可能使用下面的代碼:

// 激活SO_KEEPALIVE
someSocket.setSoKeepAlive(true);

  盡管keepalive的好處并不多,但是很多開發(fā)者提倡在更高層次的應(yīng)用程序代碼中控制超時設(shè)置和死的套接字。同時需要記住,keepalive不允許你為探測套接字終點(endpoint)指定一個值。我們建議開發(fā)者使用的另一種比keepalive更好的解決方案是修改超時設(shè)置套接字選項。

  ⑵SO_RCVBUF套接字操作

  接收緩沖區(qū)套接字選項控制用于接收數(shù)據(jù)的緩沖區(qū)。你可以通過調(diào)用方法改變它的大小。例如,為了把緩沖區(qū)大小改變?yōu)?096,可以使用下面的代碼:

// 修改緩沖區(qū)大小
someSocket.setReceiveBufferSize(4096);
注意:修改接收緩沖區(qū)大小的請求不能保證改變成功。例如,有些操作系統(tǒng)可能不允許修改這個套接字選項,并忽略對該值的任何改變。你可以調(diào)用Socket. getReceiveBufferSize()方法得到當(dāng)前緩沖區(qū)的大小。使用緩沖的更好的選擇是使用BufferedInputStream/BufferedReader。

  ⑶ SO_SNDBUF套接字操作

  發(fā)送緩沖區(qū)套接字選項控制用于發(fā)送數(shù)據(jù)的緩沖區(qū)的大小。通過調(diào)用Socket.setSendBufferSize(int)方法,你能夠試圖改變緩沖區(qū)的大小,但是改變緩沖區(qū)大小的請求可能被操作系統(tǒng)拒絕。

// 把發(fā)送緩沖區(qū)的大小改為4096字節(jié)
someSocket.setSendBufferSize(4096);
為了得到當(dāng)前發(fā)送緩沖區(qū)的大小,你可以調(diào)用Socket.getSendBufferSize()方法,它返回一個整型值。
// 得到默認(rèn)的大小
int size = someSocket.getSendBufferSize();

  使用DatagramSocket類時改變緩沖區(qū)大小可能更有效。當(dāng)對寫進(jìn)行緩沖的時候,更好的選擇是使用BufferedOutputStream和BufferedWriter。

  ⑷ SO_LINGER套接字操作

  當(dāng)某個TCP套接字連接被關(guān)閉的時候,可能還有一些數(shù)據(jù)在隊列中等待發(fā)送但是還沒有被發(fā)送(特別是在IP數(shù)據(jù)報在傳輸過程中丟失了,必須重新發(fā)送的情況下)。Linger(拖延)套接字選項控制未發(fā)送的數(shù)據(jù)可能發(fā)送的時間總和,過了這個時間以后數(shù)據(jù)就會被完全刪除。通過使用Socket.setSoLinger(boolean onFlag, int duration)方法完全激活/禁止linger選項、或者修改linger的持續(xù)時間都是可以的。

// 激活linger,持續(xù)50秒
someSocket.setSoLinger( true, 50 );

  ⑸ TCP_NODELAY套接字操作

  這個套接字選項是一個標(biāo)記,它的狀態(tài)控制著是否激活Nagle算法(RFC 896)。因為TCP數(shù)據(jù)是使用IP數(shù)據(jù)報在網(wǎng)絡(luò)上發(fā)送的,因此每個包都有一定位數(shù)的開銷(例如IP和TCP頭部信息)。如果在某個時刻每個包中只發(fā)送了少量的字節(jié),頭部信息的大小將遠(yuǎn)遠(yuǎn)超過數(shù)據(jù)的大小。在局域網(wǎng)中,發(fā)送的額外的數(shù)據(jù)可能不會很多,但是在Internet上,成百、成千、甚至于成百萬地客戶端可能通過某個路由器發(fā)送這種數(shù)據(jù)包,加起來顯著地增加了帶寬的消耗。

  解決的方法是Nagle算法,它規(guī)定TCP在一個時刻只能發(fā)送一個數(shù)據(jù)報。當(dāng)每個IP數(shù)據(jù)報得到肯定應(yīng)答的時候,才能發(fā)送新的隊列中包含數(shù)據(jù)的數(shù)據(jù)報。它限制了數(shù)據(jù)報頭部信息消耗的帶寬總量,但是有不太重要的代價--網(wǎng)絡(luò)延遲。因為數(shù)據(jù)被排隊了,它們不是立即發(fā)送的,因此需要快速響應(yīng)時間的系統(tǒng)(例如X-Windows或telnet)的速度被減慢了。禁止Nagle算法可能提高性能,但是如果被太多的客戶端使用,網(wǎng)絡(luò)性能也會降低。

  可以通過調(diào)用Socket.setTcpNoDelay(boolean state)方法激活或禁止Nagle算法。例如,為了禁止該算法,可能使用下面的代碼:

// 為了得到更快的響應(yīng)時間禁止Nagle算法
someSocket.setTcpNoDelay(false);
為了獲取Nagle算法的狀態(tài)和TCP_NODELAY標(biāo)識符,可以使用Socket.getTcpNoDelay()方法:
// 得到TCP_NODELAY標(biāo)識符的狀態(tài)
boolean state = someSocket.getTcpNoDelay();

  ⑹ SO_TIMEOUT套接字操作

  超時設(shè)置選項是最有用的套接字選項。在默認(rèn)情況下,I/O操作(基于文件的或基于網(wǎng)絡(luò)的)都是阻塞的操作。試圖從InputStream讀取數(shù)據(jù)將無限期等待直到輸入到達(dá)。如果輸入永遠(yuǎn)沒有到達(dá),應(yīng)用程序?qū)⑼V共⑶以诖蠖鄶?shù)情況下變得不可用(除非使用了多線程)。用戶不喜歡不能響應(yīng)的應(yīng)用程序,他們認(rèn)為這類應(yīng)用程序行為很討厭。更牢固的應(yīng)用程序應(yīng)該預(yù)料到這類問題并采取正確的操作。

  注意

  在測試期間的本地內(nèi)部網(wǎng)環(huán)境中網(wǎng)絡(luò)問題很少,但是在Internet上,應(yīng)用程序停止是很可能的。服務(wù)器應(yīng)用程序并沒有免疫力--服務(wù)器也使用Socket類連接客戶端,并且很容易停止。因為這個原因,所有的應(yīng)用程序(無論是客戶端或者服務(wù)器)都應(yīng)該溫和地處理網(wǎng)絡(luò)超時的問題。

  當(dāng)激活SO_TIMEOUT選項時,任何向套接字的InputStream的讀取請求都會啟動一個計時器。當(dāng)數(shù)據(jù)沒有按時到達(dá)并且計時器超期的時候,就產(chǎn)生java.io.InterruptedIOException異常,你可以捕捉該異常。接著就是應(yīng)用程序開發(fā)者的工作了--可以再次嘗試、通知用戶或取消連接。可以調(diào)用Socket. setSoTimeout(int)方法控制計時器的持續(xù)時間,它的參數(shù)是等待數(shù)據(jù)的毫秒數(shù)。例如,為了設(shè)置5秒鐘超時,將使用下面的代碼:

// 設(shè)置5秒鐘超時
someSocket.setSoTimeout ( 5 * 1000 );

  激活設(shè)置后,任何讀取數(shù)據(jù)的企圖都可能產(chǎn)生InterruptedIOException異常,該異常擴(kuò)展自java.io.IOException類。由于讀取數(shù)據(jù)的企圖可能已經(jīng)產(chǎn)生了IOException異常,所以不需要更多的代碼來處理該異常了--但是,有些應(yīng)用程序可能希望逐步捕捉與超時設(shè)置相關(guān)地異常,在這種情況下可能需要添加另外地異常處理代碼:

try
{
Socket s = new Socket (...);
s.setSoTimeout ( 2000 );

// 執(zhí)行一些讀取操作
}
catch (InterruptedIOException iioe)
{
timeoutFlag = true; // 執(zhí)行一些操作,例如設(shè)置標(biāo)識符
}
catch (IOException ioe)
{
System.err.println ("IO error " + ioe);
System.exit(0);
}

  為了得到TCP計時器的長度,可以使用Socket.getSoTimeout()方法,它返回一個整型值。如果返回值為零表明超時設(shè)定被禁止了,任何讀取操作將無限期阻塞。

// 查看超時設(shè)定是否為零
if ( someSocket.getSoTimeout() == 0) someSocket.setSoTimeout (500);