1. 程式人生 > >socket系列之socket服務端與客戶端如何通訊

socket系列之socket服務端與客戶端如何通訊

上面已經分別介紹了ServerSocket跟Socket的工作步驟,並且從應用層往系統底層剖析其運作原理,我們清楚了他們各自的一塊,現在我們將把他們結合起來,看看他們是如何通訊的,並詳細討論一下他們之間相互通訊的一些細節。
藉助圖2-3-2-4,想象一下你正在大學課室上著電腦,你跟你另外兩個朋友覺得老師講得課很菜,沒必要聽,於是你們仨都各自開啟瀏覽器衝浪,剛好你們訪問了同一臺伺服器,假如你用的是瀏覽器A,那麼整個流程為:
①瀏覽器確認目標IP跟目標埠號(http預設使用80埠),當然如果你在瀏覽器位址列輸入帶埠的地址,那麼目標埠號將是你指定的埠,另外瀏覽器還將組織http報文,一併將這些引數傳入系統底層socket。
②系統層socket對TCP/IP協議的操作,從前面我們已經學到,一層一層往下其實就是將報文一層一層包起來,所以這裡是先用http報文組織成TCP報文,再組織成IP報文,其中socket的源埠是隨機分配的(一個大於1023隨機數),最後放到TCP/IP棧裡,排隊傳送訊息。
③ServerSocket正在堵塞監聽接收連線,客戶端socket與之建立連線。
④伺服器端根據客戶端請求建立socket,此socket即可用於跟客戶端socket進行通訊。
⑤伺服器處理相關邏輯,例如資料庫操作,邏輯判斷等等。
⑥伺服器處理完,行成相應的用於客戶端瀏覽器顯示的http報文,http報文經過tcp跟ip協議包裝,返回給客戶端socket。
⑦客戶端經過層層解析,把http報文傳到瀏覽器,瀏覽器對http報文進行解析,最後一個漂亮整潔的網頁展示在你眼前。
⑧釋放關閉連線。
如果你們仨在同一時間訪問,伺服器為了能有更好地效能,為了幾乎同一時間響應你們,而不是一個處理完才處理另外一個,伺服器會採取多執行緒處理,一個執行緒處理一個請求。
 

圖2-3-2-4 ServerSocket與Socket通訊模型


上面的通訊模型從較巨集觀的層面描述了socket之間的通訊,而在通訊過程中必然涉及資料報文的傳輸,接著將學習socket底層資料的傳輸模型。圖2-3-2-5形象地展現了資料是怎樣從應用層到系統底層,再到網際網路傳輸的。首先先介紹兩個FIFO佇列:SendQ跟RecvQ。它們都處於系統底層,在傳輸過程中充當緩衝區的作用,SendQ是把應用層傳送到系統底層的資料進行緩衝,然後排隊傳送到因特網的佇列,而RecvQ則是把從因特網接收到的資料進行緩衝,等待應用層去讀取的佇列。具體步驟如下:
①應用層的Socket或Serversocket把要傳送的資料寫入系統底層的SendQ佇列。
②SendQ佇列根據先進先出原則,把這些資料發往因特網,到達目標主機。(從傳輸層來看,可以認為這時的資料是TCP報文)
③如果有資料從因特網傳送過來,首先會被接收到RecvQ佇列進行緩衝。
④應用層的Socket或Serversocket會迴圈嘗試讀取RecvQ佇列,如果有資料則讀取到應用層。
從中可以總結,TCP協議其實就是負責將資料按順序從SendQ與RecvQ之間互相轉移。而且從我們應用層來看這個轉移過程我們是沒有辦法控制或直接觀察的。同時,由於TCP提供可靠的資料傳輸服務,所以任何寫入SendQ佇列的資料都必須要保留一份資料副本,直到連線的另外一端成功接收。另外,Socket通過輸出流向SendQ寫資料並不意味著資料已經被髮送,他們僅僅只是到了緩衝區,就算Socket的輸出流進行了flush()操作也不能保證資料馬上被髮送到通道。
 

圖2-3-2-5 socket資料傳輸模型


那麼我們能不能就認為在連線的一端寫入資料,另外一端就能馬上讀取資料呢?接著我們將更深入探討SendQ與RecvQ佇列裡面的資料是怎樣傳輸的,這個過程應該說是以塊來傳輸的,而這些塊的大小在一定程度上獨立於應用層socket讀寫的緩衝大小。
如圖2-3-2-6,假如應用層socket呼叫輸出流write()方法向SendQ佇列寫資料,三次寫入的資料大小分別為1000位元組、2000位元組、5000位元組,而假設SendQ佇列的快取塊大小為3000位元組,第一次1000位元組加上2000位元組剛好等於3000位元組,那麼這兩次的資料作為一個塊進行傳輸。通過因特網到達RecvQ佇列,應用層socket再通過輸入流的read()方法讀取RecvQ裡的資料,輸入流緩衝區讀取了RecvQ佇列3000位元組就返回值3000。
 

圖2-3-2-6 socket底層的塊傳輸


在現實使用中,SendQ跟RecvQ佇列可能會被無限填充, 為了防止一個TCP連線將系統記憶體全部耗盡,必須要對SendQ跟RecvQ佇列的大小進行限制。一旦RecvQ佇列數達到最大值,TCP流控制就會通知SendQ先停止傳送資料,等我的RecvQ中的資料被socket輸入流讀走了有空間了再傳給我。這樣就能有效地控制過量傳送,有效杜絕超出系統接收處理能力的情況。對於socket輸出流可以不斷寫入資料,直到SendQ佇列被填滿了,此時write()方法將會阻塞等待。

==========廣告時間==========

鄙人的新書《Tomcat核心設計剖析》已經在京東預售了,有需要的朋友可以到 https://item.jd.com/12185360.html 進行預定。感謝各位朋友。

=========================