Java必知必會之socket

Internet上,數據按有限大小的包傳輸,這些包成爲數據報(datagram),每一個數據報包含一個首部(header)和一個有效載荷(payload)。首部包含包發送到的地址和端口、包來自的地址和端口、檢測數據是否被破壞的校驗和,以及用於保證可靠傳輸的各類其餘管理信息。java

有效載荷包含數據自己。程序員

不過,因爲數據報長度有限,一般必須將數據分解爲多個包,再在目的地從新組合。也有可能一個包或多個包在傳輸中丟失或遭到破壞,須要重傳。或者包亂序到達,須要從新排序。全部這些(將數據分解爲包、生成首部、解析入站包的首部、跟蹤哪些包已經收到而哪些沒有收到等)是很繁重的工做,須要大量複雜的代碼。算法

Socket幫你掩蓋了這些底層細節,如錯誤檢測、包大小、包分解、包重傳、網絡地址等。Socket容許程序員將網絡鏈接看做是另一個能夠讀寫字節的流。服務器

Socket是兩臺主機之間的一個鏈接,它能夠完成7個基本操做:
1)鏈接遠程主機
2)發送數據
3)接收數據
4)關閉鏈接
5)綁定端口
6)監聽入站數據
7)在綁定端口上接受來自遠程機器的鏈接網絡

一旦創建了socket鏈接,就可使用輸入輸出流,這個鏈接是全雙工的(full-duplex),兩臺主機均可以同時發送和接收數據。socket

SMTP是服務器之間或郵件客戶端與服務器之間傳輸電子郵件所用的協議。函數

半關閉Socket:close方法同時關閉Socket的輸入流和輸出流,若是隻但願關閉鏈接的一半(輸入/輸出),調用shutdownInput或shutdownOutput方法便可。這兩個方法並不關閉Scoket,實際上,它會調整與Socket鏈接的流,使它認爲已經到了流的末尾。關閉輸入以後,再讀取輸入流會返回-1,關閉輸出流以後再寫入Socket則會拋出一個IOException異常。
即便半關閉了鏈接,或者將鏈接的兩半都關閉了,使用結束後仍是須要關閉該Socket。shutdown只是影響了socket流,並不釋放與socket關聯的資源,如佔用的端口等。性能

java.net.Socket是Java完成客戶端TCP操做的基礎類,它使用原生代碼與主機操做系統的本地TCP棧進行通訊。大數據

public Socket(String host, int port) throws UnknownHostException, IOException
public Socket(InetAddress host, int port) throws IOException操作系統

這兩個構造函數,在返回以前會與遠程主機創建一個活動的網絡鏈接。port在1~65535之間。

public Socket()
public Socket(Proxy proxy)
protected Socket(SocketImpl impl)
這三個函數能夠建立未鏈接的Socket。

public Socket(String host, int port, InetAddress interface, int localPort)

throws IOException, UnknownHostException

public Socket(InetAddress host, int port, InetAddress interface, int localPort)

throws IOException

這兩個構造函數能夠用來指定從哪一個接口和端口鏈接。

SocketAddress

SocketAddress表示一個鏈接端點,理論上能夠用於TCP和非TCP socket。實際上只支持TCP/IP Socket。

SocketAddress主要是爲了暫時的socket鏈接信息(如IP地址和端口)提供一個方便的存儲,即便最初的socket已斷開並被垃圾回收,這些信息也能夠重用來建立新的Socket。

boolean connected = socket.isConnected() && ! socket.isClosed();

isConnected表示是否鏈接過一個遠程主機,即便socket已經關閉,於是要判斷socket是否打開着的,還要判斷是否已經關閉了。

Socket選項

1)TCP_NODELAY

設置爲true,可確保包會盡量快地發送,而不論包的大小。
正常狀況下,小數據包在發送前會組合爲更大的包,在發送另外一個包以前,本地主機要等待遠程系統對前一個包的確認,這稱爲Nagle算法。

Nagle算法的問題是若是遠程系統沒有足夠快地將確認發送回本地系統,那麼依賴於小數據量信息穩定傳輸的應用程序會變慢。對於網絡或遊戲等計算機應用程序(服務器須要實時跟蹤客戶端鼠標的移動),這個問題尤其嚴重,在一個至關慢的網絡中,即便簡單地打字也會因爲持續的緩衝而變得太慢。設置爲true,能夠關閉這種緩衝模式,這樣素有包一旦就緒就會發送。

2)SO_LINGER

指定了Socket關閉時如何處理還沒有發送的數據報,默認狀況下,close方法將當即返回,但系統仍然會嘗試發送剩餘的數據,若是延遲時間設置爲0,那麼當Socket關閉時,全部未發送的數據包都將被丟棄,若是SO_LINGER打開並且延遲時間設置爲任意正數,close方法會阻塞指定的時間,等待發送數據和接收確認。指定時間一過,Socket關閉,全部剩餘的數據都不會發送,也不會接收確認。
返回-1表示該選項被禁用,會根據須要用更多的時間發送剩餘的數據。

3)SO_TIMEOUT

正常狀況下,嘗試從Socket讀取數據時,read()調用會阻塞儘量長的時間來獲得足夠的字節。設置timeout確保這個調用會阻塞的時間不會超過指定的閾值,若是超出則拋異常,可是Socket仍然是鏈接的,能夠再次嘗試肚餓去這個Socket,下一次調用可能會成功。
0表示無限超時。

4)SO_RCVBUF和SO_SNDBUF

TCP使用緩衝區來提高網絡性能,較大的緩衝區會提高快速鏈接(好比10M/s)的性能,而較慢的撥號鏈接利用較小的緩衝區會有更好地表現。

通常來說,傳輸連續的大數據塊時(在ftp和http中較爲常見),能夠從大緩衝區收益,而對於交互式會話的小數據量傳輸(好比telnet和不少遊戲),大緩衝區則沒有多大幫助。現在128K字節已是一個常見的默認設置。

能夠達到的最大帶寬=緩衝區大小/延遲。例如,xp上,假設兩個主機之間的延遲爲500ms,xp上的緩衝區大小爲17520字節,則帶寬=17520/0.5=273.75kb/s。這是Socket的最大速度,而不論網絡速度有多快。對於一個撥號鏈接來講,這樣的速度已經很快了。

能夠經過減小延遲來提高速度,不過,延遲與網絡硬件有關,另外還取決於你的應用控制以外的其餘一些因素。
若是把緩衝區從17520字節提高到128K,則最大帶寬會增長到2Mb/s,若是加到256K時,最大帶寬會增大到4Mb/s。

固然網絡自己對最大帶寬也是有限制的,若是將緩衝區設置的太高,程序會試圖以太高的速度發送和接收數據,而網絡來不及處理,這就會致使擁塞、丟包和性能降低。所以,要獲得最大帶寬,須要讓緩衝區大小與鏈接的延遲匹配,是它稍小於網絡的帶寬。

能夠用ping去檢測主機的延遲。

SO_RCVBUF控制用於網絡輸入的建議的接收緩衝區的大小,雖然能夠獨立地設置接收和發送緩衝區的大小,可是實際上緩衝區一般會設置爲兩者中較小的一個。

Linux系統一般指定一個最大緩衝區大小,通常是64KB或256KB,並且不容許任何socket有更大的緩衝區。

通常狀況下,若是你發送你的應用不能充分利用可用帶寬(例如,你有一個25Mb/s的Internet鏈接,可是數據傳輸速率僅爲1.5Mb/s),那麼能夠試着增長緩衝區的大小;相反,若是存在丟包和擁塞現象,則要減小緩衝區大小。

不過,大部分狀況,除非網絡在某個方向上負載過大,不然默認值就很合適。具體來講,現代操做系統使用TCP窗口縮放來動態調整緩衝區的大小,以適應網絡。

通常經驗是,除非你檢測到某個問題,不然不要進行調整。通常調整操做系統的最大緩衝區比在Java裏頭調整單個socket的緩衝區大小效益要高。

5)SO_KEEPALIVE

若是打開這個,客戶端偶爾會經過一個空閒鏈接發送一個數據包(通常兩小時一次),以確保服務器沒有崩潰。若是服務器沒能響應這個包,客戶端會持續嘗試11分鐘多的時間,直到接收到響應爲止。若是在12分鐘內未收到響應,則客戶端就關閉socket。若是不打開這個,不活動的客戶端可能會永遠存在下去,而不會注意到服務器是否已經崩潰。

6)OOBINLINE

TCP包括一個能夠發送單字節帶外(Out Of Band,OOB)緊急數據的特性。這個數據會當即發送,此外,當接收方收到緊急數據時會獲得通知,在處理其餘已收到的數據以前能夠選擇先處理這個緊急數據(必要時flush當前緩衝區)。

Java裏對應的方法是sendUrgentData

默認狀況下,Java會忽略從Socket接收的緊急數據,若是但願接收到正常數據中的緊急數據,須要setOOBInline爲true。一旦開啓,到達的任何緊急數據將以正常方式放在Socket的輸入流中等待讀取。

7)SO_REUSEADDR

一個Socket關閉時,可能不會當即釋放本地端口,尤爲是當Socket關閉時若仍有一個打開的鏈接,就不會釋放本地端口,有時會等待一小段時間,確保接收到全部要發送到這個端口的延遲數據包,Socket關閉時這些數據包可能仍在網絡上傳輸,系統不會對接收到的延遲包作任何處理,只是但願確保這些數據不會意外地傳入綁定到同一端口的新進程。

若是使用隨機端口,則問題不大,可是若是Socket綁定到一個已知的端口,可能會有問題,由於這會阻止全部其餘Socket同時使用這個端口,若是開啓這個參數(默認是關閉),則容許另一個Socket綁定到這個端口,即便此時仍有可能存在前一個Socket未接收的數據。

setReuseAddress必須在綁定新Socket以前調用。只有以前鏈接的Socket和重用老地址的新Scoket的這個值都設置爲true,才能生效。

8)IP_TOS

不一樣類型的Internet服務對性能有不一樣的需求,好比視頻要求相對較高的帶寬和較短的延遲,而email能夠經過較低帶寬的鏈接傳遞等。
服務類型存儲在IP首部中的一個名爲IP_TOS的8位字段中。在Java中使用setTrafficClass來設定,java裏頭是0-255,可是TCP首部要求是8位,於是只能使用int的低字節。

Socket異常1)BindException,端口被佔用或沒有權限使用該端口2)ConnectException,鏈接遠程主機被拒絕(遠程主機忙或者沒有進程監聽該端口)3)NoRouteToHostException,鏈接超時4)ProtocolException,違反TCP/IP規定

相關文章
相關標籤/搜索