从零构建自己的TCP/IP协议(一)

这篇博客是读完《图解TCP/IP协议》和《TCP/IP协议详解卷一:协议》之后的总结

我从0构建了一个可靠的双工的有序的基于流的协议,叫做PCT协议 :)

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

谈到计算机网络,就一定会说起OSI七层模型和TCP/IP四层模型,不过我们先从为何分层
说起。

为什么要分层

软件开发的过程中,我们经常听到的词语是"解耦","高内聚,低耦合"等等诸如此类的词语,又常听见写Java的同学念叨着"桥接模式","面向接口"等词语,那么他们说的这些词语的核心问题是什么呢?我们先从一个简单的问题看起:

现在我们需要做一个推送系统,要对接Android和iOS两个系统,大家都知道,Apple有统一的推送渠道,APNs,所以我们只要接入这个就好,但是Android的推送在国内是百家争鸣,就拿之前我为公司接入推送通知来举例,要接入极光,小米,可能要接入华为推送。

那我要怎么从具体的推送里抽象出来呢?运用面向对象的想法,我们很容易就能想到,我们有一个父类,叫 BasePush,他的子类就是具体的 MiPush, JPush, HMSPush。父类中有 push_by_idpush_by_tag 等方法,子类重写。这样我们在具体实现的时候实例化子类,并且调用对应的方法就好。这种思想其实就是面向接口编程,在Java中我们可以转变一下编程的写法,把继承变成接口。在Python中我们就可以直接脑补这种写法。用图来表示,纯粹面向对象的时候我们的想法是这样的:

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

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

def push(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"的话,那么就发送这样一串二进制(用换行分割,这样更容易看清楚):

111
01110010 01101111 01110101 01110100 01100101 01110010
01110000 01101000 01101111 01101110 01100101 01110010
01101000 01100101 01101100 01101100 01101111
000

这样表示看起来可行,不过遇到一个问题,就是如果这一串二进制中间就出现了000怎么办?因为计算机读取的时候是从头开始读的,这样子计算机就会乱掉。

为了解决这个问题,我们修改一下协议,在111之后加上发送者地址+接受者地址+所要发送的信息的长度。我们用 16个bit来表示,也就是说这中间不能发送多于 2 ** 16 个bit。

所以协议变成了:

  • 最开始我们发送111表示信息开始
  • 随后我们用16个bit表示包的长度
  • 然后,我们先有48个bit表示发送者的MAC地址,再有48个bit表示接受者的MAC地址
  • 之后,就是我们要发送的信息
  • 最后我们发送000表示结束,如果开头和结尾不是这样的,那么说明这是假的信息。

发送者地址+接收者地址+hello的bit长度是 6 * 8 + 6 * 8 + 5 * 8 = 136,二进制表示为: 00000000 10001000

所以发送的整个信息变成了:

111
00000000 10001000
01110010 01101111 01110101 01110100 01100101 01110010
01110000 01101000 01101111 01101110 01100101 01110010
01101000 01100101 01101100 01101100 01101111
000

网络层

现在我们终于可以发送信息了。不过有个缺点,我们只能在相邻的时候才可以发送信息,那有没有办法可以借助两两传递,在不同的地方也发送信息呢?有,那就是我们的网络层也就是ip(我们能遇到的最通俗易懂的一个名词了,暂时把它当作网络层的代名词也不为过)。

top Created with Sketch.