上一篇:
Java網絡編程從入門到精通(17):Socket類的getter和setter方法(1)
二、
用于獲得和設置
Socket
選項的
getter
和
setter
方法
Socket 選擇可以指定 Socket 類發送和接受數據的方式。在 JDK1.4 中共有 8 個 Socket 選擇可以設置。這 8 個選項都定義在 java.net.SocketOptions 接口中。定義如下:
public final static int SO_REUSEADDR = 0x04 ;
public final static int SO_LINGER = 0x0080 ;
public final static int SO_TIMEOUT = 0x1006 ;
public final static int SO_SNDBUF = 0x1001 ;
public final static int SO_RCVBUF = 0x1002 ;
public final static int SO_KEEPALIVE = 0x0008 ;
public final static int SO_OOBINLINE = 0x1003 ;
有趣的是,這
8
個選項除了第一個沒在
SO
前綴外,其他
7
個選項都以
SO
作為前綴。其實這個
SO
就是
Socket Option
的縮寫;因此,在
Java
中約定所有以
SO
為前綴的常量都表示
Socket
選項;當然,也有例外,如
TCP_NODELAY
。在
Socket
類中為每一個選項提供了一對
get
和
set
方法,分別用來獲得和設置這些選項。
1. TCP_NODELAY
public void setTcpNoDelay( boolean on) throws SocketException
在默認情況下,客戶端向服務器發送數據時,會根據數據包的大小決定是否立即發送。當數據包中的數據很少時,如只有 1 個字節,而數據包的頭卻有幾十個字節( IP 頭 +TCP 頭)時,系統會在發送之前先將較小的包合并到軟大的包后,一起將數據發送出去。在發送下一個數據包時,系統會等待服務器對前一個數據包的響應,當收到服務器的響應后,再發送下一個數據包,這就是所謂的 Nagle 算法;在默認情況下, Nagle 算法是開啟的。
這種算法雖然可以有效地改善網絡傳輸的效率,但對于網絡速度比較慢,而且對實現性的要求比較高的情況下(如游戲、 Telnet 等),使用這種方式傳輸數據會使得客戶端有明顯的停頓現象。因此,最好的解決方案就是需要 Nagle 算法時就使用它,不需要時就關閉它。而使用 setTcpToDelay 正好可以滿足這個需求。當使用 setTcpNoDelay(true) 將 Nagle 算法關閉后,客戶端每發送一次數據,無論數據包的大小都會將這些數據發送出去。
2. SO_REUSEADDR
public void setReuseAddress( boolean on) throws SocketException
通過這個選項,可以使多個 Socket 對象綁定在同一個端口上。其實這樣做并沒有多大意義,但當使用 close 方法關閉 Socket 連接后, Socket 對象所綁定的端口并不一定馬上釋放;系統有時在 Socket 連接關閉才會再確認一下是否有因為延遲面未到達的數據包,這完全是在底層處理的,也就是說對用戶是透明的;因此,在使用 Socket 類時完全不會感覺到。
這種處理機制對于隨機綁定端口的
Socket
對象沒有什么影響,但對于綁定在固定端口的
Socket
對象就可能會拋出
“Address already in use: JVM_Bind”
例外。因此,使用這個選項可以避免個例外的發生。
import java.net. * ;
import java.io. * ;
public class Test
{
public static void main(String[]args)
{
Socketsocket1 = new Socket();
Socketsocket2 = new Socket();
try
{
socket1.setReuseAddress( true );
socket1.bind( new InetSocketAddress( " 127.0.0.1 " , 88 ));
System.out.println( " socket1.getReuseAddress(): "
+ socket1.getReuseAddress());
socket2.bind( new InetSocketAddress( " 127.0.0.1 " , 88 ));
}
catch (Exceptione)
{
System.out.println( " error: " + e.getMessage());
try
{
socket2.setReuseAddress( true );
socket2.bind( new InetSocketAddress( " 127.0.0.1 " , 88 ));
System.out.println( " socket2.getReuseAddress(): "
+ socket2.getReuseAddress());
System.out.println( " 端口88第二次綁定成功! " );
}
catch (Exceptione1)
{
System.out.println(e.getMessage());
}
}
}
}
上面的代碼的運行結果如下:
error:Addressalreadyinuse:JVM_Bind
socket2.getReuseAddress():true
端口88第二次綁定成功!
使用SO_REUSEADDR選項時有兩點需要注意:
1. 必須在調用bind方法之前使用setReuseAddress方法來打開SO_REUSEADDR選項。因此,要想使用SO_REUSEADDR選項,就不能通過Socket類的構造方法來綁定端口。
2. 必須將綁定同一個端口的所有的Socket對象的SO_REUSEADDR選項都打開才能起作用。如在例程4-12中,socket1和socket2都使用了setReuseAddress方法打開了各自的SO_REUSEADDR選項。
3. SO_LINGER
public void setSoLinger( boolean on, int linger) throws SocketException
這個 Socket 選項可以影響 close 方法的行為。在默認情況下,當調用 close 方法后,將立即返回;如果這時仍然有未被送出的數據包,那么這些數據包將被丟棄。如果將 linger 參數設為一個正整數 n 時 (n 的值最大是 65,535) ,在調用 close 方法后,將最多被阻塞 n 秒。在這 n 秒內,系統將盡量將未送出的數據包發送出去;如果超過了 n 秒,如果還有未發送的數據包,這些數據包將全部被丟棄;而 close 方法會立即返回。如果將 linger 設為 0 ,和關閉 SO_LINGER 選項的作用是一樣的。
如果底層的 Socket 實現不支持 SO_LINGER 都會拋出 SocketException 例外。當給 linger 參數傳遞負數值時, setSoLinger 還會拋出一個 IllegalArgumentException 例外??梢酝ㄟ^ getSoLinger 方法得到延遲關閉的時間,如果返回 -1 ,則表明 SO_LINGER 是關閉的。例如,下面的代碼將延遲關閉的時間設為 1 分鐘:
4. SO_TIMEOUT
public void setSoTimeout( int timeout) throws SocketException
這個 Socket 選項在前面已經討論過??梢酝ㄟ^這個選項來設置讀取數據超時。當輸入流的 read 方法被阻塞時,如果設置 timeout ( timeout 的單位是毫秒),那么系統在等待了 timeout 毫秒后會拋出一個 InterruptedIOException 例外。在拋出例外后,輸入流并未關閉,你可以繼續通過 read 方法讀取數據。
如果將 timeout 設為 0 ,就意味著 read 將會無限等待下去,直到服務端程序關閉這個 Socket 。這也是 timeout 的默認值。如下面的語句將讀取數據超時設為 30 秒:
當底層的 Socket 實現不支持 SO_TIMEOUT 選項時,這兩個方法將拋出 SocketException 例外。不能將 timeout 設為負數,否則 setSoTimeout 方法將拋出 IllegalArgumentException 例外。
5. SO_SNDBUF
public void setSendBufferSize( int size) throws SocketException
在默認情況下,輸出流的發送緩沖區是 8096 個字節( 8K )。這個值是 Java 所建議的輸出緩沖區的大小。如果這個默認值不能滿足要求,可以用 setSendBufferSize 方法來重新設置緩沖區的大小。但最好不要將輸出緩沖區設得太小,否則會導致傳輸數據過于頻繁,從而降低網絡傳輸的效率。
如果底層的 Socket 實現不支持 SO_SENDBUF 選項,這兩個方法將會拋出 SocketException 例外。必須將 size 設為正整數,否則 setSendBufferedSize 方法將拋出 IllegalArgumentException 例外。
6. SO_RCVBUF
public void setReceiveBufferSize( int size) throws SocketException
在默認情況下,輸入流的接收緩沖區是 8096 個字節( 8K )。這個值是 Java 所建議的輸入緩沖區的大小。如果這個默認值不能滿足要求,可以用 setReceiveBufferSize 方法來重新設置緩沖區的大小。但最好不要將輸入緩沖區設得太小,否則會導致傳輸數據過于頻繁,從而降低網絡傳輸的效率。
如果底層的 Socket 實現不支持 SO_RCVBUF 選項,這兩個方法將會拋出 SocketException 例外。必須將 size 設為正整數,否則 setReceiveBufferSize 方法將拋出 IllegalArgumentException 例外。
7. SO_KEEPALIVE
public void setKeepAlive( boolean on) throws SocketException
如果將這個
Socket
選項打開,客戶端
Socket
每隔段的時間(大約兩個小時)就會利用空閑的連接向服務器發送一個數據包。這個數據包并沒有其它的作用,只是為了檢測一下服務器是否仍處于活動狀態。如果服務器未響應這個數據包,在大約
11
分鐘后,客戶端
Socket
再發送一個數據包,如果在
12
分鐘內,服務器還沒響應,那么客戶端
Socket
將關閉。如果將
Socket
選項關閉,客戶端
Socket
在服務器無效的情況下可能會長時間不會關閉。
SO_KEEPALIVE
選項在默認情況下是關閉的,可以使用如下的語句將這個
SO_KEEPALIVE
選項打開:
8. SO_OOBINLINE
public void setOOBInline( boolean on) throws SocketException
如果這個 Socket 選項打開,可以通過 Socket 類的 sendUrgentData 方法向服務器發送一個單字節的數據。這個單字節數據并不經過輸出緩沖區,而是立即發出。雖然在客戶端并不是使用 OutputStream 向服務器發送數據,但在服務端程序中這個單字節的數據是和其它的普通數據混在一起的。因此,在服務端程序中并不知道由客戶端發過來的數據是由 OutputStream 還是由 sendUrgentData 發過來的。下面是 sendUrgentData 方法的聲明:
雖然
sendUrgentData
的參數
data
是
int
類型,但只有這個
int
類型的低字節被發送,其它的三個字節被忽略。下面的代碼
演示了如何使用
SO_OOBINLINE
選項來發送單字節數據。
import java.net. * ;
import java.io. * ;
class Server
{
public static void main(String[]args) throws Exception
{
ServerSocketserverSocket = new ServerSocket( 1234 );
System.out.println( " 服務器已經啟動,端口號:1234 " );
while ( true )
{
Socketsocket = serverSocket.accept();
socket.setOOBInline( true );
InputStreamin = socket.getInputStream();
InputStreamReaderinReader = new InputStreamReader(in);
BufferedReaderbReader = new BufferedReader(inReader);
System.out.println(bReader.readLine());
System.out.println(bReader.readLine());
socket.close();
}
}
}
public class Client
{
public static void main(String[]args) throws Exception
{
Socketsocket = new Socket( " 127.0.0.1 " , 1234 );
socket.setOOBInline( true );
OutputStreamout = socket.getOutputStream();
OutputStreamWriteroutWriter = new OutputStreamWriter(out);
outWriter.write( 67 ); // 向服務器發送字符"C"
outWriter.write( " helloworld\r\n " );
socket.sendUrgentData( 65 ); // 向服務器發送字符"A"
socket.sendUrgentData( 322 ); // 向服務器發送字符"B"
outWriter.flush();
socket.sendUrgentData( 214 ); // 向服務器發送漢字”中”
socket.sendUrgentData( 208 );
socket.sendUrgentData( 185 ); // 向服務器發送漢字”國”
socket.sendUrgentData( 250 );
socket.close();
}
}
由于運行上面的代碼 需要一個服務器類,因此,在 加了一個類名為 Server 的服務器類,關于服務端套接字的使用方法將會在后面的文章中詳細討論。在類 Server類 中只使用了 ServerSocket 類的 accept 方法接收客戶端的請求。并 從客戶端傳來的數據中讀取兩行字符串,并顯示在控制臺上。
測試
由于本例使用了 127.0.0.1 ,因Server 和 Client類 必須在同一臺機器上運行。
運行 Server
運行 Client
在服務端控制臺的輸出結果
ABChelloworld
中國
在ClienT類中使用了 sendUrgentData 方法向服務器發送了字符 'A'(65) 和 'B'(66) 。但發送'B'時 實際發送的是 322 ,由于 sendUrgentData 只發送整型數的低字節。因此,實際發送的是 66 。十進制整型 322 的二進制形式如圖 1 所示。

從圖1可以看出,雖然322分布在了兩個字節上,但它的低字節仍然是66。
在Client類中使用 flush 將緩沖區中的數據發送到服務器。我們可以從輸出結果發現一個問題,在Client類中 先后向服務器發送了 'C' 、 "hello world"r"n" 、 'A' 、 'B' 。而在服務端程序的控制臺上顯示的卻是 ABChello world 。這種現象說明使用 sendUrgentData 方法發送數據后,系統會立即將這些數據發送出去;而使用 write 發送數據,必須要使用 flush 方法才會真正發送數據。
在Client類中 向服務器發送 " 中國 " 字符串。由于 " 中 " 是由 214 和 208 兩個字節組成的;而 " 國 " 是由 185 和 250 兩個字節組成的;因此,可分別發送這四個字節來傳送 " 中國 " 字符串。
注意:
在使用setOOBInline方法打開SO_OOBINLINE選項時要注意是必須在客戶端和服務端程序同時使用setOOBInline方法打開這個選項,否則無法命名用sendUrgentData來發送數據。
下一篇: Java網絡編程從入門到精通(19):套接字(Socket)的異常
國內最棒的Google Android技術社區(eoeandroid),歡迎訪問!
《銀河系列原創教程》 發布
《Java Web開發速學寶典》 出版,歡迎定購
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
