Socket選擇能夠指定Socket類發送和接受數據的方式。在JDK1.4中共有8個Socket選擇能夠設置。這8個選項都定義在java.net.SocketOptions接口中。定義以下:html
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方法,分別用來得到和設置這些選項。java
PS:算法
netty的有差選項說明:http://docs.jboss.org/netty/3.2/api/org/jboss/netty/channel/socket/SocketChannelConfig.html編程
oracle的說明 http://docs.oracle.com/javase/6/docs/technotes/guides/net/socketOpt.htmlapi
public boolean getTcpNoDelay() throws SocketExceptionpublic void setTcpNoDelay(boolean on) throws SocketException
在默認狀況下,客戶端向服務器發送數據時,會根據數據包的大小決定是否當即發送。當數據包中的數據不多時,如只有1個字節,而數據包的頭卻有幾十個 字節(IP頭+TCP頭)時,系統會在發送以前先將較小的包合併到軟大的包後,一塊兒將數據發送出去。在發送下一個數據包時,系統會等待服務器對前一個數據 包的響應,當收到服務器的響應後,再發送下一個數據包,這就是所謂的Nagle算法;在默認狀況下,Nagle算法是開啓的。服務器
這種算法雖然能夠有效地改善網絡傳輸的效率,但對於網絡速度比較慢,並且對實現性的要求比較高的狀況下(如遊戲、Telnet等),使用這種方式傳 輸數據會使得客戶端有明顯的停頓現象。所以,最好的解決方案就是須要Nagle算法時就使用它,不須要時就關閉它。而使用setTcpToDelay正好 能夠知足這個需求。當使用setTcpNoDelay(true)將Nagle算法關閉後,客戶端每發送一次數據,不管數據包的大小都會將這些數據發送出 去。網絡
public boolean getReuseAddress() throws SocketException public void setReuseAddress(boolean on) throws SocketException
錯誤的說法:oracle
經過這個選項,可使多個Socket對象綁定在同一個端口上。
正確的說明是:socket
若是端口忙,但TCP狀態位於 TIME_WAIT ,能夠重用 端口。若是端口忙,而TCP狀態位於其餘狀態,重用端口時依舊獲得一個錯誤信息, 拋出「Address already in use: JVM_Bind」。若是你的服務程序中止後想當即重啓,不等60秒,而新套接字依舊 使用同一端口,此時 SO_REUSEADDR 選項很是有用。必須意識到,此時任何非期 望數據到達,均可能致使服務程序反應混亂,不過這只是一種可能,事實上很不可能。
這個參數在Windows平臺與Linux平臺表現的特色不同。在Windows平臺表現的特色是不正確的, 在Linux平臺表現的特色是正確的。ide
在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選項時有兩點須要注意:
必須在調用bind方法以前使用setReuseAddress方法來打開SO_REUSEADDR
選項。所以,要想使用SO_REUSEADDR選項,就不能經過Socket類的構造方法來綁定端口。
必須將綁定同一個端口的全部的Socket對象的SO_REUSEADDR選項都打開才能起做用。如在例程4-12中,socket1和socket2都使用了setReuseAddress方法打開了各自的SO_REUSEADDR
選項。
在Windows操做系統上運行上面的代碼的運行結果以下: 這種結果是不正確的。
socket1.getReuseAddress():truesocket2.getReuseAddress():true
在Linux操做系統上運行上面的代碼的運行結果以下:
socket1.getReuseAddress():true java.net.BindException: Address already in use at java.net.PlainSocketImpl.socketBind(Native Method) at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:383) at java.net.ServerSocket.bind(ServerSocket.java:328) at java.net.ServerSocket.bind(ServerSocket.java:286) at com.Test.main(Test.java:15)
這種結果是正確的。由於第一個鏈接不是TIME_WAIT狀態的,第二個鏈接就不能使用8899端口;
只有第一個鏈接是TIME_WAIT狀態的,第二個鏈接就才能使用8899端口;
public int getSoLinger() throws SocketExceptionpublic 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(true, 60);
public int getSoTimeout() throws SocketExceptionpublic 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例外。
public int getSendBufferSize() throws SocketExceptionpublic void setSendBufferSize(int size) throws SocketException
在默認狀況下,輸出流的發送緩衝區是8096個字節(8K)。這個值是Java所建議的輸出緩衝區的大小。若是這個默認值不能知足要求,能夠用 setSendBufferSize方法來從新設置緩衝區的大小。但最好不要將輸出緩衝區設得過小,不然會致使傳輸數據過於頻繁,從而下降網絡傳輸的效 率。
若是底層的Socket實現不支持SO_SENDBUF選項,這兩個方法將會拋出SocketException例外。必須將size設爲正整數,不然setSendBufferedSize方法將拋出IllegalArgumentException例外。
public int getReceiveBufferSize() throws SocketExceptionpublic void setReceiveBufferSize(int size) throws SocketException
在默認狀況下,輸入流的接收緩衝區是8096個字節(8K)。這個值是Java所建議的輸入緩衝區的大小。若是這個默認值不能知足要求,能夠用 setReceiveBufferSize方法來從新設置緩衝區的大小。但最好不要將輸入緩衝區設得過小,不然會致使傳輸數據過於頻繁,從而下降網絡傳輸 的效率。
若是底層的Socket實現不支持SO_RCVBUF選項,這兩個方法將會拋出SocketException例外。必須將size設爲正整數,不然setReceiveBufferSize方法將拋出IllegalArgumentException例外。
public boolean getKeepAlive() throws SocketExceptionpublic void setKeepAlive(boolean on) throws SocketException
若是將這個Socket選項打開,客戶端Socket每隔段的時間(大約兩個小時)就會利用空閒的鏈接向服務器發送一個數據包。這個數據包並無其 它的做用,只是爲了檢測一下服務器是否仍處於活動狀態。若是服務器未響應這個數據包,在大約11分鐘後,客戶端Socket再發送一個數據包,若是在12 分鐘內,服務器還沒響應,那麼客戶端Socket將關閉。若是將Socket選項關閉,客戶端Socket在服務器無效的狀況下可能會長時間不會關閉。
SO_KEEPALIVE選項在默認狀況下是關閉的,可使用以下的語句將這個SO_KEEPALIVE選項打開:
socket1.setKeepAlive(true);
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
在服務端控制檯的輸出結果
服務器已經啓動,端口號:1234ABChello world中國
在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來發送數據。
網絡編程之 keepalive http://blog.csdn.net/historyasamirror/article/details/5526486
在Nettyk,connectionTimeoutMillis選項只能是client使用,server不能使用 http://stackoverflow.com/questions/3234433/what-is-the-meaning-of-child-connecttimeoutmillis-in-nettys-configuration
Netty中ReadTimeoutException的寫法:http://stackoverflow.com/questions/3726696/setting-socket-timeout-on-netty-channel