Socket中的異常和參數設置


1.常見異常
1.java.net.SocketTimeoutException . 這個異 常比較常見,socket 超時。一般有 2 個地方會拋出這個,一個是 connect 的 時 候 , 這 個 超 時 參 數 由connect(SocketAddress endpoint,int timeout) 中的后者來決定,還有就是 setSoTimeout(int timeout),這個是設定讀取的超時時間。它們設置成 0 均表示無限大。
2.java.net.BindException:Address already in use: JVM_Bind 。 該異常發生在服務 端進行new ServerSocket(port) 或者 socket.bind(SocketAddress bindpoint)操作時。
原因:與 port 一樣的一個端口已經被啟動,並進行監聽。此時用 netstat –an 命令,可以看到一個 Listending 狀態的端口。只需要找一個沒有被占用的端口就能解決這個問題。
3.java.net.ConnectException: Connection refused: connect。該異常發生在客戶端進行new Socket(ip, port)或者 socket.connect(address,timeout)操作時,
原 因:指定 ip 地址的機器不能找到(也就是說從當前機器不存在到指定 ip 路由),或者是該 ip 存在,但找不到指定的端口進行監聽。應該首先檢查客戶端的 ip 和 port是否寫錯了,假如正確則從客戶端 ping 一下服務器看是否能 ping 通,假如能 ping 通(服務服務器端把 ping 禁掉則需要另外的辦法),則 看在服務器端的監聽指定端口的程序是否啟動。
4.java.net.SocketException: Socket is closed ,該異常在客戶端和服務器均可能發生。異常的原因是己方主動關閉了連接后(調用了 Socket 的 close 方法)再對網絡連接進行讀寫操作。
5.java.net.SocketException: Connection reset 或者Connect reset by peer:Socket write error。 該異常在客戶端和服務器端均有可能發生,引起該異常的原因有兩個,第一個就是假如一端的 Socket 被關閉(或主動關閉或者因為異常退出而引起的關閉), 另一端仍發送數據,發送的第一個數據包引發該異常(Connect reset by peer)。另一個是一端退出,但退出時並未關閉該連接,另一端假如在從連接中讀數據則拋出該異常(Connection reset)。簡單的說就是在連接斷開后的讀和寫操作引起的。
對於服務器,一般的原因可以認為:
a) 服務器的並發連接數超過了其承載量,服務器會將其中一些連接主動 Down 掉.
b) 在數據傳輸的過程中,瀏覽器或者接收客戶端關閉了,而服務端還在向客戶端發送數據。
6.java.net.SocketException: Broken pipe。 該異常在客戶端和服務器均有可能發生。在拋出SocketExcepton:Connect reset by peer:Socket write error 后,假如再繼續寫數據則拋出該異常。前兩個異常的解決方法是首先確保程序退出前關閉所有的網絡連接,其次是要檢測對方的關閉連接操作,發現對方 關閉連接后自己也要關閉該連接。
對於 4 和 5 這兩種情況的異常,需要特別注意連接的維護。在短連接情況下還好,如果是長連接情況,對於連接狀態的維護不當,則非常容易出現異常。基本上對長連接需要做的就是:
a) 檢測對方的主動斷連(對方調用了 Socket 的 close 方法)。因為對方主動斷連,另一方如果在進行讀操作,則此時的返回值是-1。所以一旦檢測到對方斷連,則主動關閉己方的連接(調用 Socket 的 close 方法)。
b) 檢測對方的宕機、異常退出及網絡不通,一般做法都是心跳檢測。雙方周期性的發送數據給對方,同時也從對方接收“心跳數據”,如果連續幾個周期都沒有收到 對方心跳,則可以判斷對方或者宕機或者異常退出或者網絡不通,此時也需要主動關閉己方連接;如果是客戶端可在延遲一定時間后重新發起連接。雖然 Socket 有一個keep alive 選項來維護連接,如果用該選項,一般需要兩個小時才能發現對方的宕機、異常退出及網絡不通。
7.java.net.SocketException: Too many open files
原因: 操作系統的中打開文件的最大句柄數受限所致,常常發生在很多個並發用戶訪問服務器的時候。因為為了執行每個用戶的應用服務器都要加載很多文件(new 一個socket 就需要一個文件句柄),這就會導致打開文件的句柄的缺乏。
解決方式:
a) 盡量把類打成 jar 包,因為一個 jar 包只消耗一個文件句柄,如果不打包,一個類就消耗一個文件句柄。
b) java 的 GC 不能關閉網絡連接打開的文件句柄,如果沒有執行 close()則文件句柄將一直存在,而不能被關閉。
也可以考慮設置 socket 的最大打開 數來控制這個問題。對操作系統做相關的設置,增加最大文件句柄數量。
ulimit -a 可以查看系統目前資源限制,ulimit -n 10240 則可以修改,這個修改只對當前窗口有效。
2.常見參數設置

backlog:用於ServerSocket,配置ServerSocket的最大客戶端等待隊列。等待隊列的意思,先看下面代碼

public class Main {
public static void main(String[] args) throws Exception {
int port = 8999;
int backlog = 2;
ServerSocket serverSocket = new ServerSocket(port, backlog);
Socket clientSock = serverSocket.accept();
System.out.println("revcive from " + clientSock.getPort());
while (true) {
byte buf[] = new byte[1024];
int len = clientSock.getInputStream().read(buf);
System.out.println(new String(buf, 0, len));
}
}
}

這段測試代碼在第一次處理一個客戶端時,就不會處理第二個客戶端,所以除了第一個客戶端,其他客戶端就是等待隊列了。所以這個服務器最多可以同時連接3個客戶端,其中2個等待隊列。大家可以telnet localhost 8999測試下。這個參數設置為-1表示無限制,默認是50個最大等待隊列,如果設置無限制,那么你要小心了,如果你服務器無法處理那么多連接,那么當很多客戶端連到你的服務器時,每一個TCP連接都會占用服務器的內存,最后會讓服務器崩潰的。另外,就算你設置了backlog為10,如果你的代碼中是一直Socket clientSock = serverSocket.accept(),假設我們的機器最多可以同時處理100個請求,總共有100個線程在運行,然后你把在100個線程的線程池處理clientSock,不能處理的clientSock就排隊,最后clientSock越來越多,也意味着TCP連接越來越多,也意味着我們的服務器的內存使用越來越高(客戶端連接進程,肯定會發送數據過來,數據會保存到服務器端的TCP接收緩存區),最后服務器就宕機了。所以如果你不能處理那么多請求,請不要循環無限制地調用serverSocket.accept(),否則backlog也無法生效。如果真的請求過多,只會讓你的服務器宕機(相信很多人都是這么寫,要注意點)

TcpNoDelay:禁用納格算法,將數據立即發送出去。納格算法是以減少封包傳送量來增進TCP/IP網絡的效能,當我們調用下面代碼,如:

Socket socket = new Socket();  
socket.connect(new InetSocketAddress(host, 8000));
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
String head = "hello ";
String body = "world\r\n";
out.write(head.getBytes());
out.write(body.getBytes());

我們發送了hello,當hello沒有收到ack確認(TCP是可靠連接,發送的每一個數據都要收到對方的一個ack確認,否則就要重發)的時候,根據納格算法,world不會立馬發送,會等待,要么等到ack確認(最多等100ms對方會發過來的),要么等到TCP緩沖區內容>=MSS,很明顯這里沒有機會,我們寫了world后再也沒有寫數據了,所以只能等到hello的ack我們才會發送world,除非我們禁用納格算法,數據就會立即發送了。

SoLinger:當我們調用socket.close()返回時,socket已經write的數據未必已經發送到對方了,例如

Socket socket = new Socket();  
socket.connect(new InetSocketAddress(host, 8000));
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
String head = "hello ";
String body = "world\r\n";
out.write(head.getBytes());
out.write(body.getBytes());
socket.close();

這里調用了socket.close()返回時,hello和world未必已經成功發送到對方了,如果我們設置了linger而不小於0,如:

bool on = true;
int linger = 100;
....
socket.setSoLinger(boolean on, int linger)
......
socket.close();

那么close會等到發送的數據已經確認了才返回。但是如果對方宕機,超時,那么會根據linger設定的時間返回。

UrgentData和OOBInline

TCP的緊急指針,一般都不建議使用,而且不同的TCP/IP實現,也不同,一般說如果你有緊急數據寧願再建立一個新的TCP/IP連接發送數據,讓對方緊急處理。

所以這兩個參數,你們可以忽略吧,想知道更多的,自己查下資料。

SoTimeout

設置socket調用InputStream讀數據的超時時間,以毫秒為單位,如果超過這個時候,會拋出java.net.SocketTimeoutException。

 KeepAlive

keepalive不是說TCP的長連接,當我們作為服務端,一個客戶端連接上來,如果設置了keeplive為true,當對方沒有發送任何數據過來,超過一個時間(看系統內核參數配置),那么我們這邊會發送一個ack探測包發到對方,探測雙方的TCP/IP連接是否有效(對方可能斷點,斷網),在Linux好像這個時間是75秒。如果不設置,那么客戶端宕機時,服務器永遠也不知道客戶端宕機了,仍然保存這個失效的連接。

SendBufferSize和ReceiveBufferSize

TCP發送緩存區和接收緩存區,默認是8192,一般情況下足夠了,而且就算你增加了發送緩存區,對方沒有增加它對應的接收緩沖,那么在TCP三握手時,最后確定的最大發送窗口還是雙方最小的那個緩沖區,就算你無視,發了更多的數據,那么多出來的數據也會被丟棄。除非雙方都協商好。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM