Sock java 重要參數 分析

Socket選擇可以指定Socket類發送和接受數據的方式。在JDK1.4中共有8個Socket選擇可以設置。這8個選項都定義在java.net.SocketOptions接口中。定義如下:

 

    public final static int TCP_NODELAY = 0x0001;
    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 boolean getTcpNoDelay() throws SocketException
public void setTcpNoDelay(boolean on) throws SocketException

    在默認情況下,客戶端向服務器發送數據時,會根據數據包的大小決定是否立即發送。當數據包中的數據很少時,如只有1個字節,而數據包的頭卻有幾十個字節(IP頭+TCP頭)時,系統會在發送之前先將較小的包合併到軟大的包後,一起將數據發送出去。在發送下一個數據包時,系統會等待服務器對前一個數據包的響應,當收到服務器的響應後,再發送下一個數據包,這就是所謂的Nagle算法;在默認情況下,Nagle算法是開啓的。

    這種算法雖然可以有效地改善網絡傳輸的效率,但對於網絡速度比較慢,而且對實現性的要求比較高的情況下(如遊戲、Telnet等),使用這種方式傳輸數據會使得客戶端有明顯的停頓現象。因此,最好的解決方案就是需要Nagle算法時就使用它,不需要時就關閉它。而使用setTcpToDelay正好可以滿足這個需求。當使用setTcpNoDelay(true)將Nagle算法關閉後,客戶端每發送一次數據,無論數據包的大小都會將這些數據發送出去。

    2.  SO_REUSEADDR

 

public boolean getReuseAddress() throws SocketException           
public void setReuseAddress(boolean on) throws SocketException

錯誤的說法:

      通過這個選項,可以使多個Socket對象綁定在同一個端口上。

正確的說明是:

 

     如果端口忙,但TCP狀態位於 TIME_WAIT ,可以重用 端口。如果端口忙,而TCP狀態位於其他狀態,重用端口時依舊得到一個錯誤信息, 拋出“Address already in use: JVM_Bind”。如果你的服務程序停止後想立即重啓,不等60秒,而新套接字依舊 使用同一端口,此時 SO_REUSEADDR 選項非常有用。必須意識到,此時任何非期 望數據到達,都可能導致服務程序反應混亂,不過這只是一種可能,事實上很不可能。 

 

 

這個參數在Windows平臺與Linux平臺表現的特點不一樣。在Windows平臺表現的特點是不正確的, 在Linux平臺表現的特點是正確的。

在Windows平臺,多個Socket新建立對象可以綁定在同一個端口上,這些新連接是非TIME_WAIT狀態的。這樣做並沒有多大意義。

在Linux平臺,只有TCP狀態位於 TIME_WAIT ,纔可以重用 端口。這纔是正確的行爲。

public class Test {

public static void main(String[] args) {

try {

ServerSocket socket1 = new ServerSocket();

ServerSocket socket2 = new ServerSocket();

socket1.setReuseAddress(true);

socket1.bind(new InetSocketAddress("127.0.0.1", 8899));

System.out.println("socket1.getReuseAddress():"

+ socket1.getReuseAddress());

socket2.setReuseAddress(true);

socket2.bind(new InetSocketAddress("127.0.0.1", 8899));

System.out.println("socket2.getReuseAddress():"

+ socket1.getReuseAddress());

} catch (Exception e) {

e.printStackTrace();

}

}

}

 

使用SO_REUSEADDR選項時有兩點需要注意:

    1.  必須在調用bind方法之前使用setReuseAddress方法來打開SO_REUSEADDR選項。因此,要想使用SO_REUSEADDR選項,就不能通過Socket類的構造方法來綁定端口。

    2.  必須將綁定同一個端口的所有的Socket對象的SO_REUSEADDR選項都打開才能起作用。如在例程4-12中,socket1和socket2都使用了setReuseAddress方法打開了各自的SO_REUSEADDR選項。

 

 

 

在Windows操作系統上運行上面的代碼的運行結果如下:

這種結果是不正確的。

socket1.getReuseAddress():true
socket2.getReuseAddress():true

在Linux操作系統上運行上面的代碼的運行結果如下:

這種結果是正確的。因爲第一個連接不是TIME_WAIT狀態的,第二個連接就不能使用8899端口;

只有第一個連接是TIME_WAIT狀態的,第二個連接就才能使用8899端口;

 

Java代碼  收藏代碼
  1. socket1.getReuseAddress():true  
  2. java.net.BindException: Address already in use  
  3.     at java.net.PlainSocketImpl.socketBind(Native Method)  
  4.     at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:383)  
  5.     at java.net.ServerSocket.bind(ServerSocket.java:328)  
  6.     at java.net.ServerSocket.bind(ServerSocket.java:286)  
  7.     at com.Test.main(Test.java:15)  
 

 

 

 

 

    3.  SO_LINGER

 

public int getSoLinger() throws SocketException
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分鐘:

 

if(socket.getSoLinger() == -1) socket.setSoLinger(true60);

    4.  SO_TIMEOUT

 

public int getSoTimeout() throws SocketException
public void setSoTimeout(int timeout) throws SocketException

    這個Socket選項在前面已經討論過。可以通過這個選項來設置讀取數據超時。當輸入流的read方法被阻塞時,如果設置timeout(timeout的單位是毫秒),那麼系統在等待了timeout毫秒後會拋出一個InterruptedIOException例外。在拋出例外後,輸入流並未關閉,你可以繼續通過read方法讀取數據。

    如果將timeout設爲0,就意味着read將會無限等待下去,直到服務端程序關閉這個Socket.這也是timeout的默認值。如下面的語句將讀取數據超時設爲30秒:

 

socket1.setSoTimeout(30 * 1000);

    當底層的Socket實現不支持SO_TIMEOUT選項時,這兩個方法將拋出SocketException例外。不能將timeout設爲負數,否則setSoTimeout方法將拋出IllegalArgumentException例外。

    5.  SO_SNDBUF

 

public int getSendBufferSize() throws SocketException
public void setSendBufferSize(int size) throws SocketException

    在默認情況下,輸出流的發送緩衝區是8096個字節(8K)。這個值是Java所建議的輸出緩衝區的大小。如果這個默認值不能滿足要求,可以用setSendBufferSize方法來重新設置緩衝區的大小。但最好不要將輸出緩衝區設得太小,否則會導致傳輸數據過於頻繁,從而降低網絡傳輸的效率。

    如果底層的Socket實現不支持SO_SENDBUF選項,這兩個方法將會拋出SocketException例外。必須將size設爲正整數,否則setSendBufferedSize方法將拋出IllegalArgumentException例外。

    6.  SO_RCVBUF

 

public int getReceiveBufferSize() throws SocketException
public void setReceiveBufferSize(int size) throws SocketException

    在默認情況下,輸入流的接收緩衝區是8096個字節(8K)。這個值是Java所建議的輸入緩衝區的大小。如果這個默認值不能滿足要求,可以用setReceiveBufferSize方法來重新設置緩衝區的大小。但最好不要將輸入緩衝區設得太小,否則會導致傳輸數據過於頻繁,從而降低網絡傳輸的效率。

    如果底層的Socket實現不支持SO_RCVBUF選項,這兩個方法將會拋出SocketException例外。必須將size設爲正整數,否則setReceiveBufferSize方法將拋出IllegalArgumentException例外。

 

    7.  SO_KEEPALIVE

 

public boolean getKeepAlive() throws SocketException
public void setKeepAlive(boolean on) throws SocketException

    如果將這個Socket選項打開,客戶端Socket每隔段的時間(大約兩個小時)就會利用空閒的連接向服務器發送一個數據包。這個數據包並沒有其它的作用,只是爲了檢測一下服務器是否仍處於活動狀態。如果服務器未響應這個數據包,在大約11分鐘後,客戶端Socket再發送一個數據包,如果在12分鐘內,服務器還沒響應,那麼客戶端Socket將關閉。如果將Socket選項關閉,客戶端Socket在服務器無效的情況下可能會長時間不會關閉。SO_KEEPALIVE選項在默認情況下是關閉的,可以使用如下的語句將這個SO_KEEPALIVE選項打開:

 

socket1.setKeepAlive(true);

    8.  SO_OOBINLINE

 

 public boolean getOOBInline() throws SocketException
 
public void setOOBInline(boolean on) throws SocketException

    如果這個Socket選項打開,可以通過Socket類的sendUrgentData方法向服務器發送一個單字節的數據。這個單字節數據並不經過輸出緩衝區,而是立即發出。雖然在客戶端並不是使用OutputStream向服務器發送數據,但在服務端程序中這個單字節的數據是和其它的普通數據混在一起的。因此,在服務端程序中並不知道由客戶端發過來的數據是由OutputStream還是由sendUrgentData發過來的。下面是sendUrgentData方法的聲明:

 

public void sendUrgentData(int data) throws IOException

    雖然sendUrgentData的參數data是int類型,但只有這個int類型的低字節被髮送,其它的三個字節被忽略。下面的代碼演示瞭如何使用SO_OOBINLINE選項來發送單字節數據。

 

package mynet;

import java.net.*;
import java.io.*;

class Server
{
    
public static void main(String[] args) throws Exception
    {
        ServerSocket serverSocket 
= new ServerSocket(1234);
        System.out.println(
"服務器已經啓動,端口號:1234");
        
while (true)
        {
            Socket socket 
= serverSocket.accept();
            socket.setOOBInline(
true);
            InputStream in 
= socket.getInputStream();
            InputStreamReader inReader 
= new InputStreamReader(in);
            BufferedReader bReader 
= 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
    {
        Socket socket 
= new Socket("127.0.0.1"1234);
        socket.setOOBInline(
true);
        OutputStream out 
= socket.getOutputStream();
        OutputStreamWriter outWriter 
= new OutputStreamWriter(out);
        outWriter.write(
67);              // 向服務器發送字符"C"
        outWriter.write("hello world\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

 

java mynet.Server

    運行Client

 

java mynet.Client

    在服務端控制檯的輸出結果

 

服務器已經啓動,端口號:1234
ABChello world
中國

    在ClienT類中使用了sendUrgentData方法向服務器發送了字符'A'(65)和'B'(66)。但發送'B'時實際發送的是322,由於sendUrgentData只發送整型數的低字節。因此,實際發送的是66.十進制整型322的二進制形式如圖1所示。

圖1  十進制整型322的二進制形式

    從圖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來發送數據
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章