--- creator: Zach tags: 計算機網路, socket, TCP created: 2021-09-27 --- # 網路是怎樣連接的(六)TCP的交互(上) ## 思考重點 - 調用socket後協議棧內部發生什麼事? - 協議棧是如何看待套接字的? - TCP如何發起連線? ## 核心知識 ![](https://i.imgur.com/QsxskZW.png) <center>核心知識點</center> ## 雙方溝通的控制消息 ![](https://i.imgur.com/MYrms2G.png) <center>協議棧在網路分層中的架構</center> </BR> 繼上一章介紹了應用程式調用socket的連線流程後,我們將透過協議棧內部的TCP/UDP消息處理來探討作業系統是如何使用socket消息進行連線以及通訊雙方如何使用TCP頭部控制消息來互相確認連線狀態 我們都知道應用程式會使用socket標識符代表一個本地端與服務端的溝通通道,當使用socket標示符進行操作時,就相當於將本地端與目標服務器的IP與端口號等控制消息交給下層,待操作系統要處理消息時,只要看一下該socket標識符對應的結構體中儲存的消息就知道對方的連線資訊 > 有使用社交聊天軟體嗎?把socket當成一個聊天視窗,視窗中有雙方的溝通訊息,例如姓名、位置、狀態等... 其實socket就相當於聊天雙方需要的連線消息,也可以說本地端socket就代表一個**我跟某某某的聊天室窗**,當然有些應用程式可以同時與多個應用程式**"聊天"** ![](https://i.imgur.com/sPcHbZQ.png) <center>可以把socket想像成代表某一聊天的標示,例如視窗1代表我和小美的對話</center> ## netstat指令 我們可以利用windows cmd下的netstat指令來查看本機端的socket內容,畢竟沒有實際操作案例其實還蠻難搞懂這種抽象觀念,可以透過在cmd中下達*netstat -ano*得到類似下圖的結果 ![](https://i.imgur.com/68M61qx.png) <center>用netstat來查看本地端socket狀態</center> <BR/> 我們看看PID編號為1580的程式的溝通狀況,它使用TCP通訊協定,本機IP與端口號為192.168.0.14與8430,且已經和遠端服務器完成連線*ESTABLISHED*,對方服務器IP與端口號分別為52.159.49.199與443 接著來看看PID編號為13348的程式,它使用UDP通訊協定,本機端顯示0.0.0.0表示不綁定IP地址,遠端服務器地址顯示*\:\*,也是不綁定IP地址的狀態,連線狀態更是省略掉了,這裡可以清楚看到TCP與UDP的差異 ## 委託TCP進行處理 當應用程式調用socket()創建套接字時,作業系統會為套接字分配一個結構體空間好存放初始化連線消息,等到connect操作時將會把儲存溝通消息的結構體做為參數供作業系統連線使用 ![](https://i.imgur.com/y344G9e.png) <center>調用connect()後TCP獲得連線雙方的位置消息</center> ### TCP頭部 客戶端與服務器進行連線實際上是雙方在交換連線控制消息。connect()操作的目的在於使用本地端socket告訴TCP連線資訊,而溝通雙方在進行連線操作時是透過更改與查看TCP頭部消息來確認每個步驟是否成功,因此接下來我們將大致介紹TCP頭部格式 >TCP頭部主要儲存兩種消息,(1)客戶端與服務器通訊流程需要的控制消息 (2)套接字給的連線消息 ![](https://i.imgur.com/1equPsL.png) <center>TCP頭部</center> </BR> #### 本地端口號 長度為16個bits,代表本地端應用程式端口號 #### 目標端口號 長度為16個bits,代表對方服務器應用程式端口號 #### 發送數據的封包序號 長度為32個bits,代表發送數據的封包序號。封包序號的意思是發送方告知接收方該封包的起始位元是占整個封包的第幾個bit,要注意起始封包序號不次從1開始,而是在建立連接時**亂數**產生的。下一次的封包序號就是該次封包序號加上封包的長度(幾個bits) #### 接收數據的封包序號 長度為32個bits,又稱為ACK號,它代表接收方告知發送方下一次發送封包要從第幾個bits開始 #### 資料偏移 長度為4個bits,它代表TCP內嵌的資料從第幾個bytes開始,也可以表示TCP頭部消息的總長,使用的單位為4bytes,例如資料偏移位填入6則代表TCP頭部總長為6 x 4 = 24 bytes #### 保留 長度為4個bits,用來當作擴充用,實際應用時必須先設為0 #### 控制位 長度為8個bits,這8個bits分別代表不同的控制消息 比特位|涵意 :--:|:--: CWR|CWR是使用於IP頭部ECN的標誌位,用於壅塞視窗控制 ECE|ECE也是使用於IP頭部ECN的標誌位,用來通知網路是否壅塞 URG|緊急指標,若URG為1則表示該包資料需要緊急處理 ACK|向對方表示接收到消息,除了客戶端一開始的連接請求外,都設定成1 PSH|是否馬上傳給上層應用,1表示立刻馬上,0表示可以先傳遞給緩衝區,不用那麼急 RST|設為1時代表TCP連線異常,需要強制切斷連線 SYN|設定為1時用來建立連線,由客戶端發起連線操作 FIN|通訊結束後若沒有數據要傳輸,將FIN設為1斷開連線 #### 視窗大小 長度為16個bits,接收方用於告知發送方,無需等待確認可以一次發送的數據大小(後面關於視窗控制時會介紹) #### 檢查碼 長度為16個bits,用來檢查封包在傳遞過程中是否發生錯誤 #### 緊急指標 長度為16個bits,若控制位中的URG設置成1,則表示TCP資料從開頭到緊急指標所指向的位置都是需要立刻處理的部分 #### 選項 長度可變,通常用來提升TCP通訊性能,是額外的可選字段 #### 填充 用來補足選項字段的byte數 ### TCP消息封包 ![](https://i.imgur.com/zpe25PJ.png) <center>消息封包的傳遞</center> </BR> 上圖顯示兩種不同的封包格式,這裡主要探討調用socket connect時的封包格式。我們可以把連接階段的TCP頭部看成是一個尋找服務器應用程式的*地圖*。 應用程式透過connect參數將socket儲存的消息傳遞給傳輸層,TCP模塊會對這些消息進行處理並寫進頭部消息中,在連接初期TCP不會寫入資料,所以整個資料封包僅只有頭部消息 TCP模塊完成對消息的初步封裝後便會通知IP模塊對該封包進行處理,然後再委託更下層進行封包的封裝以及傳送 ### 三次握手連線流程 ![](https://i.imgur.com/EeQ2Jeq.png) <center>三次握手</center> </BR> #### 一次握手 TCP是面向連接的通訊方式,在進行連接請求前會先準備齊連線所需要的資料。**通常連線請求由客戶端發出**,客戶端必須確認服務端的IP地址與端口號,對於TCP頭部消息,首先會隨機產生一個亂數的發送數據序號,然後把SYN控制位設為1。緊接者客戶端發出連接請求,並把狀態改為*SYN-SEND* #### 二次握手 當接收到客戶端的連接請求後,服務器會解析TCP頭部並找到等待中的服務器應用程式,並將雙方的溝通消息寫入服務器上的socket,與此同時服務器產生一個隨機的發送數據序列號,並將客戶端的發送數據封包序號加上1並且田進自己的接收數據封包序號中,最後將SYN與ACK都設置成1遂返回響應。服務端這時將狀態改成*SYN-RCVD*且該封包一樣不含資料區段 #### 三次握手 客戶端需要再發送一個封包來完成連線的最後步驟。客戶端會將服務器響應的發送數據封包序號加1並填入自己的接收數據封包序號中,同時將ACK設置成1然後發送出去。一旦服務器接收到三次握手的消息,雙方將會處於*ESTABLISHED*狀態,也就是我們在netstat視窗中看到的連線狀態