自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從零構(gòu)建TCP/IP協(xié)議

開發(fā) 前端
這篇博客是讀完《圖解TCP/IP協(xié)議》和《TCP/IP協(xié)議詳解卷一:協(xié)議》之后的總結(jié),我從0構(gòu)建了一個(gè)可靠的雙工的有序的基于流的協(xié)議,叫做PCT協(xié)議。

從零構(gòu)建TCP/IP協(xié)議(這次叫PCT協(xié)議)

這篇博客是讀完《圖解TCP/IP協(xié)議》和《TCP/IP協(xié)議詳解卷一:協(xié)議》之后的總結(jié)

我從0構(gòu)建了一個(gè)可靠的雙工的有序的基于流的協(xié)議,叫做PCT協(xié)議 :)

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

談到計(jì)算機(jī)網(wǎng)絡(luò),就一定會(huì)說起OSI七層模型和TCP/IP四層模型,不過我們先從為何分層 說起。

為什么要分層

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

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

那我要怎么從具體的推送里抽象出來呢?運(yùn)用面向?qū)ο蟮南敕?,我們很容易就能想到?我們有一個(gè)父類,叫 BasePush ,他的子類就是具體的 MiPush , JPush , HMSPush 。 父類中有 push_by_id 和 push_by_tag 等方法,子類重寫。這樣我們在具體實(shí)現(xiàn)的時(shí)候 實(shí)例化子類,并且調(diào)用對應(yīng)的方法就好。這種思想其實(shí)就是面向接口編程,在Java中我們 可以轉(zhuǎn)變一下編程的寫法,把繼承變成接口。在Python中我們就可以直接腦補(bǔ)這種寫法。 用圖來表示,純粹面向?qū)ο蟮臅r(shí)候我們的想法是這樣的:

從零構(gòu)建 TCP/IP 協(xié)議

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

從零構(gòu)建 TCP/IP 協(xié)議

在使用面向接口之后,我們就是做了這樣一種假設(shè):

 

  1. def push(pusher, id): 
  2.     pusher.push_by_id(id) 

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

要分層的原因也就顯現(xiàn)出來了,為了把不同的東西錯(cuò)綜復(fù)雜的關(guān)系劃分開來,也就是古話 說的"快刀斬亂麻"的這種感覺。

兩種網(wǎng)絡(luò)模型

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

  • DNS -> UDP
  • 連接MySQL -> TCP
  • 連接Redis -> TCP
  • RPC -> TCP
  • 訪問網(wǎng)站 -> TCP

當(dāng)然了,這只是常見實(shí)現(xiàn)方式如此,其實(shí)用UDP也是可以實(shí)現(xiàn)的。這篇博客里我們暫時(shí)不討論 UDP。我們先來看TCP/IP四層是怎么分層的:

ascii 表格其實(shí)挺好看的,最后渲染的時(shí)候因?yàn)閷捵址脑蚋袷接悬c(diǎn)亂掉了,下同

  1. +------------+-----------------------+ 
  2. | 層         | 例如                  | 
  3. +------------+-----------------------+ 
  4. | 應(yīng)用層     | HTTP協(xié)議              | 
  5. +------------+-----------------------+ 
  6. | 傳輸層     | TCP                   | 
  7. +------------+-----------------------+ 
  8. | 網(wǎng)絡(luò)互連層 | IP                    | 
  9. +------------+-----------------------+ 
  10. | 網(wǎng)絡(luò)接口層 | 如網(wǎng)線,雙絞線,Wi-Fi | 
  11. +------------+-----------------------+ 

我們直接把 TCP/IP 四層協(xié)議 映射到 OSI七層協(xié)議 上看:

 

  1. +--------------+---------------+----------------+ 
  2. | OSI 七層協(xié)議 | 例如          | 對應(yīng)TCP/IP四層 | 
  3. +--------------+---------------+----------------+ 
  4. | 應(yīng)用層       | HTTP協(xié)議      |                | 
  5. +--------------+---------------+                | 
  6. | 表示層       |               | 應(yīng)用層         | 
  7. +--------------+---------------+                | 
  8. | 會(huì)話層       |               |                | 
  9. +--------------+---------------+----------------+ 
  10. | 傳輸層       | TCP           | 傳輸層         | 
  11. +--------------+---------------+----------------+ 
  12. | 網(wǎng)絡(luò)層       | IP            | 網(wǎng)際層         | 
  13. +--------------+---------------+----------------+ 
  14. | 數(shù)據(jù)鏈路層   | 因特網(wǎng),Wi-Fi |                | 
  15. +--------------+---------------+ 網(wǎng)絡(luò)接口層     | 
  16. | 物理層       | 雙絞線,光纜  |                | 
  17. +--------------+---------------+----------------+ 

接下來我們將從底層逐層向上來解析網(wǎng)絡(luò),最后我們將簡略的介紹TCP(TCP的知識(shí)足夠 寫好幾本書,一篇博客里遠(yuǎn)遠(yuǎn)介紹不完。不信可以看看TCP/IP協(xié)議詳解那三卷書加起來 有多厚)。

物理層

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

數(shù)據(jù)鏈路層

如果我們?nèi)ム]局寫一封信,填完收件人之后,郵局派發(fā)的順序可能是,先投遞到指定的 國家,然后投遞到具體的省,然后市。。。逐次投遞下去。那么我們玩電腦的時(shí)候,計(jì)算機(jī) 要怎么把A發(fā)給B的信息準(zhǔn)確送達(dá)呢?

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

協(xié)議,其實(shí)就是一種約定 :)

  • 最開始我們發(fā)送111表示信息開始
  • 然后,我們先有48個(gè)bit表示發(fā)送者的MAC地址,再有48個(gè)bit表示接受者的MAC地址
  • 之后,就是我們要發(fā)送的信息
  • 最后我們發(fā)送000表示結(jié)束,如果開頭和結(jié)尾不是這樣的,那么說明這是假的信息。

知道上面為啥手機(jī)叫 phoner 而不叫 phone 了嘛 :) 就是為了保證地指名長度一樣

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

這樣表示看起來可行,不過遇到一個(gè)問題,就是如果這一串二進(jìn)制中間就出現(xiàn)了000怎么辦? 因?yàn)橛?jì)算機(jī)讀取的時(shí)候是從頭開始讀的,這樣子計(jì)算機(jī)就會(huì)亂掉。

為了解決這個(gè)問題,我們修改一下協(xié)議,在111之后加上發(fā)送者地址+接受者地址+所要發(fā)送的 信息的長度。我們用 16個(gè)字節(jié)來表示,也就是說這中間不能發(fā)送多于 2 ** 16 個(gè)bit。

所以協(xié)議變成了:

  • 最開始我們發(fā)送111表示信息開始
  • 隨后我們用16個(gè)bit表示包的長度
  • 然后,我們先有48個(gè)bit表示發(fā)送者的MAC地址,再有48個(gè)bit表示接受者的MAC地址
  • 之后,就是我們要發(fā)送的信息
  • 最后我們發(fā)送000表示結(jié)束,如果開頭和結(jié)尾不是這樣的,那么說明這是假的信息。

發(fā)送者地址+接收者地址+hello的bit長度是 6 * 8 + 6 * 8 + 5 * 8 = 136,二進(jìn)制表示 為: 00000000 10001000

所以發(fā)送的整個(gè)信息變成了:

網(wǎng)絡(luò)層

現(xiàn)在我們終于可以發(fā)送信息了。不過有個(gè)缺點(diǎn),我們只能在相鄰的時(shí)候才可以發(fā)送信息, 那有沒有辦法可以借助兩兩傳遞,在不同的地方也發(fā)送信息呢?有,那就是我們的網(wǎng)絡(luò)層 也就是ip(我們能遇到的最通俗易懂的一個(gè)名詞了,暫時(shí)把它當(dāng)作網(wǎng)絡(luò)層的代名詞也不為過)。

剛剛我們已經(jīng)學(xué)會(huì)了一種技術(shù),就是分配一個(gè)地址,剛剛的叫做MAC地址,我們用來做 相鄰兩個(gè)節(jié)點(diǎn)的定位。其實(shí)這個(gè)地址也可以用來在多個(gè)節(jié)點(diǎn)之間找人,基于這樣一種 技術(shù):每個(gè)節(jié)點(diǎn)都知道和自己相鄰的節(jié)點(diǎn)的MAC地址,那么,比如這樣一種連接方式:

 

  1. A - B - C - E 
  2.  \     / 
  3.   - D - 
  • A向E發(fā)送消息,就可以這樣:
  • A向B和D發(fā)消息:給我發(fā)到E去
  • B和D接到之后發(fā)現(xiàn)來源是A,所以就只給C發(fā)消息:給我發(fā)到E去
  • C接到消息之后發(fā)現(xiàn)來源是B和D,所以就給E發(fā)消息:給我發(fā)到E去
  • E接到消息之后發(fā)現(xiàn)接收方是自己,所以就把消息吞了

你別說,這種方式好像真的行得通呢,除了有一個(gè)顯著的問題,A向E發(fā)送一份消息, 最后E收到了兩份,這個(gè)我們需要到后面進(jìn)行去重。我們先打上一個(gè)TODO的標(biāo)簽吧。

還有一個(gè)細(xì)節(jié)問題,不知道大家發(fā)現(xiàn)了么,剛才我們說過,MAC地址是相鄰兩個(gè)節(jié)點(diǎn) 通信用的,里面有來源地址和目標(biāo)地址,如果我們向上面這樣傳輸?shù)脑?,每個(gè)節(jié)點(diǎn)都 只是把里面的信息傳過去,但是來源地址卻改要改寫成自己的MAC地址,要不然的話, B就不知道信息是A發(fā)來的還是C發(fā)來的呀,對不對?那問題就來了,E要怎么知道信息 其實(shí)是從A發(fā)過來的呢?

沒辦法了,我們只好在傳輸?shù)男畔⒗锇颜嬲膩碓吹刂穼戇M(jìn)去,所以我們又定了一個(gè) 協(xié)議,我們管它叫做ip:

  • MAC攜帶的信息的開始,是來源的ip地址,32個(gè)bit表示
  • 然后是目標(biāo)的ip地址,32個(gè)bit表示
  • 然后是我要帶的信息

那和上面的數(shù)據(jù)鏈路層的協(xié)議合一下起來,假設(shè)來源地址是 192.168.1.1 ,目標(biāo)地址是 192.168.1.2 ,發(fā)送的信息還是 "hello",整個(gè)包就像這樣:

 

  1. 111(開始) 
  2. 00000000 11001000(長度) 
  3. 01110010 01101111 01110101 01110100 01100101 01110010(來源MAC地址) 
  4. 01110000 01101000 01101111 01101110 01100101 01110010(目標(biāo)MAC地址) 
  5. 11000000 10101000 00000001 00000001(來源ip地址) 
  6. 11000000 10101000 00000001 00000010(目標(biāo)ip地址) 
  7. 01101000 01100101 01101100 01101100 01101111(字符串"hello"
  8. 000(結(jié)束) 

這樣是不是就很科學(xué)?那必須的。哎呀,終于可以跨節(jié)點(diǎn)發(fā)送消息了,小開心~

可是還是有問題,如果我想確定A發(fā)的信息一定送達(dá)了E怎么辦?怎么提供可靠性?IP這一層 并不提供可靠性,只是說盡量送達(dá)。看來有必要再來一層!

傳輸層

我們知道,一臺(tái)計(jì)算機(jī)上可能有很多個(gè)程序在運(yùn)行,那怎么區(qū)分不同的程序呢?所以我們 給程序加上了id,叫做pid。那計(jì)算機(jī)網(wǎng)絡(luò)通信的時(shí)候怎么區(qū)分呢?又假設(shè)n個(gè)進(jìn)程想和另外 一臺(tái)機(jī)器上的某一個(gè)進(jìn)程通信呢?怎么辦?

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

我們繼續(xù)造協(xié)議,不過這一次我們想要這個(gè)協(xié)議賊可靠,所以要多做一些工作。其實(shí)要是 按照七層協(xié)議來實(shí)現(xiàn)的話,完全不必在這一層干這么多事情,不同的層干不同的事情嘛, 對不對。不過為了理解TCP協(xié)議,我們呀,也跟著來自己捏造一個(gè)協(xié)議,不如叫PCT好了。

繼續(xù),我們要在ip帶的信息里規(guī)定好我們這樣發(fā):

  • 首先是來源地址的端口號(hào),8個(gè)bit來表示,因?yàn)閕p里面已經(jīng)待了ip地址,我這里就不重復(fù)帶了
  • 然后是目標(biāo)地址的端口號(hào),8個(gè)bit來表示

這樣,簡單的PCT協(xié)議就做好了。

還有一個(gè)問題,就是我們要保證發(fā)出去的信息是有序的,因?yàn)榭赡苡械男畔⒆吖饫w, 有的信息走Wi-Fi,他們傳輸速率不一樣嘛。

所以我們在協(xié)議里這樣寫:

  • 首先是來源地址的端口號(hào),8個(gè)bit來表示,因?yàn)閕p里面已經(jīng)待了ip地址,我這里就不重復(fù)帶了
  • 然后是目標(biāo)地址的端口號(hào),8個(gè)bit來表示
  • 然后是這個(gè)包的序號(hào),8個(gè)bit來表示

但是我們說好了要把這個(gè)協(xié)議打造成一個(gè)可靠的協(xié)議,可不能食言。我想想,怎么讓他 可靠呢,無非就是我發(fā)一個(gè)信息,你告訴我你收到了,要是你不告訴我,我就發(fā)到你告訴我 為止。差不多就是這么個(gè)意思。但是呢,又不想構(gòu)造多個(gè)不同的協(xié)議,你知道,編程的時(shí)候 要是寫一堆的if-else樹那可就很蛋疼了。再改改協(xié)議:

  • 首先是來源地址的端口號(hào),8個(gè)bit來表示,因?yàn)閕p里面已經(jīng)待了ip地址,我這里就不重復(fù)帶了
  • 然后是目標(biāo)地址的端口號(hào),8個(gè)bit來表示
  • 然后是這個(gè)包的序號(hào),8個(gè)bit來表示
  • 然后是想確認(rèn)的包的序號(hào),8個(gè)bit來表示

咦,點(diǎn)睛之筆耶,這個(gè)確認(rèn)的包的序號(hào),因?yàn)槲覀兪请p向通信,我發(fā)他信息的時(shí)候還可以順便 確認(rèn)我收到了他的包啊,真是一箭雙雕。

TCP是一個(gè)面向流的協(xié)議,什么叫流?車流,水流,車流比較形象。車和車之間是分開的, 但是速度一快起來,就可以把它們看成連起來的。TCP也是這樣,單個(gè)包之間是分開的, 但是卻可以看作是連起來,為什么呢?因?yàn)槊總€(gè)包里都帶了ip地址和端口號(hào),ip地址和端口 號(hào)一樣的,就可以看作是連起來的 :)

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

 

  1. 111(開始) 
  2. 00000000 11101000(長度) 
  3. 01110010 01101111 01110101 01110100 01100101 01110010(來源MAC地址) 
  4. 01110000 01101000 01101111 01101110 01100101 01110010(目標(biāo)MAC地址) 
  5. 11000000 10101000 00000001 00000001(來源ip地址) 
  6. 11000000 10101000 00000001 00000010(目標(biāo)ip地址) 
  7. 00000001(來源的端口號(hào)) 
  8. 00000010(目標(biāo)的端口號(hào)) 
  9. 00000001(發(fā)送的包的序號(hào)是1) 
  10. 00000000(已經(jīng)確認(rèn)的包的序號(hào)是0,表示啥都沒有嘛) 
  11. 01101000 01100101 01101100 01101100 01101111(字符串"hello"
  12. 000(結(jié)束) 

duang,就這樣,我們構(gòu)建起了屬于自己的可靠的基于流的雙工的協(xié)議 :)

順便我們還完成了上面的TODO,通過序號(hào)我們就可以判斷這個(gè)包是不是重復(fù)了,哈哈哈, 一箭n雕~

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

應(yīng)用層

這下我們終于可以放心大膽的發(fā)送消息了,PCT協(xié)議是個(gè)負(fù)責(zé)任的協(xié)議,如果能送到,他就一定 會(huì)送到,并且是有序的,要是網(wǎng)絡(luò)壞掉了,實(shí)在連不上,他就會(huì)告訴我網(wǎng)絡(luò)連不上。

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

現(xiàn)在我想知道瀏覽器和服務(wù)器是怎么通信的。我們來看看百度。

 

  1. $ telnet www.baidu.com 80 
  2. Trying 183.232.231.173... 
  3. Connected to www.baidu.com. 
  4. Escape character is '^]'
  5. GET / HTTP/1.1 
  6.  
  7. HTTP/1.1 302 Moved Temporarily 
  8. Date: Sat, 12 Aug 2017 10:45:14 GMT 
  9. Content-Type: text/html 
  10. Content-Length: 215 
  11. Connection: Keep-Alive 
  12. Location: http://www.baidu.com/search/error.html 
  13. Server: BWS/1.1 
  14. X-UA-Compatible: IE=Edge,chrome=1 
  15. BDPAGETYPE: 3 
  16. Set-Cookie: BDSVRTM=0; path=/ 
  17.  
  18. <html> 
  19. <head><title>302 Found</title></head> 
  20. <body bgcolor="white"
  21. <center><h1>302 Found</h1></center> 
  22. <hr><center>pr-nginx_1-0-350_BRANCH Branch 
  23. Time : Tue Aug  8 20:41:04 CST 2017</center> 
  24. </body> 
  25. </html> 
  26. ^] 
  27. telnet>  
  28. Connection closed. 

輸入 GET / HTTP/1.1 之后回車,百度就給我返回了下面的一長串,然后瀏覽器再根據(jù) 返回的內(nèi)容進(jìn)行渲染,這又是一個(gè)大話題了,不講了不講了,收工 :)

責(zé)任編輯:未麗燕 來源: Github
相關(guān)推薦

2019-09-18 08:53:55

2020-01-06 11:22:06

TCPLinux內(nèi)核

2010-09-08 15:11:36

TCP IP協(xié)議棧

2010-06-08 13:32:19

TCP IP協(xié)議基礎(chǔ)

2010-06-08 14:23:47

TCP IP協(xié)議概念

2014-10-15 09:14:24

IP

2014-11-21 09:16:23

TCPIP

2020-12-03 08:37:38

TCPIPARP協(xié)議

2019-06-12 14:18:31

TCPIP協(xié)議DNS

2010-06-12 15:54:09

TCP IP協(xié)議

2010-06-18 14:37:20

TCP IP協(xié)議

2010-06-08 15:10:08

2010-09-17 16:38:41

TCP IP協(xié)議

2010-06-09 16:28:50

TCP IP傳輸協(xié)議

2010-06-13 14:49:40

TCP IP協(xié)議優(yōu)化

2019-09-18 20:07:06

AndroidTCP協(xié)議

2019-09-30 09:28:26

LinuxTCPIP

2010-09-08 15:34:27

TCP IP協(xié)議棧

2010-06-18 15:31:21

TCP IP協(xié)議簇

2010-06-08 13:50:40

TCP IP協(xié)議族
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)