成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從零構建TCP/IP協議

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

從零構建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中我們就可以直接腦補這種寫法。 用圖來表示,純粹面向對象的時候我們的想法是這樣的:

從零構建 TCP/IP 協議

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

從零構建 TCP/IP 協議

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

 

  1. def push(pusher, id): 
  2.     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 表格其實挺好看的,最后渲染的時候因為寬字符的原因格式有點亂掉了,下同

  1. +------------+-----------------------+ 
  2. | 層         | 例如                  | 
  3. +------------+-----------------------+ 
  4. | 應用層     | HTTP協議              | 
  5. +------------+-----------------------+ 
  6. | 傳輸層     | TCP                   | 
  7. +------------+-----------------------+ 
  8. | 網絡互連層 | IP                    | 
  9. +------------+-----------------------+ 
  10. | 網絡接口層 | 如網線,雙絞線,Wi-Fi | 
  11. +------------+-----------------------+ 

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

 

  1. +--------------+---------------+----------------+ 
  2. | OSI 七層協議 | 例如          | 對應TCP/IP四層 | 
  3. +--------------+---------------+----------------+ 
  4. | 應用層       | HTTP協議      |                | 
  5. +--------------+---------------+                | 
  6. | 表示層       |               | 應用層         | 
  7. +--------------+---------------+                | 
  8. | 會話層       |               |                | 
  9. +--------------+---------------+----------------+ 
  10. | 傳輸層       | TCP           | 傳輸層         | 
  11. +--------------+---------------+----------------+ 
  12. | 網絡層       | IP            | 網際層         | 
  13. +--------------+---------------+----------------+ 
  14. | 數據鏈路層   | 因特網,Wi-Fi |                | 
  15. +--------------+---------------+ 網絡接口層     | 
  16. | 物理層       | 雙絞線,光纜  |                | 
  17. +--------------+---------------+----------------+ 

接下來我們將從底層逐層向上來解析網絡,最后我們將簡略的介紹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地址,那么,比如這樣一種連接方式:

 

  1. A - B - C - E 
  2.  \     / 
  3.   - 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",整個包就像這樣:

 

  1. 111(開始) 
  2. 00000000 11001000(長度) 
  3. 01110010 01101111 01110101 01110100 01100101 01110010(來源MAC地址) 
  4. 01110000 01101000 01101111 01101110 01100101 01110010(目標MAC地址) 
  5. 11000000 10101000 00000001 00000001(來源ip地址) 
  6. 11000000 10101000 00000001 00000010(目標ip地址) 
  7. 01101000 01100101 01101100 01101100 01101111(字符串"hello"
  8. 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。那我們發送這樣的包:

 

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

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

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

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

應用層

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

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

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

 

  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 之后回車,百度就給我返回了下面的一長串,然后瀏覽器再根據 返回的內容進行渲染,這又是一個大話題了,不講了不講了,收工 :)

責任編輯:未麗燕 來源: Github
相關推薦

2019-09-18 08:53:55

2020-01-06 11:22:06

TCPLinux內核

2010-09-08 15:11:36

TCP IP協議棧

2010-06-08 14:23:47

TCP IP協議概念

2010-06-08 13:32:19

TCP IP協議基礎

2014-11-21 09:16:23

TCPIP

2014-10-15 09:14:24

IP

2020-12-03 08:37:38

TCPIPARP協議

2019-06-12 14:18:31

TCPIP協議DNS

2010-06-12 15:54:09

TCP IP協議

2010-06-18 14:37:20

TCP IP協議

2010-06-08 15:10:08

2010-06-09 16:28:50

TCP IP傳輸協議

2010-09-17 16:38:41

TCP IP協議

2010-06-13 14:49:40

TCP IP協議優化

2019-09-18 20:07:06

AndroidTCP協議

2019-09-30 09:28:26

LinuxTCPIP

2010-09-08 15:34:27

TCP IP協議棧

2010-06-18 15:31:21

TCP IP協議簇

2010-06-08 13:50:40

TCP IP協議族
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 免费在线观看黄色av | 精品一区精品二区 | 中文字幕视频一区二区 | 国产福利在线 | 欧美精品网站 | 欧美激情精品久久久久久 | 在线观看中文字幕 | 国产第一区二区 | 最新国产精品 | 精品一二区 | 婷婷精品 | 亚洲天堂精品久久 | 人妖av| 欧美男人天堂 | 久久久久久久av | 亚洲国产91 | 99久久精品国产一区二区三区 | 成人av网站在线观看 | 中文在线视频 | 精品在线一区二区 | 日韩精品在线免费 | www.国产精 | 精区3d动漫一品二品精区 | 色射综合 | 日韩中文字幕在线视频 | 精品视频一区二区三区在线观看 | av毛片| 色婷婷综合久久久中字幕精品久久 | 亚洲九九色 | 国产精品欧美一区二区三区不卡 | 亚洲一区| 一级片av| 91精品国产色综合久久 | 久久国产欧美一区二区三区精品 | 操久久| 亚洲一区中文字幕 | 日韩高清在线观看 | 超碰地址| 在线观看免费毛片 | 国产精品久久久乱弄 | 亚洲九九精品 |