當前與網絡相關的業務主要是基於tcp/ip或http,熟悉j2ee的同窗必定會對http場景下的開發比較瞭解。可是,精通tcp/ip以及如何構建一個直接基於tcp/ip層通信的知識卻不太多見。恰巧,最近一年來我參與了一些基於tcp/ip應用的開發工做。總算有所收穫,今天在博客中作些分享,但願對有興趣的同窗有所幫助。spring
比較常見的4層網絡模型(圖)以下:編程
基於應用層的開發難度是相對比較低的,由於絕大部分與鏈接和數據傳輸、校驗相關的事情已經交給(系統)來完成,使得開發人員只須要專一於業務便可。這種分層的技術結構是很是高級和有效的。基於應用層的開發雖然方便,可是當咱們須要在功能上實現某些特殊需求的時候,就不免有些掣肘。例如,咱們須要從一些傳感器上採集數據或但願他們可以主動將數據上送,並在通過了中心繫統處理後推送到其它響應裝置。這樣的需求使用http來開發,反而增大了難度。服務器
操做系統實際已經爲咱們提供了一種基於傳輸層的通信方式:套接字(socket)。使用套接字可讓咱們自由定義通信協議並選擇合適的鏈接方式。網絡
利用socket實現網絡通訊分爲服務端和客戶端,服務端綁定端口並主動監聽鏈接,客戶端須要向服務端發起鏈接。創建一次tcp鏈接須要進過「三次」握手:socket
第一次握手:客戶端發送syn包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;tcp
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時本身也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;操作系統
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。blog
三次握手被抽象成socket鏈接,這個過程服務端和客戶端會分別生成一個socket並經過在這個套接字上的鏈接收發數據。那麼問題產生了,假如咱們知道服務端對8081端口進行監聽,客戶端會隨機打開一個高位端口進行鏈接。鏈接創建後,服務端是在哪一個端口上監聽數據的呢?答案是8081端口,服務端會根據端口上數據的源地址和端口判斷從而將數據分發到正確的應用上去。ip
理解這一點其實很重要,若是此時通訊的雙方沒有任何數據交換,socket也沒法判斷鏈接是否被斷開。任意一方必須首先通知socket斷開鏈接,整個通訊過程纔算結束。若是中間網絡中斷,鏈接會一直處於等待狀態。開發
利用socket編程的另外一個難點是,因爲通訊的雙方徹底對等任何一方均可以主動發送數據,如何實如今http應用中常見的請求/應答會比較麻煩。爲此我專門查閱了http1.0和http1.1的相關資料,基本的解決方案總結以下:
服務器的運算能力一般都比客戶端強,第二種解決方案能更加有效的利用網絡。可是,若是有一條請求須要請求佔用服務端大量的運算時間,後續應答都會被堵塞,所以在某些狀況下也會引起比較嚴重的問題。
爲了解決這個問題,我借鑑了spring kafka在實現消息交互的時候提供的一種解決思路:爲每一條請求指定一個ID,通過服務端處理後的應答都須要帶上這個ID。這樣在回覆給客戶端的時候,客戶端就能夠根據這條ID值來調用不一樣的回調處理業務。
與tcp/ip開發的總結,大體如此。後面,我還會分享一些基於技術的實際項目,若是你對這些問題有興趣,也歡迎給我留言討論。