Socket/ServerSocket 选项
?
Socket選項
1.TCP_NODELAY
在Socket發送數據時,默認情況下,數據會先進入緩沖區,等緩沖區滿了再發送出去,意圖是為了通過減少傳輸數據的次數,以此來提高通信效率。
但是,對于一些需要即時發送,即時響應的場景并不合適,比如網絡游戲。客戶端因為采用的默認行為,會等到緩沖區滿了后才發送數據,服務端的響應相應也會變慢,導致整個游戲運行起來不流暢。
這時,就要開啟TCP_NODELAY,關閉默認行為,實時發送實時響應。
2.SO_TIMEOUT
在讀取數據時,常常因為種種原因,導致讀方法阻塞,為了保證阻塞時間可控,可以設置SO_TIMEOUT選項,設置讀數據時的最大等待時間,如果阻塞時間超過設置的時間,
則會拋出【Exception in thread "main" java.net.SocketTimeoutException: Read timed out】。示例如下。
客戶端代碼:阻塞6000ms后才發送數據
public class Main {public static void main(String[] args) throws InterruptedException, IOException {Socket socket = new Socket("127.0.0.1", 8899);System.out.println("客戶端啟動...");OutputStream out = socket.getOutputStream();Thread.sleep(6000); //阻塞6000ms后才發送數據out.write("This is for everyone".getBytes());} }
服務端代碼:通過SO_TIMEOUT選項,設置讀阻塞時長最大為5000ms,由于客戶端再6000ms后才會出數據,所以會拋出SocketTimeoutException異常。
public class SimpleServer {public static void main(String[] args) throws IOException, InterruptedException {ServerSocket serverSocket = new ServerSocket(8899, 2);System.out.println("服務端啟動...");while (true) {Socket socket = serverSocket.accept();System.out.println("連接成功:" + socket);soTimeout(socket);}}private static void soTimeout(Socket socket) throws IOException {socket.setSoTimeout(5000); //設置最大阻塞時間為5000msInputStream in = socket.getInputStream();byte[] buf = new byte[1024];int len = 0;while ((len = in.read(buf)) >= 0) { //因為阻塞時間超過5000ms,所以這里拋出【Exception in thread "main" java.net.SocketTimeoutException: Read timed out】異常System.out.println("read,len=" + len + ",str=" + new String(buf, 0, len));}} }
?
3.SO_REUSEADDR
對于Socket來說,當通過Socket.close()關閉時,底層的Socket為了把數據發送完或者接受完,不會立即關閉而是會等待一段時間,確保完成自己的使命。
但這會帶來一個問題,由于Socket的端口號不能共享,一旦一個端口號被占用,之后再用這個端口號嘗試建立新連接的話會報端口沖突的異常。
為了確保一個進程關閉Socket后,同一主機的其他進程還能使用這個端口,可以設置SO_REUSEADDR選項。
?
4.SO_LINGER
SO_LINGER選項用來控制Socket關閉時的行為,上面已經簡單講過,默認情況下,調用Socket.close()方法后,close()方法會立即返回,但是底層的Socket會等待數據發送完成后再關閉。
當設置Socket.setSoLinger(true, 0)選項時,close()方法也會立即返回,底層的Socket也會立即關閉,未發送完的數據會拋棄掉。
當設置Socket.setSoLinger(true, n)選項時,close()方法會阻塞,直到滿足下列條件中的一個才會返回。
1. n秒后,即使底層Socket還未發送完,也強制關閉底層Socket,并返回close()方法。
2. 底層Socket數據全部發送完(全部送到緩沖區)。
示例如下。
服務端代碼:沒什么特別的,就是從Socket中讀出數據,重點在客戶端
public class SimpleServer {public static void main(String[] args) throws IOException, InterruptedException {ServerSocket serverSocket = new ServerSocket(8899, 2);System.out.println("服務端啟動...");while (true) {Socket socket = serverSocket.accept();System.out.println("連接成功:" + socket);soLinger(socket);}}// 沒什么特別的,就是從Socket中讀出數據,重點在客戶端private static void soLinger(Socket socket) throws InterruptedException, IOException {InputStream in = socket.getInputStream();byte[] buf = new byte[1024];int len = 0;while ((len = in.read(buf)) >= 0) {System.out.println("read,len=" + len + ",str=" + new String(buf, 0, len));}} }
?
客戶端代碼:首先是不設置SO_LINGER選項,已默認的方式運行。之后#1,close()方法立即返回。#2,close()方法阻塞了4ms。
public class Main {public static void main(String[] args) throws InterruptedException, IOException {Socket socket = new Socket("127.0.0.1", 8899);//socket.setSoLinger(true, 0); #1//socket.setSoLinger(true, 10); #2System.out.println("客戶端啟動...");OutputStream out = socket.getOutputStream();StringBuilder sb = new StringBuilder();for (int i = 0; i < 500000; i++) {sb.append(i);}out.write(sb.toString().getBytes());long start = System.currentTimeMillis();socket.close();long end = System.currentTimeMillis();System.out.println("close:" + (end - start)); //#1:0 #2:4 } }
?
默認情況下,close()方法立即返回,Socket底層在發送完數據后關閉,服務端正常運行。
當把#1的注釋給刪掉,close()方法立即返回,Socket底層也會立即關閉,因為客戶端數據沒有完全發出,所以服務端運行時拋出了【Exception in thread "main" java.net.SocketException: Connection reset】異常
當吧#2的注釋給刪掉,close()方法阻塞了4ms才返回,Socket底層在發送完數據后關閉,服務端正常運行。
?
5.SO_RCVBUF,SO_SNDBUF
設置輸入,輸出緩沖區的大小。
一般情況下,對于數據量大,傳輸頻率低的場景,適合大的緩沖區,減少傳輸數據的次數,提高傳輸效率,比如FTP、HTTP等。
對于數據量小,傳輸頻率高,對實時性要求高的場景,適合設置小的緩沖區。
?
6.SO_KEEPALIVE
檢測TCP連接的有效性。大概機制是,2小時后雙方沒有過交互一直處于空閑狀態,則會發送一個請求,如果請求沒有響應,則會認為對方已經關閉,這樣本地的Socket也會自動關閉。
?
ServerSocket選項
1.SO_TIMEOUT
ServerSocket.accept()方法是個阻塞方法,會一直阻塞到有一個TCP連接成功建立,SO_TIMEOUT選項則可以設置最大的阻塞時間,超過這個時間還沒有建立TCP連接的話則拋出異常。
注意和Socket的SO_TIMEOUT選項區別開,Socket的SO_TIMEOUT選項是設置讀操作的最大阻塞時間,ServerSocket的SO_TIMEOUT選項是設置accep()操作的最大阻塞時間。
服務端代碼:5000ms后任然沒有與某個客戶端成功建立連接,所以會拋出SocketTimeoutException異常。
public class SimpleServer {public static void main(String[] args) throws IOException, InterruptedException {ServerSocket serverSocket = new ServerSocket(8899, 2);serverSocket.setSoTimeout(5000); System.out.println("服務端啟動...");while (true) {Socket socket = serverSocket.accept(); SO_TIMEOUT選項為5000ms,超過這段時間后,拋出【Exception in thread "main" java.net.SocketTimeoutException: Accept timed out】System.out.println("連接成功:" + socket);}} }
?
2.SO_REUSEADDR
同Socket的SO_REUSEADDR選項類似,為了保證一個進程關閉ServerSocket后,即使它還沒有釋放端口,同一主機的其他進程還能使用這個端口,可以設置SO_REUSEADDR解決這個問題。
對于Socket來講,一般Socket的端口號都是系統隨機分配的,碰巧碰到同一端口的情況比較少。但是對于ServerSocket來講,往往用的都是固定端口號,所以同一端口的情況就不能忽視了。
?
3.SO_RCVBUF
設置ServerSocket輸入緩沖區的大小。
?
引用
1.《Java網絡編程精解》(孫衛琴)
posted on 2019-03-11 09:26 NET未來之路 閱讀(...) 評論(...) 編輯 收藏轉載于:https://www.cnblogs.com/lonelyxmas/p/10508664.html
總結
以上是生活随笔為你收集整理的Socket/ServerSocket 选项的全部內容,希望文章能夠幫你解決所遇到的問題。