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也沒法生效。若是真的請求過多,只會讓你的服務器宕機(相信不少人都是這麼寫,要注意點)java

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鏈接發送數據,讓對方緊急處理。併發

因此這兩個參數,大家能夠忽略吧,想知道更多的,本身查下資料。socket

SoTimeout

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

 KeepAlive測試

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

SendBufferSize和ReceiveBufferSize

TCP發送緩存區和接收緩存區,默認是8192,通常狀況下足夠了,並且就算你增長了發送緩存區,對方沒有增長它對應的接收緩衝,那麼在TCP三握手時,最後肯定的最大發送窗口仍是雙方最小的那個緩衝區,就算你無視,發了更多的數據,那麼多出來的數據也會被丟棄。除非雙方都協商好。

相關文章
相關標籤/搜索