不少同窗知道在大學課程中,咱們學習的《計算機網絡》一書採用的是OSI七層網絡模型(OSI Model),可是OSI 七層模型是一種抽象模型,在操做系統實際實現中,採用的是TCP/IP四層網絡模型,四層模型將七層模型合併爲了應用層(Application Layer)、傳輸(Transport Layer)、網絡層(Internet Layer)、鏈路層(Link Layer),使得網絡系統在具體實現中更加簡化,OSI七層網絡模型與TCP/IP四層網絡模型以及協議對應關係以下表所示。在計算機系統中,分層是一種很重要的編程思想,分層思想將系統的功能與責任進行了層次化劃分,基本上全部系統的架構設計,都是按照層次架構做爲基本架構來設計的。在計算機網絡中,相同層次具備相同的協議處理方式,下層協議上層提供服務,上層協議的行爲控制着下層的工做狀態,層層之間責任單一,目的明確。
linux
二 、Java對於TCP/IP協議的實現
程序員
在網絡程序開發中,操做系統都爲咱們提供了全面 方便的應用層網絡操做類與接口,使得程序員在使用過程當中無需考慮協議棧的細節,而專心於數據的傳輸處理過程當中。固然,操做系統也爲程序員提供了能夠掌控協議細節的機會,例如使用原始套接字(Raw Socket)能夠控制TCP的三次握手的細節實現TCP SYN掃描(注意部分 Windows 7系統不支持原始套接字的半開掃描)。可是在大多常規網絡應用開發中,咱們都直接使用系統提供的應用層接口來實現網絡程序。這裏咱們羅列出在Java中常見的TCP/IP協議的實現類或方法,以下表所示:編程
三 、TCP協議爲何須要三次握手windows
首先咱們來看一下TCP協議三次握手的具體過程(本圖選自網絡):
服務器
第一次握手:創建鏈接。客戶端發送鏈接請求報文段,將SYN
位置爲1,Sequence Number
爲x;而後,客戶端進入SYN_SEND
狀態,等待服務器的確認;
網絡
第二次握手:服務器收到SYN
報文段。服務器收到客戶端的SYN
報文段,須要對這個SYN報文段進行確認,設置Acknowledgment Number
爲x+1(Sequence Number
+1);同時,本身還要發送SYN
請求信息,將SYN位置爲1,Sequence Number
爲y;服務器端將上述全部信息放到一個報文段(即SYN+ACK
報文段)中,一併發送給客戶端,此時服務器進入SYN_RECV
狀態;架構
第三次握手:客戶端收到服務器的SYN+ACK
報文段。而後將Acknowledgment Number
設置爲y+1,向服務器發送ACK
報文段,這個報文段發送完畢之後,客戶端和服務器端都進入ESTABLISHED
狀態,完成TCP三次握手。併發
那麼,爲何TCP協議須要三次握手?在謝希仁的《計算機網絡》中是這樣說的:
tcp
已失效的鏈接請求報文段會產生在這樣一種狀況下:client發出的第一個鏈接請求報文段並沒有丟失,而是在某個網絡結點長時間的滯留了,以至延誤到鏈接釋放之後的某個時間纔到達server。原本這是一個早已失效的報文段。但server收到此失效的鏈接請求報文段後,就誤認爲是client再次發出的一個新的鏈接請求。因而就向client發出確認報文段,贊成創建鏈接。假設不採用「三次握手」,那麼只要server發出確認,新的鏈接就創建了。因爲如今client並無發出創建鏈接的請求,所以不會理睬server的確認,也不會向server發送數據。但server卻覺得新的運輸鏈接已經創建,並一直等待client發來數據。這樣,server的不少資源就白白浪費掉了。採用「三次握手」的辦法能夠防止上述現象發生。例如剛纔那種狀況,client不會向server的確認發出確認。server因爲收不到確認,就知道client並無要求創建鏈接。
函數
4、Java中TCP通訊的相關實現
在上節咱們分析了TCP協議創建鏈接,數據傳輸以及關閉鏈接的具體實現方式,而在實際的開發中,程序員只需瞭解TCP協議是一種可靠的傳輸協議便可實現數據在客戶端與服務器之間穩定的傳輸。在Java中,提供了Socket與SocketServer類來實現TCP服務器與客戶端的相關功能,一次正常的TCP通訊其大體流程能夠分爲四步(BIO模式):
服務器端(ServerSocket)綁定監聽端口,等待客戶端的TCP的鏈接(ServerSocket.accept())
客戶端(Socket)經過IP地址與端口鏈接服務器監聽端口(Socket.Connect()),鏈接成功後服務器端返回表示該TCP鏈接的Socket對象。
客戶端與服務器經過打開Socket對象的InputStream和OutputStream數據流,實現數據的傳輸工做(Java將I/O相關的操做都提供了流操做接口,網絡接口操做方式也同樣)。
客戶端與服務器完成數據傳輸,關閉數據流,關閉TCP鏈接(Socket.close())。
下圖是Socket的通行模型圖:
這裏有個問題,TCP的三次握手是在Socket的哪一步中實現?
在ServerSocket.accept()與Socket.Connect()的過程當中實現的,在客戶端經過Connect()接口鏈接服務器時,操做系統底層的TCP/IP協議棧便開始了發送SYN包,回覆SYN+1等TCP的三次握手過程,只有三次握手成功,ServerSocket.accept()纔會返回一個合法的Scoket對象,客戶端Socket.Connect()函數會正常返回,若是三次握手失敗,客戶端Socket.Connect()會拋出IOException異常。
這裏須要注意,具體的三次握手協議細節的實現,也不是在Java中實現的,Java只是運行在JVM虛擬機上的語言,具體的實現是由宿主主機的TCP/IP協議棧實現的,Java只是經過虛擬機調用了這些宿主主機提供的方法而已。
5、TCP半關閉現象(Half-Close
)
有TCP服務器與客戶端應用程序開發經驗的同窗應該遇到過一個問題,那就是服務器端忽然崩潰(kill 掉服務器進程),查看系統中的網絡鏈接時,發現TCP客戶端的狀態仍是處於鏈接狀態(ESTABLISHED
),而該TCP鏈接實際已經失效了,這就是TCP的Half-Close 現象。若是應用程序判斷與服務器鏈接狀態的方法依賴於TCP的鏈接狀態,客戶端將會一直認爲與服務器的TCP鏈接是正常的,只有在客戶端向服務器發送數據時,才能發現TCP鏈接對應的套接字失效了。之因此產生Half-Close現象就是因爲客戶端與服務器之間沒有經過四次揮手的方式關閉TCP鏈接,在服務器的忽然下線會形成客戶端沒法當即感知的問題。有同窗會問,不是有超時時間嗎,一旦超時,客戶端不就能夠感知到服務器已經下線了嗎?不錯,系統的協議棧實現具備超時時間的機制,可是在windows系統下,這個超時時間默認是2小時(做者未考證linux下的keepAlive時間)。
那麼如何避免Half-Close現象?
1.首先進行再使用完tcp鏈接後,必定要將套接字close掉。
2.添加心跳包機制,服務器與客戶端之間的鏈接保持機制不該該依賴套接字的狀態,而應該在TCP協議之上設計心跳包機制,例如每5分鐘,客戶端與服務器之間經過發送心跳包來感知對方的存在。
3.TCP Server應該實現JVM的關閉鉤子(Runtime.addShutdownHook()),主動關閉全部TCP鏈接,清理佔用資源,JVM關閉鉤子的使用方式以下所:
總結
TCP協議做爲可靠傳輸協議,是全部協議中最經常使用的協議,什麼?最經常使用的協議不是HTTP嗎?HTTP協議只是TCP協議的應用協議而已。TCP協議相關的開發難點在於服務器端的開發,須要考慮併發性能,本文以講解了TCP的協議爲主,所以只採用了BIO模式進行分析,在以後的文章中將會分析高併發的TCP服務器的實現原理。