從零構建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。
台灣電動床工廠
電動床
台灣電動床工廠
電動床
AUGI SPORTS|重機車靴|重機車靴推薦|重機專用車靴|重機防摔鞋|重機防摔鞋推薦|重機防摔鞋
AUGI SPORTS|augisports|racing boots|urban boots|motorcycle boots
一川抽水肥清理行|台中抽水肥|台中市抽水肥|台中抽水肥推薦|台中抽水肥價格|台中水肥清運
X戰警多鏡頭行車記錄器專業網|多鏡頭行車記錄器|多鏡頭行車紀錄器比較|多鏡頭行車紀錄器推薦|多鏡頭行車紀錄器影片
台中抽水肥專業網|台中抽水肥|台中市抽水肥|台中抽水肥推薦|台中抽水肥價格|台中水肥清運
台灣靜電機批發工廠|靜電機|靜電機推薦|靜電油煙處理機|靜電油煙處理機推薦
優美環保科技工程-靜電機,靜電機推薦,靜電機保養,靜電機清洗,靜電油煙處理機
留言列表