天天看點

Java網絡程式設計從入門到精通(29):服務端Socket的選項

本文為原創,如需轉載,請注明作者和出處,謝謝!

ServerSocket類有以下三個選項:

1.      

SO_TIMEOUT: 設定accept方法的逾時時間。

2.      

SO_REUSEADDR:設定服務端同一個端口是否可以多次綁定。

3.      

SO_RECBUF:設定接收緩沖區的大小。

一、SO_TIMEOUT選項

可以通過SeverSocket類的兩個方法(setSoTimeout和getSoTimeout)來設定和獲得SO_TIMEOUT選項的值,這兩個方法的定義如下:

public synchronized void setSoTimeout(int timeout) throws SocketException

public synchronized int getSoTimeout() throws IOException

setSoTimeout方法的timeout參數表示accept方法的逾時時間,機關是毫秒。在通常情況下,ServerSocket類的accept方法在等待用戶端請求時處于無限等待狀态。如HTTP伺服器在沒有使用者通路網頁時會一直等待使用者的請求。一般不需要對服務端設定等待用戶端請求逾時,但在某些特殊情況下,服務端規定用戶端必須在一定時間内向服務端送出請求,這時就要設定等待用戶端請求逾時,也就是accept方法的逾時時間。當設定用戶端請求逾時後,accept方法在等待逾時時間後抛出一個SocketTimeoutException異常。下面的代碼示範了如何設定和獲得SO_TIMEOUT選項的值,逾時時間通過指令行參數方式傳入AcceptTimeout。

package server;

import java.net.*;

public class AcceptTimeout

{

    public static void main(String[] args) throws Exception

    {

        if (args.length == 0)

            return;

        ServerSocket serverSocket = new ServerSocket(1234);

        int timeout = Integer.parseInt(args[0]);

        serverSocket.setSoTimeout(Integer.parseInt(args[0]));

        System.out.println((timeout > 0) ? "accept方法将在"

                + serverSocket.getSoTimeout() + "毫秒後抛出異常!" : "accept方法永遠阻塞!");;

        serverSocket.accept();

    }

}

執行下面的指令:

java server.AcceptTimeout 3000

運作結果:

accept方法将在3000毫秒後抛出異常!

Exception in thread "main" java.net.SocketTimeoutException: Accept timed out

    at java.net.PlainSocketImpl.socketAccept(Native Method)

    at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:384)

    at java.net.ServerSocket.implAccept(ServerSocket.java:450)

    at java.net.ServerSocket.accept(ServerSocket.java:421)

    at chapter5.AcceptTimeout.main(AcceptTimeout.java:16)

setSoTimeout方法可以在ServerSocket對象綁定端口之前調用,也以在綁定端口之後調用。如下面的代碼也是正确的:

ServerSocket serverSocket = new ServerSocket();

serverSocket.setSoTimeout(3000);

serverSocket.bind(new InetSocketAddress(1234));

二、SO_REUSEADDR選項

SO_REUSEADDR選項決定了一個端口是否可以被綁定多次。可以通過SeverSocket類的兩個方法(setReuseAddres和getReuseAddress)來設定和獲得SO_TIMEOUT選項的值,這兩個方法的定義如下:

public void setReuseAddress(boolean on) throws SocketException 

public boolean getReuseAddress() throws SocketException

在大多數作業系統中都不允許一個端口被多次綁定。如果一個ServerSocket對象綁定了已經被占用的端口,那麼ServerSocket的構造方法或bind方法就會抛出一個BindException異常。

Java提供這個選項的主要目的是為了防止由于頻繁綁定釋放一個固定端口而使系統無法正常工作。當ServerSocket對象關閉後,如果ServerSocket對象中仍然有未處理的資料,那麼它所綁定的端口可能在一段時間内不會被釋放。這就會造成其他的ServerSocket對象無法綁定這個端口。在設定這個選項時,如果某個端口是第一次被綁定,無需調用setReuseAddress方法,而再次綁定這個端口時,必須使用setReuseAddress方法将這個選項設為true。而且這個方法必須在調用bind方法之前調用。下面的代碼示範了如何設定和獲得這個選項的值:

public class TestReuseAddr1

        ServerSocket serverSocket1 = new ServerSocket(1234);

        System.out.println(serverSocket1.getReuseAddress());

        ServerSocket serverSocket2 = new ServerSocket();

        serverSocket2.setReuseAddress(true);

        serverSocket2.bind(new InetSocketAddress(1234));

        ServerSocket serverSocket3 = new ServerSocket();

        serverSocket3.setReuseAddress(true);

        serverSocket3.bind(new InetSocketAddress(1234));

運作結果:false

在上面代碼中第一次綁定端口1234,是以,serverSocket1對象無需設定SO_REUSEADDR選項(這個選項在大多數作業系統上的預設值是false)。而serverSocket2和serverSocket3并不是第一次綁定端口1234,是以,必須設定這兩個對象的SO_REUSEADDR值為true。在設定SO_REUSEADDR選項時要注意,必須在ServerSocket對象綁定端口之前設定這個選項。

    也許有的讀者可能有這樣的疑問。如果多個ServerSocket對象同時綁定到一個端口上,那麼當用戶端向這個端口送出請求時,該由哪個ServerSocket對象來接收用戶端請求呢?在給出答案之前,讓我們先看看下面的代碼的輸出結果是什麼。

public class TestReuseAddr2 extends Thread

    String s;

    public void run()

        try

        {

            ServerSocket serverSocket = new ServerSocket();

            serverSocket.setReuseAddress(true);

            serverSocket.bind(new InetSocketAddress(1234));

            Socket socket = serverSocket.accept();

            System.out.println(s + ":" + socket);

            socket.close();

            serverSocket.close();

        }

        catch (Exception e)

    public TestReuseAddr2(String s)

        this.s = s;

    public static void main(String[] args)

        for (int i = 1; i <= 5; i++)

            new TestReuseAddr2("ServerSocket" + i).start();

java server.TestReuseAddr2

    連續執行5次下面的指令:

telnet localhost 1234

執行結果:

ServerSocket1:Socket[addr=/127.0.0.1,port=11724,localport=1234]

ServerSocket3:Socket[addr=/127.0.0.1,port=11725,localport=1234]

ServerSocket5:Socket[addr=/127.0.0.1,port=11726,localport=1234]

ServerSocket2:Socket[addr=/127.0.0.1,port=11727,localport=1234]

ServerSocket4:Socket[addr=/127.0.0.1,port=11728,localport=1234]

    上面的運作結果隻是一種可能,如果多次按着上面的步驟操作,可能得到不同的運作結果。由此可以斷定,當多個ServerSocket對象同時綁定一個端口時,系統會随機選擇一個ServerSocket對象來接收用戶端請求。但要注意,這個接收用戶端請求的ServerSocket對象必須關閉(如019行如示),才能輪到其他的ServerSocket對象接收用戶端請求。如果不關閉這個ServerSocket對象,那麼其他的ServerSocket對象将永遠無法接收用戶端請求。讀者可以将

serverSocket.close()去掉,再執行上面操作步驟,看看會有什麼結果。

三、SO_RCVBUF選項

可以通過SeverSocket類的兩個方法(setReceiveBufferSize和getReceiveBufferSize)來設定和獲得SO_RCVBUF選項的值,這兩個方法的定義如下:

public synchronized void setReceiveBufferSize (int size) throws SocketException

public synchronized int getReceiveBufferSize() throws SocketException

    其中size參數表示接收緩沖區的大小,機關是位元組。設定了ServerSocket類的SO_RCVBUF選項,就相當于設定了Socket對象的接收緩沖區大小。這個Socket對象是由accept傳回的。下面積代碼示範了如何使用這兩個方法來設定和獲得接收緩沖區的大小:

public class TestReceiveBufferSize

        serverSocket.setReceiveBufferSize(2048); // 将接收緩沖區設為2K

        while (true)

            // 如果用戶端請求使用的是本地IP位址,重新将Socket對象的接

            // 收緩沖區設為1K            

            if (socket.getInetAddress().isLoopbackAddress())

                socket.setReceiveBufferSize(1024);

            System.out.println("serverSocket:"

                            + serverSocket.getReceiveBufferSize());

            System.out.println("socket:" + socket.getReceiveBufferSize());

執行如下指令:

java server.TestReceiveBufferSize

執行如下三個指令 (192.168.18.100為本機IP位址):

telnet 192.168.18.100 1234

serverSocket:2048

socket:2048

socket:1024

從上面的運作結果可以看出,在執行telnet localhost 1234指令後,由于localhost是本地位址,是以程式通過将Socket對象的接收緩沖區設為1024,而在執行其他兩條指令後,由于192.168.18.100不是本機位址,是以Socket對象的接收緩沖區仍然保留着serverSocket的值:2048。是以,我們可以得出一個結論,設定ServerSocket對象的接收緩沖區就相當于設定了所有從accept傳回的Socket對象的接收緩沖區,隻要不單獨對某個Socket對象重新設定,這些Socket對象的接收緩沖區就會都保留這個值。

無論在ServerSocket對象綁定到端口之前還是之後設定SO_RCVBUF選項都有效,但如果要設定大于64K的接收緩沖區時,就必須在ServerSocket對象綁定端口之前設定SO_RCVBUF選項。如下面的代碼将接收緩沖區的大小設為100K。

ServerSocket serverSocket = new ServereSocket();

serverSocket. setReceiveBufferSize(100 * 1024);  // 将接收緩沖區的大小設為100K。

   一般情況下,并不需要設定這個選項,它的預設值(一般為8K)足可以滿足大多數情況。但有時為了适應特殊的需要,必須更改接收緩沖區的值。如在一些網絡遊戲中,需要實時地向伺服器傳送各種動作、指令資訊。這就需要将接收緩沖區設小一點。這樣可以在一定程度上增加遊戲用戶端的靈敏度。如果需要傳送大量的資料,如HTTP、FTP等協定。這就需要較大的接收緩沖區。

四、設定ServerSocket的性能偏好 

在Java SE5.0及以上版本中為ServerSocket類增加了一個setPerformancePreferences方法。這個和方法和Socket類中的setPerformancePreferences的作用一樣,用來設定連接配接時間、延遲和帶寬的相對重要性。setPerformancePerferences方法的定義如下:

public void setPerformancePreferences(int connectionTime, int latency, int bandwidth)

<a href="http://www.eoeandroid.com/forumdisplay.php?fid=4">國内最棒的Google Android技術社群(eoeandroid),歡迎通路!</a>

繼續閱讀