網絡基礎 -- 傳輸層協議(UDP與TCP/三次握手與四次揮手/可靠傳輸)

 

目錄

傳輸層

端口號

UDP協議(User Datagram Protocol, 用戶數據報協議)

UDP報文格式

UDP的特點

協議實現(原理) / 特性對於上層應用層代碼編寫的影響 (我們用UDP協議時該注意什麼?)

TCP協議(Transmission Control Protocol, 傳輸控制協議)

TCP報文格式

TCP三次握手與四次揮手

close()shutdown()的區別及使用場景

三次揮手和四次揮手中的一些問題

TCP特點

面向字節流

可靠傳輸


傳輸層

傳輸層負責應用程序之間的數據傳輸, 也就是負責數據能夠從發送端傳輸到接收端 .

一個發送的數據在網絡中, 用IP地址來確定這條數據來自哪臺主機(源IP地址), 要發往哪臺主機(目的IP地址). 但當一臺主機接收到一條網絡數據時, 主機裏有不止一個應用程序在運行着, 誰來接收這條網絡數據呢 ? 所以還需要一個信息來標識, 這條數據是哪個應用程序的, 這個標識就是端口號. 

源IP地址和目的IP地址是在傳輸層下層的網絡層IP協議中封裝在頭部的. 而端口號封裝在傳輸層協議的頭部.

這是爲什麼呢? 因爲傳輸層的作用就是負責應用程序之間的數據傳輸, 在網絡上中數據時如何傳輸是下層網絡層該操心的事, 當數據到達對端主機時, 哪個應用程序接收纔是傳輸層關心的事, 所以IP地址封裝在網絡層, 端口號封裝在傳輸層.


端口號

在傳輸層的上層應用層中, 用一些常用協議搭建的服務器都有一些固定端口號, 如下:

  • http 服務器 : 80端口
  • https 服務器 : 443端口
  • ssh 服務器 : 22端口
  • ftp 服務器 : 21端口
  • telnet 服務器 : 23端口

在Linux中, 在 /etc/services 中保存着這些知名端口,  我們自己寫的程序要避開這些知名端口

端口號的劃分範圍

  • 0 ~ 1023 : 知名端口號, 上面所說的廣泛使用的應用層協議, 它們的端口號是固定的.
  • 1024 ~ 65535 : 操作系統動態分配的端口號, 客戶端程序的一般不主動綁定固定端口號的原因就是就是由操作系統動態分配, 避免端口號衝突.

問題

1. 一個進程是否可以bind 多個端口號? (bind指bind()接口, 用於給socket套接字綁定自己的地址信息(包括源IP地址和源端口號))

 答 : 一個進程可以bind多個端口

2.一個端口號是否可以被多個進程 bind?

答  : 一個端口號只能被一個進程bind

注 : 在Linux中, 這裏的一個進程可以理解爲一個PCB, 因爲Linux中的線程是輕量級進程, 所以線程也有相同特性, 所也就能說是一個PCB可以bind多個端口號, 一個端口號只能被一個PCB所bind.

相關命令

netstat : 用於查看Linux中網絡狀態

  • n 拒絕顯示別名,能顯示數字的全部轉化成數字
  • l 僅列出有在 Listen (監聽) 的服務狀態
  • p 顯示建立相關鏈接的程序名
  • t (tcp)僅顯示tcp相關選項
  • u (udp)僅顯示udp相關選項
  • a (all)顯示所有選項,默認不顯示LISTEN相關

pidof : 用於查找指定名稱的進程的進程id

語法 : pidof 進程名


UDP協議(User Datagram Protocol, 用戶數據報協議)

UDP報文格式

圖片來源於網絡
  • 16位源端口號 :  標識本機的哪個應用程序發送的數據
  • 16位目的端口號 : 標識要給對端主機哪個應用程序發送數據
  • 16位UDP長度 : 標識整個UDP報文(首部 + 數據)的最大長度
  • 16位校驗和 : 在TCP協議, 網絡層的IP協議等協議的報文頭部中都要, 作用是檢驗所傳輸的報文數據是否出錯, 出錯則丟棄.
    檢測原理 : 發送數據時, 將校驗和設置爲0, 然後從所要發送數據的第一個字節開始, 每個字節進行取反相加, 溢出時, 溢出部分再從低位進行相加, 最終得到的數字填充到檢驗和中. 在接收數據時, 直接拿出校驗和計算, 用和發送時同樣的. 如果最後結果爲0, 則表示發送的數據與接收的數據一致, 否則數據不一致, 直接丟棄.

UDP的特點

  • 無鏈接 : 知道對端的IP和端口號就能直接進行傳輸, 無需建立連接
  • 不可靠傳輸 : 沒有確認應答機制, 沒有重傳機制, 沒有進行包序管理, 流量控制等機制, 相對於TCP來說非常不可靠
  • 面向數據報 : 收發數據報文時整條一次收發完成, 不夠靈活

協議實現(原理) / 特性對於上層應用層代碼編寫的影響 (我們用UDP協議時該注意什麼?)

1. 由於UDP報頭中只用了16位2字節來標識整個UDP報文的大小, 所以, UDP報文的大小是有限制的, 16位最大能表示的數是65535, 即UDP報文最大是65535個字節, 即64K, 但報文中頭部還佔了8字節, 所以, UDP所能發送的數據最大是64k - 8. 但由於傳輸層協議在下層網絡層中還要進行封裝, 而網絡層的IP協議中, 整個IP報文最大也只能是64K, 而IP協議報文頭部最小也要20字節, 這樣一來, UDP報文中數據最大也只能是64k - 20 - 8, 如下圖:

                                                    

注意 : 值得注意的是, 這裏限制UDP報文大小的根本原因不是UDP長度標識是16位的數據, 如果是這樣, 那要想數據再長些給更多位不就好了. 其實UDP報文不能太大的根本原因是UDP協議是面向數據報傳輸, 也就是在調用sendto()接口發送數據時, socket會將數據直接交給內核封裝UDP報頭然後一次性發送出去 (注意 : UDP是沒有真正意義上的發送緩衝區的), 如果這個數據很大, 那麼發送失敗的機率就很大, 所以, UDP報文數據不能太大的原因就是因爲其面向數據報傳輸, 如果數據很大, 發送失敗的機率就很大

剛說到, UDP沒有真正意義上的緩衝區, 什麼意思呢? 因爲UDP是不可靠傳輸, 其發送失敗也不會保存應用程序的數據拷貝(比如說, 應用程序產生了一個臨時的數據需要發送, 之後就會銷燬這個數據, 此時UDP發送失敗了, 那麼這條數據就沒有了, 因爲這個數據是在應用程序中被銷燬, 而UDP也沒有將其先放在發送緩衝區中).

2. 如果發送的數據大於64K-20-8, 則不能一次性發送, 需要用戶在上層應用層手動進行分包操作(將一個大數據截斷爲多個小數據,分別發送). 由於UDP協議是不可靠傳輸, 發送的數據並不能有序到達, 還可能會丟失, 所以需要我們程序員手動在應用層進行包序管理.

3. 在使用UDP協議時, 由於收發都是整個報文一次性收發, 所以用戶的接收緩衝區要定義的足夠大, 否則recvfrom()接收緩衝區滿了之後之後的數據就會接被丟棄.


TCP協議(Transmission Control Protocol, 傳輸控制協議)

TCP報文格式

圖片來源於網絡
  • 16位源端口號 :  標識本機的哪個應用程序發送的數據
  • 16位目的端口號 : 標識要給對端主機哪個應用程序發送數據
  • 32位序號(seq) : 用來標記數據段的順序,TCP把連接中發送的所有數據字節都編上一個序號,第一個字節的編號由本地隨機產生. 給字節編上序號後,就給每一個報文段指派一個序號. 序列號seq就是這個報文段中的第一個字節的數據編號.
  • 32位確認序號(ack) : 期待收到對方下一個報文段的第一個數據字節的序號. 序列號表示報文段攜帶數據的第一個字節的編號. 而確認號指的是期望接收到下一個字節的編號;因此當前報文段最後一個字節的編號+1即爲確認號. 確認號的字段只在ACK標誌爲1時纔有效.
  • 4位數據偏移(或稱爲首部長度) :  用於表示TCP報文首部的長度. 單位是字節, 一個TCP報文前20個字節是必有的, 後40個字節根據情況可能有可能沒有. 如果TCP報文首部是20個字節, 則首部長度爲 20/4 = 5 .
  • 6位保留位 : 目前必須是0, 因爲設計之初還沒想好用來幹嘛, 爲將來定義新用途保留的.
  • 6位標誌位 : 6個標誌位各佔一位
        • URG -- 緊急指針標誌位 : URG=1時, 緊急指針字段有效.URG=0時, 無效
        • ACK -- 確認回覆標誌位 :  ACK=1時, 確認號字段纔有效. ACK=0時, 無效
        • PSH -- 提示立即接收位 :  PSH=1時, 表示接收方應立即將數據交給應用層處理
        • RST -- 重置連接位 : RST=1時, 請求重建一個當前連接, 用來複位產生錯誤/混亂的連接, 也用來拒絕錯誤和非法的數據包
        • SYN -- 連接建立請求位 : 連接建立時用於同步序號. 當SYN=1, ACK=0時表示, 這是一個連接請求報文段(客戶端向服務端請求). 若(服務端)同意連接, 則在響應報文段中使得SYN=1, ACK=1. 因此, SYN=1表示這是一個連接請求, 或連接接受報文。SYN這個標誌位只有在TCP建產連接(三次握手)時纔會被置1, 握手完成後SYN標誌位被置0.
        • FIN  --  斷開連接請求位 : 用來釋放一個連接. FIN=1表示:此報文段的發送方的數據已經發送完畢, 並要求釋放連接
  • 16位窗口大小 : TCP流量控制由連接的每一端通過聲明的窗口大小來提供
  • 16位校驗和 : 和其他協議中的校驗和作用一樣, 用於收到的數據段(首部+數據)是否出錯.
  • 16位緊急指針 : URG = 1時, 該字段有效, 有效時指向數據中優先部分的最後一個字節, 通知接收方緊急數據的長度.
  • 最長40位選項 : 長度爲0 ~ 40字節, 必須以4字節爲單位變化, 必要時可以填充0. 通常包含: 最長報文大小(MaximumSegment Size,MSS)、窗口擴大選項、時間戳選項、選擇性確認(Selective ACKnowlegement,SACK)等.

TCP三次握手與四次揮手

三次握手建立連接

第一次握手 : 

     client  : 客戶端向服務端發送連接請求SYN包(發送連接請求)(SYN=1, 同時選擇一個初始序號seq=x)後, 客戶端進入SYN-SENT狀態, 等待服務器確認回覆.

     server :當服務端還沒有接收到客戶端的連接請求時, 服務端處於LISTEN狀態.

第二次握手 : 

     server : 當服務端收到客戶端的連接請求時(收到syn包), 爲新的連接請求創建新的通信socket, 此時服務端必須確認客戶端的SYN請求(回覆確認序號ack = x + 1), 確認序號有效ACK=1, 因爲連接是雙向的, 所以服務端也向客戶端發送連接請求SYN包(SYN = 1, 爲自己選擇一個初始序號seq = y), 即服務端向客戶端發送 ACK+SYN 包, 服務端進入SYN_RCVD狀態. 當第二次握手完成, 還沒進行第三次握手時, 此時TCP連接的狀態稱之爲半連接狀態.

第三次握手 : 

      client : 當客戶端收到服務端回覆的SYN+ACK包時, 確認建立連接(客戶端這邊已經沒什麼問題了, 可以通信了), 並回復給服務端確認信息ACK包(seq = x + 1, ack = y+1), 客戶端的進入ESTABLISHED狀態, 完成連接

      server : 當服務端收到客戶端發送的ACK包後, 確認客戶端連接就緒, 可以開始通行, 進入ESTABLISHED狀態 


 四次揮手斷開連接

圖片來源於網絡

第一次揮手 :

      client : 當客戶端確定不再需要發送數據時, 調用 close(sockfd) / shutdown(sockfd, SHUT_WR) (兩者的區別以及用法下面說). 客戶端會向服務端發送FIN包(FIN=1, seq = u)(u就是客戶端之前收到的數據的最後一個字節的序號+1), 客戶端進入FIN_WAIT1狀態.  (注意 : TCP協議規定, FIN報文段就算沒有數據, 也需要消耗一個序號)

      server : 當服務端未收到客戶端發送的FIN包時, 一直處於ESTABLISHED狀態

第二次揮手 :

      server : 當服務端收到客戶端發來的FIN包後, 知道客戶端不會再發送數據了, 也就不需要接受, 先調用close(sockfd) / shutdown(sockfd, SHUT_RD), 再確認回覆客戶端, 即(ACK=1, ack = u+1),  並且帶上自己的序列號seq=v, 此時服務端的進入了CLOSE_WAIT狀態. TCP服務端就通知高層的應用進程, 客戶端不會再向服務端發送數據了, 此時TCP通信的連接狀態就稱爲半關閉狀態,即客戶端已經沒有數據要發送了, 但是服務器若發送數據, 客戶端依然要接受. 這個狀態還要持續一段時間, 也就是整個CLOSE_WAIT狀態持續的時間.

     client : 當客戶端收到服務器的確認請求(ACK包)後, 此時, 客戶端就進入FIN_WAIT2狀態, 等待服務器發送FIN (在這之前還需要接收服務器發送的最後的數據)

第三次揮手 : 

     server : 服務端將最後的數據發送完畢後, 再不需要發送數據了, 就調用shutdown(sockfd, SHUT_WR) , 再向客戶端發送FIN包, 由於在半關閉狀態, 服務器很可能又向客戶端發送了一些數據, 假定此時的序列號爲seq=w,即FIN包(ACK=1, seq=w, ack=u+1). 此時, 服務端就進入了LAST_ACK(最後確認)狀態, 等待客戶端的確認.

第四次揮手 : 

    client : 當客戶端收到服務端的連接釋放請求(FIN包)時, 必鬚髮出確認, ACK=1,ack=w+1, 而自己的序列號是seq=u+1, 此時,客戶端就進入了TIME_WAIT狀態. 注意 : 此時TCP連接還沒有釋放, 必須經過2倍的MSL(最長報文段壽命)的時間後, 當客戶端釋放連接後, 才進入CLOSED狀態.

    server : 服務端只要收到了客戶端發出的確認(ACK包), 立即進入CLOSED狀態. 同樣, 釋放TCB連接後, 就結束了這次的TCP連接. 可以看到, 服務端結束TCP連接的時間要比客戶端早一些.

注意 : 需要注意的是, 四次揮手可以是由客戶端首先發送FIN包觸發, 也可以由服務端首先發送FIN包觸發.


close()shutdown()的區別及使用場景

由於TCP是全雙工雙的, 所以連接的斷開也需要單獨將兩個通道拆除, 四次揮手所做的事情就是拆除兩條通道釋放資源.

那麼已經完成三次握手的TCP連接, 什麼時候會觸發四次握手呢 ? 從邏輯上, 是通信結束, 不需要這個TCP連接時, 從具體操作上, 就涉及到兩個系統調用接口close()和shutdown()

對於close(), 當調用close(sockdfd)只會造成套接字文件描述符的引用計數減一, 當引用計數爲0時, 調用方主動發送FIN包, 觸發四次揮手. 例如在多進程服務器中, 父子進程共享socket文件描述符, 當父進程或者某個子進程調用close(sockfd) 時, socket文件描述的引用計數減一, 直到父進程和所有的子進程都調用close(sockfd)後(引用計數爲0), 纔會發生四次揮手, 釋放資源.

對於shutdown(sockfd, how), 是拆分四次揮手過程, 在設置how參數爲SHUT_WR或者SHUT_RDWR時, 會立即發送FIN, 觸發四次揮手. 無論socket文件描述符引用計數是多少,  只要任一進程調用該接口都會破壞所有進程的連接, 任一進程在該描述符上讀取數據都會收到EOF結束符, recv()返回0, 寫數據時會收到SIGPIPE信號. 因爲其並不釋放資源, 所以最後還需要調用close().

對於FIN包, shutdown()和close()都能發送FIN包, 對於發送FIN包, 是想要告訴對方, 我不再向你發送數據了

close()和shutdown()的使用場景 : 多進程服務器, 就不能用shutdown()只能用close(), 客戶端可以用shutdown(), 也可以用close(). 多進程服務器端, 每監聽到一個連接就會創建一個子進程, shutdown會關閉服務器監聽工作, 也會關閉其他正在與不同客戶端通信的進程. 客戶端可以使用shutdown(), 因爲客戶端和服務器端一般只有一條連接,可以使用shutdown(). 當客戶端與服務器端有多條連接且是在同一個進程中, 最好用close(), 以免影響到其他連接通信.

int shutdown(int sockfd, int how) 

頭文件 :  sys/socket.h

功能 : 關閉socket的全部或部分全雙工連接, 只關閉其讀寫功能, 並不釋放資源

參數 : sockfd : socket的操作句柄, 即文件描述符
          how : 有三種選擇 : SHUT_RD : 值爲0, 關閉讀端
                                         SHUT_WR : 值爲1, 關閉寫端
                                         SHUT_RDWR : 值爲2, 關閉讀端和寫端

由於TCP是全雙工通信, 所以會存在半連接狀態. 即當主動端關閉寫端代表不再發送數據, 被動端關閉讀端代表不再接收數據(但還是能對收到的數據進行確認回覆).

返回值 : 正確執行返回0, 錯誤返回-1, 並設置errno


int close(int fd)

頭文件 : unistd.h

參數 : fd : 文件描述符

返回值 : 正確執行返回0, 錯誤返回-1, 並設置errno


三次揮手和四次揮手中的一些問題

1. 爲什麼不是兩次/四次握手而是三次握手?

    答 : 兩次不安全, 四次沒必要;  三次握手完成兩個重要功能, 一是確保通信雙方都準備好了(雙方各自都知道對方準備好了), 二是雙方就初始序列號進行協商(這個序列號在握手過程中被髮送和確認).

兩次不安全 : 假設一個客戶端像一個服務端發送連接請求報文段(SYN包), 服務器端收到SYN包並回復確認應答報文段(ACK+SYN), 如果是兩次握手那麼此時連接已建立, 可以開始通信(發送數據報文段).  如果有以下可能, 就會造成不安全的情況. 如 :  服務端的ACK包在傳輸過程中丟失, 那麼客戶端就不知道服務端是否已經準備好, 不知道服務端建議用什麼樣的序號用於客戶端和服務端之間的傳輸, 也不知道服務器是否同意自己發送的初始序列號, 甚至懷疑服務端是否收到自己發送的的SYN包
                                                       
在這種情況下, 客戶端既不會向服務端發送數據報文段, 也不會接收服務端發送來的數據報文段(就算服務端發送了, 客戶端還以爲沒建立連接, 當然不會接收), 服務端發給客戶端的數據報文段沒有得到客戶端的確認應答, 服務端就開始只等待接收客戶端的確認應答報文段了, 而等到服務器懷疑自己發出的數據報文段沒有被客戶端收到時, 就會重複發送同樣的數據報文段(超時重傳機制, 下面說), 就形成了死鎖.     或者當客戶端像服務端發出SYN包之後就關閉了(斷網斷電...), 服務端會出現相同的問題. 

                       

四次沒必要 : 由於握手要確保通信雙方都準備好了(雙方各自都知道對方準備好了), 不僅僅客戶端要向服務端發送連接請求SYN包, 服務端確認應答回覆給客戶端ACK包. 還要服務端向客戶端發送連接請求SYN包, 客戶端在確認應答回覆給服務端ACK包, 這樣雙方都就放心了, 但中間服務端給客戶端先回復ACK包, 再次向客戶端發送請求SYN包, 這就有點憨了, 何不一起置1, 一次發送呢? 所以四次可以, 但沒必要.

2. 爲什麼不是三次揮手而是四次揮手?

疑問 : 建立連接時需要三次握手, 那斷開連接反着來不就好了, 不也只需要三次嗎?

答 : 前面說到, FIN包的發送並不表示發送方不發送數據也不接收數據了, 而是告訴對方我不再給你發送數據報文段了, 但不代表發送發不再接收數據報文段了, 所以當客戶端向服務端發送FIN包後, 服務端需要確認回覆ACK包, 但不能像三次握手一樣, ACK和FIN同時置1回覆, 因爲客戶端(雖然不能發送數據報文段了, 但)還可能要接收服務端發送的的數據報文段, 所以服務端要先給客戶端回覆ACK包, 再向客戶端發送FIN包, 在服務端發送ACK包和FIN包之間服務端還可能會向客戶端發送數據報文段. 所以在客戶端回覆ACK包之後, 確定自己不再向客戶端送數據報文段了時, 纔會向客戶端發送SYN包.

3. 爲什麼握手三次, 揮手四次?

答 : 也就是前兩個問題, 因爲當服務端端收到客戶端的SYN連接請求報文(SYN包)後, 可以直接發送SYN+ACK報文. 其中ACK報文是用來應答的, SYN報文是用來請求連接的. 但是關閉連接時, 當服務端收到FIN報文時, 很可能並不會立即關閉SOCKET, 所以只能先回復一個ACK報文, 告訴客戶端, "你發的FIN報文我收到了". 只有等到服務端所有的報文都發送完了, 才能向客戶端發送FIN報文, 因此不能一起發送. 所以需要四次揮手.

4. 三次握手失敗了, 服務端如何處理?

    1. 如果是客戶端發送的SYN包沒有到達服務端, 服務器根本不知道有這個請求, 因此服務器沒什麼要處理的

    2. 客戶端發送了SYN後服務端收到並進行了SYN+ACK報文的響應, 但沒有收到客戶端應答的ACK包. 服務端等待最後一個ACK超時後, 則會給客戶端發送RST重置連接報文, 要求對方重新發起連接請求, 釋放爲本次連接請求創建的socket, 而並非重傳SYN+ACK

5. 爲什麼FIN包最先發送方發送了ACK包後, 並沒有直接進入CLOSED狀態釋放資源, 而是進入TIME_WAIT狀態呢?
   (爲什麼TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能返回到CLOSED狀態?) (TIME_WAIT的作用?)

 答 : 講道理, FIN包先發送方在ACK包發送完了之後, 就可以直接進入CLOSED狀態, 釋放資源了, 但我們的網絡不是一定可靠的, 有可能最後一個ACK包會丟失, 假設沒有TIME_WAIT狀態, 客戶端在發送最後一個ACK包之後直接進如CLOSED狀態釋放資源, 客戶端關閉. 而這個ACK包丟失在網絡中, 服務端因爲沒有收到客戶端ACK包, 向客戶端超時重傳了FIN包, 在服務端向已經關閉的客戶端重傳FIN包之前將客戶端再重啓. 如果重啓後的客戶端地址信息與之前關閉的客戶端相同, 有兩種情況 :
  1. 剛啓動的客戶端就會收到一個服務端的FIN包. 就會對接下來客戶端與服務端的連接造成影響.  
  2. 重啓的客戶端向服務端請求新連接發送SYN包, 服務端正在四次揮手, 處於LAST_ACK狀態. 突然客戶端來個SYN包, 服務端就會認爲狀態錯誤, 向客戶端發送RST連接重置報文(RST包), 要求客戶端重新建立連接. (分手分到一半, 對方說發個消息說做我女(男)朋友好嗎, 懵不懵?, 卑微的我就心裏想 : "我就當你腦子抽風了". 然後告訴對方, 我們重新來過吧)

所以, 在FIN包先發送方在ACK包發送完了之後, 進入TIME_WAIT狀態進過兩個MSL時間後再進入CLOSED狀態, 就是爲在這段時間內處理服務端可能重傳的SYN包, 爲了本次連接的所有數據都被處理或消失於網絡之中, 不會對後續新連接造成影響.
MSL : 報文最大生存週期, 任何報文在網絡上存在的最長時間, 超過這個時間報文將被丟棄(一個報文在網絡中總不能一直傳一直傳, 可能造成佔用大量帶寬等問題, MSL就是限制報文在網絡中一直傳輸的一種方式)

總結 : TIME_WAIT的作用 : 1. 可靠地實現TCP全雙工連接的終止  2. 讓"老"的數據在網絡中被丟棄

5. 服務器上出現了大量的CLOSE_WAIT狀態是什麼原因?

CLOSE_WAIT的危害在於, 在一個端口上打開的文件描述符超過一定數量, (在linux上默認是1024, 可修改), 新來的socket連接就無法建立了.

答 : CLOSE_WAIT是被動方收到FIN包進行回覆後還沒向主動方發送FIN時的狀態, 等待調用close(sockfd) / shutdown(sockfd, SHUT_WR).  服務器中出現大量的CLOSE_WAIT很大可能是對於大量的套接字連接斷開之後, 沒有調用clsoe()關閉, 釋放資源, 是代碼不夠健壯造成的

6. 服務器出現大量的TIME_WAIT是什麼原因?

答 : TIME_WAIT是主動關閉方, 在發送最後一個ACK包之後的狀態, 意味着服務器上大量主動的關閉了連接, 通常出現在爬蟲服務器上. 解決方法如下 :
   1. 開啓地址複用 : 如果服務器重啓時需要對端口號以及socket地址進行復用,從而避免了TIME_WAIT狀態(接口 : setsockopt() )

   2. 設置MSL時間, 設置的更短一些.

7. SYN 泛洪(flood)攻擊

攻擊方的客戶端只發送SYN包發送給服務器,然後對服務器發回來的SYN+ACK什麼也不做, 直接忽略掉, 也就是不發送ACK包給服務器. 當有大量的SYN flood 攻擊時, 半連接隊列就會被佔滿, 會導致正常的客戶端連接無法連接上服務器

SYN flood攻擊的方式其實也分兩種, 第一種, 攻擊方的客戶端一直向服務端發送SYN包, 對於服務器迴應的SYN+ACK什麼也不做. 也就是不給服務端回覆ACK包.  第二種,攻擊方的客戶端發送SYN包時, 將源IP改爲一個虛假的IP, 然後服務器將SYN+ACK發送到虛假的IP, 這樣當然永遠也得不到ACK的迴應.

8. 如果已經建立了連接,但是客戶端突然出現故障了怎麼辦?

答 : TCP還設有一個保活機制, 其中有一個保活計時器.  顯然, 客戶端如果出現故障,服務器不能一直等下去,白白浪費資源. 服務器每收到一次客戶端的請求後都會重新復位這個計時器,時間通常是設置爲7200s, 即2小時, 若兩小時還沒有收到客戶端的任何數據,服務器就會每隔75秒發送一個探測報文段, 若一連發送9個探測報文, 客戶端仍然沒反應,服務器就認爲客戶端出了故障, 斷開連接,上層(應用層)體現, recv()返回0, send()觸發異常


TCP特點

  • 有鏈接 : 需要三次握手建立連接, 斷開連接要四次揮手
  • 面向字節流 : 可靠的, 有序的, 雙向的, 基於連接的字節流傳輸方式
  • 可靠傳輸 : 保證數據能夠有序, 可靠的到達對端

有連接上面已經寫完, 接下來再看面向字節流和可靠傳輸 .


面向字節流

可靠的, 有序的, 雙向的, 基於連接的字節流傳輸方式

字節流傳輸 : 發送的數據都會放到發送緩衝區中進行緩衝, 字節流傳輸不關心緩衝區有多少數據, 會根據自己實際情況選擇一次發送的數據大小, 然後從緩衝區中取出合適大小的數據進行封裝.

面向字節流傳輸 : 傳輸靈活, 並不限制上層發送的數據大小以及接收的數據大小. (字節流就像生活中的水流一樣)

字節流的弊端 : 粘包

粘包就是多條數據粘連在一起被TCP作爲一條數據進行發送或交付給上層(接收), 粘包可能發生在接收端或發送端.

本質原因 : TCP對上層數據邊界並不敏感(因爲其不關心上層是什麼數據, 有多少數據, 只管從緩衝區中取出合適大小的數據進行發送(給網絡層)或交付(給應用層)).  當發送端發送的數據較小, 需要等多個數據填滿緩衝區才發送出去, 就會造成粘包, 或者接收方不及時接收緩衝區的包, 造成多個包接收在緩衝區中

不是所有的粘包現象都需要處理, 若傳輸的數據爲不帶結構的連續流數據(如文件傳輸), 則不必把粘連的包分開(分包). 但在實際工程應用中, 傳輸的數據一般爲帶結構的數據,這時就需要做分包處理.

解決方案: 既然TCP在傳輸層並不不對數據進行邊界管理, 那麼就需要在應用層我們程序員自己進行邊界管理, 

  • 1. 使用特殊字符進行間隔, (在HTTP協議中的典型應用, 用\r\n\r\n來標識頭部結尾) .(缺點 : 數據中如果有特殊字符, 就需要進行轉義)
  • 2. 定長數據. (缺點 : 小數據(沒有規定長度長), 就只能補全, 會造成資源浪費)
  • 3. 將數據長度與數據一起發送

注意 : UDP不會產生粘包現象, 因爲其傳輸方式是面向數據報的, 並且其頭部中有數據長度


可靠傳輸

寫在另一篇中: 戳鏈接( ̄︶ ̄)↗ : https://blog.csdn.net/qq_41071068/article/details/105474889

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章