close

從零構建TCP/IP協議

【51CTO活動】8.26 帶你走進清華大學、搜狗等企業基於算法的IT運維實踐與探索
從零構建TCP/IP協議(這次叫PCT協議)

這篇博客是讀完《圖解TCP/IP協議》和《TCP/IP協議詳解卷一:協議》之後的總結

我從0構建瞭一個可靠的雙工的有序的基於流的協議,叫做PCT協議 :)

OSI七層模型和TCP/IP四層模型

談到計算機網絡,就一定會說起OSI七層模型和TCP/IP四層模型,不過我們先從為何分層 說起。

為什麼要分層

軟件開發的過程中,我們經常聽到的詞語是 解耦 , 高內聚,低耦合 等等諸如此類的 詞語,又常聽見寫Java的同學念叨著 橋接模式 , 面向接口 等詞語,那麼他們說的這些 詞語的核心問題是什麼呢?我們先從一個簡單的問題看起:

現在我們需要做一個推送系統,要對接Android和iOS兩個系統,大傢都知道,Apple有統一 的推送渠道,APNs,所以我們隻要接入這個就好,但是Android的推送在國內是百傢爭鳴, 就拿之前我為公司接入推送通知來舉例,要接入極光,小米,可能要接入華為推送。

那我要怎麼從具體的推送裡抽象出來呢?運用面向對象的想法,我們很容易就能想到, 我們有一個父類,叫 BasePush ,他的子類就是具體的 MiPush , JPush , HMSPush 。 父類中有 push_by_id 和 push_by_tag 等方法,子類重寫。這樣我們在具體實現的時候 實例化子類,並且調用對應的方法就好。這種思想其實就是面向接口編程,在Java中我們 可以轉變一下編程的寫法,把繼承變成接口。在Python中我們就可以直接腦補這種寫法。 用圖來表示,純粹面向對象的時候我們的想法是這樣的:



如果我們把上面的圖倒過來,就變成瞭面向接口:



在使用面向接口之後,我們就是做瞭這樣一種假設:

defpush(pusher,id):pusher.push_by_id(id) 即,傳給push函數的pusher實例一定存在 push_by_id 方法。正是基於這樣一種假設, 我們得以把具體業務代碼和具體的推送商劃分開來,這就是所謂的抽象,也就是一種分層。

要分層的原因也就顯現出來瞭,為瞭把不同的東西錯綜復雜的關系劃分開來,也就是古話 說的 快刀斬亂麻 的這種感覺。

兩種網絡模型

日常編程裡我們用的最多的就是TCP瞭,UDP也是有的,但是很少,舉一些常見的例子:

DNS - UDP 連接MySQL - TCP 連接Redis - TCP RPC - TCP 訪問網站 - TCP 當然瞭,這隻是常見實現方式如此,其實用UDP也是可以實現的。這篇博客裡我們暫時不討論 UDP。我們先來看TCP/IP四層是怎麼分層的:

ascii 表格其實挺好看的,最後渲染的時候因為寬字符的原因格式有點亂掉瞭,下同

+------------+-----------------------+|層|例如|+------------+-----------------------+|應用層|HTTP協議|+------------+-----------------------+|傳輸層|TCP|+------------+-----------------------+|網絡互連層|IP|+------------+-----------------------+|網絡接口層|如網線,雙絞線,Wi-Fi|+------------+-----------------------+ 我們直接把 TCP/IP 四層協議 映射到 OSI七層協議 上看:

+--------------+---------------+----------------+|OSI七層協議|例如|對應TCP/IP四層|+--------------+---------------+----------------+|應用層|HTTP協議||+--------------+---------------+||表示層||應用層|+--------------+---------------+||會話層|||+--------------+---------------+----------------+|傳輸層|TCP|傳輸層|+--------------+---------------+----------------+|網絡層|IP|網際層|+--------------+---------------+----------------+|數據鏈路層|因特網,Wi-Fi||+--------------+---------------+網絡接口層||物理層|雙絞線,光纜||+--------------+---------------+----------------+ 接下來我們將從底層逐層向上來解析網絡,最後我們將簡略的介紹TCP(TCP的知識足夠 寫好幾本書,一篇博客裡遠遠介紹不完。不信可以看看TCP/IP協議詳解那三卷書加起來 有多厚)。

物理層

物理層,顧名思義,就是物理的,可見的東西。也就是平時我們所說的光纖,Wi-Fi(無線電波) 等,我們知道計算機是用0和1來表示的,對應到不同的介質裡是不同的表現形式, 因此為瞭把物理層的實現屏蔽掉,我們把這些都分到一層裡,例如Wi-Fi通過波的 波峰與波谷可以表示出0和1的狀態(我們平時會說成1和-1,對應計算機裡其實就是1和0)。 對應到電裡,我們可以用高電壓和低電壓來表示出1和0。如同最開始講的例子一樣, 我們不管具體的介質是什麼,隻知道,我們用的這個介質有辦法表示1和0。

數據鏈路層

如果我們去郵局寫一封信,填完收件人之後,郵局派發的順序可能是,先投遞到指定的 國傢,然後投遞到具體的省,然後市。。。逐次投遞下去。那麼我們玩電腦的時候,計算機 要怎麼把A發給B的信息準確送達呢?

肯定大傢都要有一個地址,上一節我們知道瞭,不同的介質都有他的方式表示1和0,那麼 我們給介質的兩端加上地址,我們叫做MAC地址,如何?就拿路由器來說吧,路由器的 MAC地址叫做 router ,手機的MAC地址叫做 phoner ,為瞭表示成0和1,我們分別取 字符串的ASCII的二進制來表示,路由器叫做 1110010 1101111 1110101 1110100 1100101 1110010 , 而手機則叫做: 1110000 1101000 1101111 1101110 1100101 1110010 ,現在我們終於可以發信息 瞭,最少是相鄰的兩個東西可以透過某種介質來發信息,所以我們定下這樣的協議:

協議,其實就是一種約定 :)

最開始我們發送111表示信息開始 然後,我們先有48個bit表示發送者的MAC地址,再有48個bit表示接受者的MAC地址 之後,就是我們要發送的信息 最後我們發送000表示結束,如果開頭和結尾不是這樣的,那麼說明這是假的信息。 知道上面為啥手機叫 phoner 而不叫 phone 瞭嘛 :) 就是為瞭保證地指名長度一樣

hello 的二進制表示是 1101000 1100101 1101100 1101100 1101111 ,如果路由器要向 手機發送 hello 的話,那麼就發送這樣一串二進制(用換行分割,這樣更容易看清楚):

這樣表示看起來可行,不過遇到一個問題,就是如果這一串二進制中間就出現瞭000怎麼辦? 因為計算機讀取的時候是從頭開始讀的,這樣子計算機就會亂掉。

為瞭解決這個問題,我們修改一下協議,在111之後加上發送者地址+接受者地址+所要發送的 信息的長度。我們用 16個字節來表示,也就是說這中間不能發送多於 2 ** 16 個bit。

所以協議變成瞭:

最開始我們發送111表示信息開始 隨後我們用16個bit表示包的長度 然後,我們先有48個bit表示發送者的MAC地址,再有48個bit表示接受者的MAC地址 之後,就是我們要發送的信息 最後我們發送000表示結束,如果開頭和結尾不是這樣的,那麼說明這是假的信息。 發送者地址+接收者地址+hello的bit長度是 6 * 8 + 6 * 8 + 5 * 8 = 136,二進制表示 為: 00000000 10001000

所以發送的整個信息變成瞭:

網絡層

現在我們終於可以發送信息瞭。不過有個缺點,我們隻能在相鄰的時候才可以發送信息, 那有沒有辦法可以借助兩兩傳遞,在不同的地方也發送信息呢?有,那就是我們的網絡層 也就是ip(我們能遇到的最通俗易懂的一個名詞瞭,暫時把它當作網絡層的代名詞也不為過)。

剛剛我們已經學會瞭一種技術,就是分配一個地址,剛剛的叫做MAC地址,我們用來做 相鄰兩個節點的定位。其實這個地址也可以用來在多個節點之間找人,基於這樣一種 技術:每個節點都知道和自己相鄰的節點的MAC地址,那麼,比如這樣一種連接方式:

A-B-C-E/-D- A向E發送消息,就可以這樣: A向B和D發消息:給我發到台中產後月子中心E去 B和D接到之後發現來源是A,所以就隻給C發消息:給我發到E去 C接到消息之後發現來源是B和D,所以就給E發消息:給我發到E去 E接到消息之後發現接收方是自己,所以就把消息吞瞭 你別說,這種方式好像真的行得通呢,除瞭有一個顯著的問題,A向E發送一份消息, 最後E收到瞭兩份,這個我們需要到後面進行去重。我們先打上一個TODO的標簽吧。

還有一個細節問題,不知道大傢發現瞭麼,剛才我們說過,MAC地址是相鄰兩個節點 通信用的,裡面有來源地址和目標地址,如果我們向上面這樣傳輸的話,每個節點都 隻是把裡面的信息傳過去,但是來源地址卻改要改寫成自己的MAC地址,要不然的話, B就不知道信息是A發來的還是C發來的呀,對不對?那問題就來瞭,E要怎麼知道信息 其實是從A發過來的呢?

沒辦法瞭,我們隻好在傳輸的信息裡把真正的來源地址寫進去,所以我們又定瞭一個 協議,我們管它叫做ip:

MAC攜帶的信息的開始,是來源的ip地址,32個bit表示 然後是目標的ip地址,32個bit表示 然後是我要帶的信息 那和上面的數據鏈路層的協議合一下起來,假設來源地址是 192.168.1.1 ,目標地址是 192.168.1.2 ,發送的信息還是 hello ,整個包就像這樣:

111(開始)0000000011001000(長度)011100100110111101110101011101000110010101110010(來源MAC地址)011100000110100001101111011011100110010101110010(目標MAC地址)11000000101010000000000100000001(來源ip地址)11000000101010000000000100000010(目標ip地址)0110100001100101011011000110110001101111(字符串 hello )000(結束) 這樣是不是就很科學?那必須的。哎呀,終於可以跨節點發送消息瞭,小開心~

可是還是有問題,如果我想確定A發的信息一定送達瞭E怎麼辦?怎麼提供可靠性?IP這一層 並不提供可靠性,隻是說盡量送達。看來有台中產後照護必要再來一層!

傳輸層

我們知道,一臺計算機上可能有很多個程序在運行,那怎麼區分不同的程序呢?所以我們 給程序加上瞭id,叫做pid。那計算機網絡通信的時候怎麼區分呢?又假設n個進程想和另外 一臺機器上的某一個進程通信呢?怎麼辦?

不如我們再分配一個id吧,他們共同持有這個id就好瞭。我們把這個id叫做端口(port)。 這樣子的話,通過ip地址我們可以確定計算機,通過端口我們可以確定一個或多個進程。

我們繼續造協議,不過這一次我們想要這個協議賊可靠,所以要多做一些工作。其實要是 按照七層協議來實現的話,完全不必在這一層幹這麼多事情,不同的層幹不同的事情嘛, 對不對。不過為瞭理解TCP協議,我們呀,也跟著來自己捏造一個協議,不如叫PCT好瞭。

繼續,我們要在ip帶的信息裡規定好我們這樣發:

首先是來源地址的端口號,8個bit來表示,因為ip裡面已經待瞭ip地址,我這裡就不重復帶瞭 然後是目標地址的端口號,8個bit來表示 這樣,簡單的PCT協議就做好瞭。

還有一個問題,就是我們要保證發出去的信息是有序的,因為可能有的信息走光纖, 有的信息走Wi-Fi,他們傳輸速率不一樣嘛。

所以我們在協議裡這樣寫:

首先是來源地址的端口號,8個bit來表示,因為ip裡面已經待瞭ip地址,我這裡就不重復帶瞭 然後是目標地址的端口號,8個bit來表示 然後是這個包的序號,8個bit來表示 但是我們說好瞭要把這個協議打造成一個可靠的協台中產後護理之家介紹議,可不能食言。我想想,怎麼讓他 可靠呢,無非就是我發一個信息,你告訴我你收到瞭,要是你不告訴我,我就發到你告訴我 為止。差不多就是這麼個意思。但是呢,又不想構造多個不同的協議,你知道,編程的時候 要是寫一堆的if-else樹那可就很蛋疼瞭。再改改協議:

首先是來源地址的端口號,8個bit來表示,因為ip裡面已經待瞭ip地址,我這裡就不重復帶瞭 然後是目標地址的端口號,8個bit來表示 然後是這個包的序號,8個bit來表示 然後是想確認的包的序號,8個bit來表示 咦,點睛之筆耶,這個確認的包的序號,因為我們是雙向通信,我發他信息的時候還可以順便 確認我收到瞭他的包啊,真是一箭雙雕。

TCP是一個面向流的協議,什麼叫流?車流,水流,車流比較形象。車和車之間是分開的, 但是速度一快起來,就可以把它們看成連起來的。TCP也是這樣,單個包之間是分開的, 但是卻可以看作是連起來,為什麼呢?因為每個包裡都帶瞭ip地址和端口號,ip地址和端口 號一樣的,就可以看作是連起來的 :)

所以我們可以想象一下,我們的ip地址是 192.168.1.1 , 端口號是 1, 目標的ip地址是 192.168.1.2 , 端口號是 2。那我們發送這樣的包:

111(開始)0000000011101000(長度)011100100110111101110101011101000110010101110010(來源MAC地址)011100000110100001101111011011100110010101110010(目標MAC地址)11000000101010000000000100000001(來源ip地址)11000000101010000000000100000010(目標ip地址)00000001(來源的端口號)00000010(目標的端口號)00000001(發送的包的序號是1)00000000(已經確認的包的序號是0,表示啥都沒有嘛)0110100001100101011011000110110001101111(字符串 hello )000(結束) duang,就這樣,我們構建起瞭屬於自己的可靠的基於流的雙工的協議 :)

順便我們還完成瞭上面的TODO,通過序號我們就可以判斷這個包是不是重復瞭,哈哈哈, 一箭n雕~

TCP三次握手四次揮手滑動窗口擁塞控制等就不講瞭,還是去看《TCP/IP協議詳解卷一》吧 :)

應用層

這下我們終於可以放心大膽的發送消息瞭台中月子中心收費,PCT協議是個負責任的協議,如果能送到,他就一定 會送到,並且是有序的,要是網絡壞掉瞭,實在連不上,他就會告訴我網絡連不上。

這樣子來編程方便多瞭呀。

現在我想知道瀏覽器和服務器是怎麼通信的。我們來看看百度。

$telnetwww.baidu.com80Trying183.232.231.173...Connectedtowww.baidu.com.Escapecharacteris'^]'.GET/HTTP/1.1HTTP/1.1302MovedTemporarilyDate:Sat,12Aug201710:45:14GMTContent-Type:text/htmlContent-Length:215Connection:Keep-AliveLocation:http://www.baidu.com/search/error.htmlServer:BWS/1.1X-UA-Compatible:IE=Edge,chrome=1BDPAGETYPE:3Set-Cookie:BDSVRTM=0;path=/ html head title 302Found /title /head bodybgcolor= white center h1 302Found /h1 /center hr center pr-nginx_1-0-350_BRANCHBranchTime:TueAug820:41:04CST2017 /center /body /html ^]telnet Connectionclosed. 輸入 GET / HTTP/1.1 之後回車,百度就給我返回瞭下面的一長串,然後瀏覽器再根據 返回的內容進行渲染,這又是一個大話題瞭,不講瞭不講瞭,收工 :)

點贊 0


每日頭條、業界資訊、熱點資訊、八卦爆料,全天跟蹤微博播報。各種爆料、內幕、花邊、資訊一網打盡。百萬互聯網粉絲互動參與,TechWeb官方微博期待您的關註。




↑掃描二維碼

想在手機上看科技資訊和科技八卦嗎?

想第一時間看獨傢爆料和深度報道嗎?

請關註TechWeb官方微信公眾帳號:

1.用手機掃左側二維碼;

2.在台中高級月子中心添加朋友裡,搜索關註TechWeb。
arrow
arrow

    fxz5wkn7n 發表在 痞客邦 留言(0) 人氣()