从零构建TCP/IP协议

从零构建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中我们就可以直接脑补这种写法。 用图来表示,纯粹面向对象的时候我们的想法是这样的:

如果我们把上面的图倒过来,就变成了面向接口:

在使用面向接口之后,我们就是做了这样一种假设:


  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 之后回车,百度就给我返回了下面的一长串,然后浏览器再根据 返回的内容进行渲染,这又是一个大话题了,不讲了不讲了,收工 :)

作者:佚名

来源:51CTO

时间: 2024-11-02 12:23:33

从零构建TCP/IP协议的相关文章

《趣学CCNA——路由与交换》一第2章 TCP/IP协议2.1 TCP协议简介

第2章 TCP/IP协议 趣学CCNA--路由与交换 在上一章,我们郑重其事地介绍了无聊的OSI七层参考模型,并浓墨重彩地讲述了其中每一层负责提供的功能.OSI模型出身名门.条理清晰,只有一个"小小的"缺点,那就是一直没人太拿它当回事儿.所以,如果对它太认真,你就败了. 我们是有职业精神的,因此在介绍OSI模型时反复强调了这个模型是如何地曲高和寡.我们在上一章中花大篇幅介绍OSI模型有三个目的:一是延续各类技术教材的惯例,以免将本书作为技术开蒙读物的读者在与别人讨论技术问题时,因全然不

TCP/IP协议 详解

Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议.Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成.TCP/IP 定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准.协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求.通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到

技术往事:改变世界的TCP/IP协议

1.前言 作为应用层开发人员,接触最多的网络协议通常都是传输层的TCP(与之同处一层的另一个重要协议是UDP协议),但对于IP协议,对于应用程序员来说更多的印象还是IP地址这个东西,再往深一点也就很难说的清楚. 本文将简要回故TCP/IP协议的过去.简单介绍TCP/IP协议族的关系,并与大家一起直观地分享由TCP/IP协议族所构建的虚拟网络与真实世界的"连接"情况. 2.TCP/IP协议简介 互联网协议族(英语:Internet Protocol Suite,缩写为IPS),是一个网络

透视你的网络 完美测试TCP/IP协议简介

安装网络硬件和网络协议之后,我们一般要进行TCP/IP协议的测试工作,那么怎样测试才算是比较全面的测试呢?我们认为,全面的测试应包括局域网和互联网两个方面,因此应从局域网和互联网两个方面测试,以下是我们在实际工作中利用命令行测试TCP/IP配置的步骤: 1. 单击"开始"/"运行",输入CMD按回车,打开命令提示符窗口. 2. 首先检查IP地址.子网掩码.默认网关.DNS服务器地址是否正确,输入命令ipconfig /all,按回车.此时显示了你的网络配置,观查是否

通过配置TCP/IP协议的方式使用DNS

本文描述了如何在Windows XP中通过配置TCP/IP协议的方式使用域名服务(DNS). DNS是一种用以将域名转换为IP地址的Internet服务.在Internet上,当您在网络操作过程中使用域名时,DNS服务负责将该域名转换为对应的IP地址.举例来说,域名www.reskit.com可能被转换为IP地址178.145.135.6. 在企业网络环境中,你可以对Windows XP进行适当配置,以便使其自动检测域控制器所使用的IP地址.此外,您也可以手工配置域控制器IP地址.下面将具体描述

[SQL]对于“无法用TCP/IP协议和远端SQL Server数据库连接”问题

server|数据|数据库|数据库连接|问题 对于"无法用TCP/IP协议和远端SQL Server数据库连接"问题 首先,需要确定您的SQL Server版本,以及它安装了哪些补丁,这很重要.如果补丁没有安装或者没有安装最新的ServicePack,请先安装. 然后,你可以试试看telnet SqlServerName 1433,看是否可以连接上.1433是SQl Server用TCP/IP协议的默认端口.你如果连这个端口都无法连接上,那就应该查查网络配置了. 第3,您得到的错误描述

XP系统下如何安装TCP/IP协议?

  XP系统下如何安装TCP/IP协议? 方法一 单击"开始"菜单,弹出的列表单击"控制面板"项 在"控制面板"里找到并双击打开"网络连接" 在"本地连接"上鼠标右键,单击"属性" 进入"常规"选项卡界面,单击"安装" 在选择网络组件类型列表中选中"协议",单击"添加" 在选择网络协议界面中,单击"

怎样解决网络无法链接TCP/IP协议变灰色

  1.使用注册表来设置 步骤1.开始--运行--regedit.exe,打开注册表编辑器,删除以下两个键: HKEY_LOCAL_MACHINESystemCurrentControlSetServicesWinsock HKEY_LOCAL_MACHINESystemCurrentControlSetServicesWinsock2 步骤2.用记事本打开%winroot%infnettcpip.inf文件,找到:[MS_TCPIP.PrimaryInstall]Characteristics

XP系统怎么重置TCP/IP协议?

  XP系统怎么重置TCP/IP协议?           具体方法: 1.单击"开始". 2.选择"运行". 3.在运行框里输入"CMD"后单击"确定". 4.在命令行模式输入命令 netsh int ip reset C:resetlog.txt (其中,Resetlog.txt记录命令结果的日志文件,一定要指定,这里指定了Resetlog.txt 日志文件及完整路径.) 5.运行结果可以查看C:resetlog.txt(