當我們說TCP是可靠協議時,我們真正表達的是什么

轉載出處:https://blog.csdn.net/dog250/article/details/82177299
很明確地說,從通信意義上推敲,TCP一點都不可靠。一個抽象的協議,怎么可能左右介質來保證可靠,不存在的。但凡是經由某種介質的通信行為均不可能是絕對可靠的!

正好比我們現實生活中的保險,其實它什么都不能阻止,什么風險也保證不了它的不發生,它保證不了飛機不會掉下來,也無法阻止人生病…事實上,TCP就是通信中的保險業。

TCP是如何設計出來的?推而廣之這類通信協議是如何設計出來的?如果說讓你在一個不可靠的介質上運行一個可靠的協議,你該怎么做?本文將介紹內中的些許因果。

可靠的通信協議如何構建

這要從經典的兩軍問題說起。

首先介紹一下兩軍問題,來自Wiki的解說是最好的:
Two Generals’ Problem:https://en.wikipedia.org/wiki/Two_Generals%27_Problem
兩軍問題本質上一個一致性確認問題,也就是說通信雙方而不是一方(這對理解TCP非常重要)都要確保信息的一致性。即假設通信雙方為A和B,那么A發送一則消息M給B,所謂的可靠性則是要同時滿足下面的條件:

  • 信道是不可靠的,任何消息均可能以任何概率丟失
  • 如果AA不能確保消息到達對方時,不能重發消息
    這一點非常重要,在經典的兩軍問題中,消息是由信使傳遞的,而信使是人,人是軍隊作戰的最重要資源也是最不可靠的資源,比如會叛變…因此每條消息或者確認相互只能派遣一個信使去遞送消息,在通信上講,就是消息不能重發!
  • 對于A而言,要確保A知道B已經收到了M
  • 對于B而言,要確保自己收到M這件事已經被A知道

數學上很容易用反證法證明上述的兩軍問題是根本無解,即一致性通信的完全可靠性是一種奢望。下面我來試著推導一下。

假設在時間點TnTn的傳輸,我們知道,信道是不可靠的,所以它可能會丟失,而它一旦丟失,整個交互過程便失去了一致性,這與假設是矛盾的,所以,一致性是不可能的


這個問題貌似徹底拆了通信技術的根基,那么通信技術還有什么意義呢?

事實上,

  • 首先,通信協議從來都不是為了滿足完全的一致性需求

通信的意義是,在時間序列上滿足消息傳遞的單向完成需求即可!通信的本質問題是確保消息傳遞,而不是維護一致性,一致性應該由業務自身來負責,通信僅僅提供消息傳遞的基礎設施而已。

  • 其次,通信傳輸的是字節電脈沖,消息可以重發

這便大大削弱了兩軍問題的強約束。基于上述的假設,我們來一步步地推導出TCP協議為什么要這么設計。

如果仔細推敲的話,你會發現,即便是消息傳遞,在數學上也是無法確保在不可靠的信道上確保消息傳遞的,然而,我們換個思路,即自問“信道到底不可靠到什么程度?”

是100%不可靠嗎?如果是的話,意味著斷路,即雙方是不可達的,無論我們發送多少次數據包,均會丟失,這樣我們馬上可以結束這個沒有意義的討論,因此,所謂的不可靠只是說信道會出現概率性丟包,丟包概率pp之間的!

這個意義十分重大,這意味著,只要我們重試特定消息MnMn的確認!,這是完全確定的一個結論,沒人反對吧。

這邊自然而然導出了可靠通信的第一個原則:

  • 1.超時重傳

該原則可以確保消息一定能有機會到達對端。每當發出一個數據包,在預期的時間內沒有確認到達,就重傳它。關于超時重傳的細節,本文稍后會淺談一下,但是現在,我們來看另外一個問題。


如何確保消息單向傳遞的完成?

換句話說,所謂消息單向傳遞的完成,即需要一種標志性的信號*,該信號揭示了消息已經被對端接收這個事實,很顯然,對端發送針對特定消息的確認并且本端收到即可。

一旦AA,不然它也不會發送確認。但是由上文可知,這個確認在不可靠的信道上也可能丟失,不過這不必驚慌,因為我們已經有了推論,即針對任意消息,只要我們重復傳輸的次數足夠多,該消息就一定能到達對端,在該推論下,采用超時重傳原則即可。

現在看來,我們導出的下列措施已經解決了幾乎所有問題:
1. 針對消息MnMn的超時重傳機制

但是這是最優解嗎?

非也!這只是一種可行的方案,但不是唯一的方案,更不是最有的方案。導出最優解需要我們深入到通信網絡的本質,先看一篇文章:
馬太效應/冪律分布的本質以及其數學表述:https://blog.csdn.net/dog250/article/details/79146511
注意,我們的通信網絡是一個網狀拓撲的連通圖,無論是單節點連接數屬性還是流量屬性均符合冪律規律,從雙對數坐標曲線可以看出網絡規模和節點的各屬性特征之間的對數線性關系,而網絡規模來自于某種指數級增長的復制,單節點的屬性特征來自于該節點的行為,很顯然,在這個雙對數坐標下線性的通信網絡中,如果想等比例地縮放其規模而不至于崩潰,就必須用指數來控制單節點的行為(把雙對數坐標化為笛卡爾坐標即可展現)。

實際上,我們把雙對數坐標中的直線(求解微分方程的結果)展開到相應的笛卡爾坐標系,就是一條指數規律的曲線了。

再看另一個抽象,即如果數據包在傳輸過程中丟失了,這件事跟什么因素相關?誠然,在網絡通信中,這件事肯定有可能是和傳輸介質相關的,但是在節點數量,即網絡規模這個因素下,介質的問題可以忽略不計。也就是說,節點越多,傳輸越容易發生沖突,數據也就越不容易到達對端。即丟包事件和網絡規模相關,網絡是一個線性系統,所以,丟包的重傳必須具備指數級的時間特征。

介質的問題隨著網絡規模的擴大是線性增長的,而傳輸沖突的問題隨著網絡規模的擴大則是指數級的,孰重孰輕,立判!

如果你了解早期的以太網,即總線式的CSMA/CD以太網,你會發現同樣的事實。

因此,很明確,超時重傳的超時規則在線性系統的平衡通過指數特性的單獨節點行為來維持的原則下,則必須是:

  • 2.超時重傳-指數退避

有了這個原則,我們再回過頭來看如何實現消息以及消息確認的超時重傳。直接說結論,即不對確認進行重傳,因為確認和消息本身屬于同一個行為,針對消息本身的超時重傳已經自動包含了一個確認,如果再針對確認進行重傳,就會破壞單點行為的指數特征,因此我們導出可靠通信的第三個特征:

  • 3.不對確認進行超時重傳

由于我們僅僅想確保消息單向傳遞的可靠,即確保對端收到了本端發出的消息而無需讓對端知道這件事,第四個特征也隨即導出:

  • 4.不對確認進行確認

基礎設施構建就此完畢,考慮到通信往往是雙向的,我們需要在其上構建一個可靠的雙向通信協議,怎么辦?

簡單,在另一端BB重新這么來一遍即可!于是我們觀察到,兩軍問題如果超時重傳的前提下將雙向的消息傳遞和確認分解成兩個單向的消息傳遞和確認,事情就會簡單得多。

原始的兩軍問題解法:
這里寫圖片描述

轉換后的解法:
這里寫圖片描述

嗯,轉換后的解法,即我們熟悉的協議,TCP協議的最基本形式。現在進入TCP時間!


TCP握手,揮手,一致性的問題

經常有人問,TCP為什么是3次握手,而不是2次,也不是4次,5次。知乎上經常會有這種問題,但是答案幾乎是千篇一律的錯誤或者答非所問,最常見的答案只是描述一下TCP握手的細節,然后導出這么做是OK的,其實不這么做也是OK的這一點沒人提。

最常見的錯誤答案:
1. 這是一種權衡,因為無數次握手也不可能完全可靠;
2. 描述握手的協議細節;
3. …

看過了我上面的論述,這個問題應該非常好答了,所謂的TCP建立連接的握手,實質上就是建立一個雙向的可靠通信連接,一邊一個來回,每一邊都自帶超時重傳來確保可靠性(而不是靠握手的次數)。TCP的3次握手是優化的結果,其實它應該是4次握手,由于是從零開始的建立連接,因此將SYN的ACK以及被動打開的SYN合并成了一個SYN-ACK,僅此而已。

握手的作用,旨在確定兩個雙向的初始序列號,TCP用序列號來編址傳輸的字節,由于是兩個方向的連接,所以需要兩個序列號,握手過程不傳輸任何字節,僅僅確定初始序列號
這里寫圖片描述

說完了3次握手,那么,其姊妹問題,為什么TCP的斷鏈是4次揮手而不是3次?

換句話說,即是在問為什么針對主動斷開方的FIN的ACK以及本端的FIN不能合并?

非常簡單,因為TCP是在一個單向可靠通信系統基礎上構建而成的雙向傳輸控制協議,握手期間可以合并ACK和SYN,是因為在握手之前兩端沒有任何連接上的包袱,而在斷鏈揮手時,一端認為可以斷開了,另一端卻不一定,可能另一端還有數據要傳輸,所以便不能合并,被動關閉的一方只能單獨處理針對FIN的ACK以及自己的FIN,僅此而已。

這里寫圖片描述

再來一個問題,TCP能確保一致性嗎?換句話說,TCP協議是兩軍問題的一個解嗎?

遠遠不是!TCP并不確保一致性。

任何時間點,TCP都不能完全確認當前時刻連接雙方的狀態,此處所謂的狀態包括兩端傳輸的數據。一致性是基于消息的,而不是基于連接的!也就是說,TCP只有收到下一個數據包時,才知道上一個數據包的接收情況,而無法實現隔空打人!TCP的好處僅在于,它在一個信息流上實現了一個一致性確認的流水線方式

我們在理解這個流水線方式的時候,不應該考慮滑動窗口,那樣會比較難以理解,我們應該僅僅考慮單字節停等機制。事實上也確實是這樣,滑動窗口機制只是為流量控制而引入的,單字節停等效率又太低,所以說這并無傷大雅,你把字節換成窗口即可,即單窗口停等。

如果我們把一致性推廣到連接的層面,在連接層面,一致性就是靠4次揮手保證的。

我們可以看到,4次揮手那里的狀態機非常之復雜,這是有原因的,即便是引入了TIMEWAIT狀態,也還是沒有辦法保證徹底的一致性,這是兩軍問題本質上不可解的一個結論,僅此而已。


1974年的TCP

現在你應該大致知道TCP如何保證可靠性了,進一步,如果你想知道TCP協議的頭部為什么是那個樣子,這一切是如何安排的,你就不得不去讀一下一篇陳年的論文:
《A Protocol for Packet Network Intercommunication》:https://www.cs.princeton.edu/courses/archive/fall08/cos561/papers/cerf74.pdf
我來大致介紹一下這篇劃時代的論文。

毫不夸張地說,該論文奠定了以TCP/IP為核心的互聯網的基礎,我們今天能刷抖音,用微信聊天,能在線看片…這一切要不是這篇論文,不會是現在這個樣子。

該論文的重點不是TCP協議,而是TCP/IP作為一個整體如何發揮作用,早在1974年,分層模型還不算太成熟,所以當我們提起TCP/IP的時候,要明白,最初的時候,這兩個協議是牢牢切合在一起的,到了后來為了兼容純IP轉發,才加入了UDP,這個時候,人們意識到分層模型的必要性。于是抽象而成的ISO/OSI模型。

該論文主要有兩個論題:

  • 網關的概念和意義–最終的IP協議
  • 進程間通信的傳輸控制–最終的TCP協議

注意,我們看看TCP最初的形式,沒錯,它是作為一種進程間通信的手段被提出的,當初TCP作為進程間通信手段,側重于不同主機的進程間通信,因此,我們可以清晰看到它的API和文件IO的API是多么相似,這也是socket可以作為文件描述符的原因。

此外,還有值得注意的是,TCP的ACK號被定義為下一個索要字節的序列號,這在當時實現了一種簡易且完備的字節流水線,節省了協議頭空間,看到這個設計,簡直太帥!雖然它也帶來了很多問題,比如無法精確測準RTT,比如無法進行選擇確認,進而無法進行良好的擁塞控制,但不得不說,在空間重于時間的1970年代,這絕對是創舉,畢竟,擁塞控制在當時是沒有意義的,1988年才被引入。


1974年的互聯網

在1974年那篇論文之后,同樣的作者歸納總結出了RFC675:
《RFC675:SPECIFICATION OF INTERNET TRANSMISSION CONTROL PROGRAM》:https://tools.ietf.org/html/rfc675
這篇劃時代的RFC正式提出了互聯網這個概念,我們常說的Internet就是Internetworking的縮寫。

TCP/IP協議確實不是一個協議棧,最初它們只是一個協議,僅此而已,不多說。


那么,接下來?

接下來,skinshoe wu來了,攜帶著他的高級皮鞋,還有高級西裝。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/382607.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/382607.shtml
英文地址,請注明出處:http://en.pswp.cn/news/382607.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

IP地址=192.168.127.101,子網掩碼255.255.255.192,計算網絡地址,主機號,廣播地址和主機最大數

純手寫,如果哪寫錯了,還希望指正 IP地址192.168.127.101,子網掩碼255.255.255.192,計算網絡地址,主機號,廣播地址和主機最大數 255.255.255.192子網掩碼轉換成二進制為 11111111.11111111.11111111.11000…

C++ 類模板遇到繼承的問題以及解決

#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;template<class T> class Base {T m_A; //子類創建時候 必須要知道T的類型&#xff0c;才能給父類中的m_A分配內存 };template<class T1 , class T2> class Son :public Base<T2…

A B C類IP地址的判斷

IP地址131.153.12.71是一個&#xff08;&#xff09;類IP地址。 IP地址分類A類網絡的IP地址范圍為1.0.0.1&#xff0d;127.255.255.254&#xff1b; B類網絡的IP地址范圍為&#xff1a;128.1.0.1&#xff0d;191.255.255.254&#xff1b; C類網絡的IP地址范圍為&#xff1a;1…

linux sshd啟動失敗 sshd re-exec requires execution with an absolute path

sshd 提示 sshd re-exec requires execution with an absolute path提示 需要絕對路徑來執行 sshd&#xff0c; 剛開始學 不知道怎么用絕對路徑來啟動&#xff1f; 以后明白了&#xff0c; 這里再補充上 google 使用 service sshd restart 輸入密碼來啟動

有一個小白程序員,寫了一個只能對5個數字進行排序的函數,現在有25個不重復的數字,

題目&#xff1a;有一個小白程序員&#xff0c;寫了一個只能對5個數字進行排序的函數&#xff0c;現在有25個不重復的數字&#xff0c;請問小白同學最少調用幾次該函數&#xff0c;可以找出其中最大的三個數&#xff1f; A.5 B.6 C.7 D.8 答案&#xff1a;C 解析&#xf…

ubuntu修改ls顯示目錄的顏色

ls查看目錄&#xff0c; 背景是黑色的&#xff0c;目錄顏色是深藍色&#xff0c;基本看不清楚。 解決辦法 因為ubuntu下的/etc/目錄里沒有DIR_COLORS, 所以費了點勁兒。 2. 利用dircolors命令&#xff0c;查看我們的系統當前的文件名稱顯示顏色的值&#xff0c;然后利用管道重…

初始序列為1 8 6 2 5 4 7 3一組數采用堆排序,當建堆(小根堆)完畢時,堆所對應的二叉樹中序遍歷序列為

初始序列為1 8 6 2 5 4 7 3一組數采用堆排序&#xff0c;當建堆&#xff08;小根堆&#xff09;完畢時&#xff0c;堆所對應的二叉樹中序遍歷序列為&#xff1a;&#xff08;&#xff09; 8 3 2 5 1 6 4 7 3 2 8 5 1 4 6 7 3 8 2 5 1 6 7 4 8 2 3 5 1 4 7 6 A

設一組初始記錄關鍵字序列為(25,50,15,35,80,85,20,40,36,70)進行一趟歸并后的結果為

設一組初始記錄關鍵字序列為(25&#xff0c;50&#xff0c;15&#xff0c;35&#xff0c;80&#xff0c;85&#xff0c;20&#xff0c;40&#xff0c;36&#xff0c;70)&#xff0c;其中含有5個長度為2的有序子表&#xff0c;則用歸并排序的方法對該記錄關鍵字序列進行一趟歸并…

C++ 異常基本語法

#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;class MyException { public:void printError(){cout << "我自己的異常類的錯誤" << endl;} };class Person { public:Person(){cout << "Person的構造函數&q…

文字常量區和棧區考點

求以下程序輸出結果 #include <stdio.h>char * fun1() {char * str "hello";return str; }char * fun2() {char str[] "world";return str; } int main() {printf("%s\n", fun1()); printf("%s\n", fun2()); return 0; }結…

C++ 異常變量的生命周期

#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;class MyException { public:MyException(){cout << "MyException構造函數調用" << endl;}MyException(const MyException & e){cout << "MyException拷貝…

判斷棧的壓入和彈出

序列1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5是壓棧序列。序列 4&#xff0c;3&#xff0c;5&#xff0c;1&#xff0c;2是彈出序列。判斷第二個是不是彈出序列 三步走 如果下一個彈出的數字剛好是棧頂數字&#xff0c;直接彈出如果不在棧頂&#xff0c;就一直…

C++ 多態在異常中的使用

#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;//異常 基類 class BaseException { public:virtual void printError() 0; };//空指針 異常 class NULLPointException :public BaseException { public:virtual void printError(){cout <…

一條語句判斷數x是否2的n次冪.求取二進制1的個數

一條語句判斷數x是否2的n次冪 return &#xff01;(x & (x - 1)); 求取十進制數字元素1的個數 int fun(int x) { int count 0; int i, j, k; /方法2 負數不可計算&#xff0c;需要改進/ while (x ! 0){ if (x & 1 1) count; x x >> 1; } /方法1/ while (x …

C++ 標準輸入流01

#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;/* cin.get() //一次只能讀取一個字符 cin.get(一個參數) //讀一個字符 cin.get(兩個參數) //可以讀字符串 cin.getline() cin.ignore() cin.peek() cin.putback() */void test01() {//cin.get(…

動態規劃學習筆記1

求連續子數組的最大和問題 代碼不重要&#xff01;重要的是思想過程(括弧 好難啊&#xff01;&#xff01;&#xff01;) 輸入的數組為{1&#xff0c;-2&#xff0c;3&#xff0c;10&#xff0c;-4&#xff0c;7&#xff0c;2&#xff0c;-5}&#xff0c;和最大的子數組為{3&…

C++ 文件讀寫操作01

#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; //文件讀寫的頭文件 #include <fstream>//1、寫文件 void test01() {//參數 1 文件路徑 參數2 打開方式//ofstream ofs("./test.txt", ios::out | ios::trunc);ofstream …

動態規劃學習筆記2

題目描述&#xff1a; 在一個mn的棋盤的每一格都放有一個禮物&#xff0c;每個禮物都有一定的價值&#xff08;價值大于0&#xff09;。你可以從棋盤的左上角開始拿格子里的禮物&#xff0c;并每次向右或者向下移動一格直到到達棋盤的右下角。給定一個棋盤及其上面的禮物&…

C++ i/o類庫中 常用流類

類名 作用 在哪個頭文件中聲明 ios 抽象基類 iostream ------------------------------------------------------------------- istream 通用輸入流和其他輸入流的基類 iostream ostream 通用輸出流和其他輸出流的基類…

動態規劃學習筆記3

某工廠預計明年有A、B、C、D四個新建項目&#xff0c;每個項目的投資額Wk及其投資后的收益Vk如下表所示&#xff0c;投資總額為30萬元&#xff0c;如何選擇項目才能使總收益最大&#xff1f; Project Wk Vk A 15 12 B 10 8 C 12 9 D 8 5 聲明一個 二維數組 m[…