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方法之前調用。下面的代碼演示了如何設置和獲得這個選項的值:
package server;
import java.net.*;
public class TestReuseAddr1
{
public static void main(String[] args) throws Exception
{
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對象來接收客戶端請求呢?在給出答案之前,讓我們先看看下面的代碼的輸出結果是什麼。
package server;
import java.net.*;
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返回的。下面積代碼演示了如何使用這兩個方法來設置和獲得接收緩沖區的大小:
package server;
import java.net.*;
public class TestReceiveBufferSize
{
public static void main(String[] args) throws Exception
{
ServerSocket serverSocket = new ServerSocket(1234);
serverSocket.setReceiveBufferSize(2048); // 將接收緩沖區設為2K
while (true)
{
Socket socket = serverSocket.accept();
// 如果客戶端請求使用的是本地IP地址,重新將Socket對象的接
// 收緩沖區設為1K
if (socket.getInetAddress().isLoopbackAddress())
socket.setReceiveBufferSize(1024);
System.out.println("serverSocket:"
+ serverSocket.getReceiveBufferSize());
System.out.println("socket:" + socket.getReceiveBufferSize());
socket.close();
}
}
}
執行如下命令:
java server.TestReceiveBufferSize
執行如下三個命令 (192.168.18.100為本機IP地址):
telnet 192.168.18.100 1234
telnet localhost 1234
telnet 192.168.18.100 1234
運行結果:
serverSocket:2048
socket:2048
serverSocket:2048
socket:1024
serverSocket:2048
socket:2048
從上面的運行結果可以看出,在執行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。
serverSocket.bind(new InetSocketAddress(1234));
一般情況下,並不需要設置這個選項,它的默認值(一般為8K)足可以滿足大多數情況。但有時為了適應特殊的需要,必須更改接收緩沖區的值。如在一些網絡游戲中,需要實時地向服務器傳送各種動作、指令信息。這就需要將接收緩沖區設小一點。這樣可以在一定程度上增加游戲客戶端的靈敏度。如果需要傳送大量的數據,如HTTP、FTP等協議。這就需要較大的接收緩沖區。
四、設置ServerSocket的性能偏好
在Java SE5.0及以上版本中為ServerSocket類增加了一個setPerformancePreferences方法。這個和方法和Socket類中的setPerformancePreferences的作用一樣,用來設置連接時間、延遲和帶寬的相對重要性。setPerformancePerferences方法的定義如下:
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth)