趣谈网络协议 - 答疑解惑第一期

这系列相关博客,参考 极客时间-趣谈网络协议

首先,感谢大家关注并在留言区写下近3000条留言。留言太多,没有及时回复,一是每周写三篇文章压力真的挺大的。为了保质保量的产出,晚上和周末的时间基本上都搭进去了。 二是很多人的留言非常有深度,水平很高,提的问题一两句话解释不清楚。

每一节结尾我基本都会留两个思考题,其中第一个问题是启发思考的,是对本节内容的延伸学习;第二个问题是为了引出下一节,下一节的内容其实就是答案。

所以我会回答一下每一节的第一个问题,并列出第一个同我的思路最相近的同学,并对留言中比较有代表性的问题,做一个统一的回答,顺便也实现之前要送知识图谱和奖励礼券的承诺。

当然,这并不能说明我的回答就是一定是正确的或者全面的,有很多同学的留言有非常大的信息量,甚至更广的思路,也对这些同学表示感谢。还有些同学指出了我的错误,也感谢你们。

第1讲 | 为什久要学习网络协议?

第1讲 | 为什久要学习网络协议?

课后思考题

当网络包到达一个城关的时候,可以通过路由表得到下一个城关的IP地址,直接通过IP地址找就可以了,为什么还要通过本地的MAC地址呢?
在这里插入图片描述
徐良红同学说的比较接近。在网络包里,有源IP地址和目标IP地址、源MAC地址和目标MAC地址。从路由表中取得下一跳的IP地址后,应该把这个地址放在哪里呢?如果放在目标IP地址里面,到了城关,谁知道最终的目标在哪里呢?所以要用MAC地址。

所谓的下一跳,看起来是IP地址,其实是要通过ARP得到MAC地址,将下—跳的MAC地址放在目标MAC地址里面。

留言问题

1. MAC地址可以修改吗?
在这里插入图片描述
在这里插入图片描述
我查了一下, MAC ( Media Access Control ,介质访问控制)地址,也叫硬件地址,长度是 48 比特( 6 字节),由 16 进制的数字组成,分为前 24 位和后 24 位。

前24位叫作 组织唯一标志符 (Organizationally Unique Identifer,OUI),是由IEEE的注册管理机构给不同厂家分配的代码,用于区分不同的厂家。后24位是厂家自己分配的,称为 扩展标识符 。同一个厂家生产的网卡中MAC地址后24位是不同的。

也就是说, MAC 本来设计为唯一性的,但是后来设备越来越多,而且还有虚拟化的设备和网卡,有很多工具可以修改,就很难保证不冲突了。但是至少应该保持一个局域网内是唯一的。

MAC 的设计,使得即便不能保证绝对唯一,但是能保证一个局域网内出现冲突的概率很小。这样,一台机器启动的时候,就能够在没有 IP 地址的情况下,先用 MAC 地址进行通信,获得 IP 地址。

好在 MAC 地址是工作在一个局域网中的,因而即便出现了冲突,网络工程师也能够在自己的范围内很快定位并解决这个问题。这就像我们生成 UUID 或者哈希值,大部分情况下是不会冲突的,但是如果碰巧出现冲突了,采取一定的机制解决冲突就好。

2. TCP重试有没有可能导致重复下单?

答案是不会的。这个在 TCP 那一节有详细的讲解。因为TCP层收到了重复包之后,TCP层自己会进行去重,发给应用层、HTTP层。还是一个唯一的下单请求,所以不会重复下单。

那什么时候会导致重复下单呢?因为网络原因或者服务端错误,导致 TCP 连接断了,这样会重新发送应用层的请求,也即 HTTP 的请求会重新发送一遍。

如果服务端设计的是无状态的,它记不住上一次已经发送了一次请求。如果处理不好,就会导致重复下单,这就需要服务端除了实现无状态,还需要根据传过来的订单号实现幂等,同一个订单只处理一次。

还会有的现象是请求被黑客拦截,发送多次,这在 HTTPS 层可以有很多种机制,例如通过 Timestamp 和 Nonce 随机数联合起来,然后做一个不可逆的签名来保证。

3. TCP报平安的包是原路返回吗?
在这里插入图片描述
谢谢语鬼同学的指正。这里的比喻不够严谨,容易让读者产生误会,这里的原路返回的意思是原样返回,也就是返回也是这个过程,不一定是完全一样的路径。

4. IP地址和MAC地址的关系?
在这里插入图片描述
芒果同学的理解非常准确,讲 IP 和 MAC 的关系 的时候说了这个问题。IP是有远程定位功能的,MAC是没有远程定位功能的,只能通过本地ARP的方式找到。

我个人认为,即便有了 IPv6 ,也不会改变当前的网络分层模式,还是 IP 层解决远程定位问题,只不过改成 IPv6 了,到了本地,还是通过 MAC 。

5. 如果最后一跳的时候,IP改变了怎么办?
在这里插入图片描述
对于 IP 层来讲,当包到达最后一跳的时候,原来的 IP 不存在了。比如网线拔掉了,或者服务器直接宕机了,则 ARP 就找不到了,所以这个包就会发送失败了。对于 IP 层的工作就结束了。

但是 IP 层之上还有 TCP 层, TCP 会重试的,包还是会重新发送,但是如果服务器没有启动起来,超过一定的次数,最终放弃。

如果服务器重启了, IP 还是原来的 IP 地址,这个时候 TCP 重新发送的一个包的时候, ARP 是能够得到这个地址的,因而会发到这台机器上来,但是机器上面没有启动服务端监听那个端口,于是会发送 ICMP 端口不可达。

如果服务器重启了,服务端也重新启动了,也在监听那个端口了,这个时候 TCP 的服务端由于是新的, Sequence Number 根本对不上,说明不是原来的连接,会发送 RST 。

那有没有可能有特殊的场景 Sequence Number 也能对的上呢?按照 Sequence Number 的生成算法,是不可能的。
但是有一个非常特殊的方式,就是虚拟机的热迁移,从一台物理机迁移到另外一台物理机, IP 不变, MAC 不变,内存也拷贝过去, Sequence Number 在内存里面也保持住了,在迁移的过程中会丢失一两个包,但是从 TCP 来看,最终还是能够连接成功的。

6. TCP层报平安,怎么确认浏览器收到呢?
TCP 报平安,只能保证 TCP 层能够收到,不保证浏览器能够收到。但是可以想象,如果浏览器是你写的一个程序,你也是通过 socket 编程写的,你是通过 socket ,建立一个 TCP 的连接,然后从这个连接里面读取数据,读取的数据就是 TCP 层确认收到的。

这个读取的动作是本地系统调用,大部分情况下不会失败的。如果读取失败呢,当然本地会报错,你的 socket 读取函数会返回错误,如果你是浏览器程序的实现者,你有两种选择,一个是将错误报告给用户,另一个是重新发送一次请求,获取结果显示给用户。

7. ARP协议属于哪一层?
在这里插入图片描述
ARP 属于哪个层,一直是有争议的。比如《 TCP/IP 详解》把它放在了二层和三层之间,但是既然是协议,只要大家都遵守相同的格式、流程就可以了,在实际应用的时候,不会有歧义的,唯一有歧义的是参加各种考试,让你做选择题, ARP 属于哪一层?平时工作中咱不用纠结这个。

第2讲|网络会层的真实含义是什久?

第2讲|网络会层的真实含义是什久?

课后思考题

如果你也觉得总经理和员工的比喻不恰当,你有更恰当的比喻吗?
在这里插入图片描述
在这里插入图片描述
我觉得,寄快递和寄信这两个比喻都挺好的。关键是有了封装和解封装的过程。有的同学举了爬楼,或者公司各层之间的沟通,都无法体现封装和解封装的过程。

留言问题

1. 为什么要分层?
在这里插入图片描述
是的,仅仅用复杂性来解释分层,太过牵强了。
在这里插入图片描述
其实这是一个架构设计的通用问题,不仅仅是网络协议的问题。一旦涉及到复杂的逻辑,或者软件需求需要经常变动,一般都会通过分层来解决问题。

假如我们将所有的代码都写在一起,但是产品经理突然想调整一下界面,这背后的业务逻辑变不变,那要不要一起修改呢?所以会拆成两层,把 UI 层从业务逻辑中分离出来,调用 API 来进行组合。 API 不变,仅仅界面变,是不是就不影响后台的代码了?

为什么要把一些原子的 API 放在基础服务层呢?将数据库、缓存、搜索引擎等,屏蔽到基础服务层以下,基础服务层之上的组合逻辑层、 API 层都只能调用基础服务层的 API ,不能直接访问数据库。

比如我们要将 Oracle 切换成 MySQL 。 MySQL 有一个库,分库分表成为 4 个库。难道所有的代码都要修改吗?当然只要把基础服务层屏蔽,提供一致的接口就可以了。

网络协议也是这样的。有的想基于 TCP ,自己不操心就能够保证到达;有的想自己实现可靠通信,不基于 TCP ,而使用 UDP 。一旦分了层就好办了,定制化后要依赖于下一层的接口,只要实现自己的逻辑就可以了。如果 TCP 的实现将所有的逻辑耦合在了整个七层,不用 TCP 的可靠传输机制都没有办法。

2.层级之间真实的调用方式是什么样的?
在这里插入图片描述
如果文中是一个逻辑图,这个问题其实已经到实现层面上来了,需要看 TCP/IP 的协议栈代码了。这里首先推荐一本书《深入理解 Linux 网络技术内幕》。

其实下层的协议知道上层协议的,因为在每一层的包头里面,都会有上一层是哪个协议的标识,所以不是一个回调函数,每一层的处理函数都会在操作系统启动的时候,注册到内核的一个数据结构里面,但是到某一层的时候,是通过判断到底是哪一层的哪一个协议,然后去找相应的处理函数去调用。

调用的大致过程我这里再讲一下。由于TCP比较复杂,我们以UDP为例子,其实发送的包就是一个sk_buf结构。这个在Socket那一节讲过。

int udp_send_skb(sruct sk_buf *skb, sruct fowi4 *f4)

接着, UDP 层会调用 IP 层的函数。

int ip_send_skb(sruct net *net, sruct sk_buf *skb)

然后, IP 层通过路由判断,最终将包发给下一层。

int ip_output(sruct net *net, sruct sock *sk, sruct sk_buf *skb)

发送的时候,要进行 ARP 。如果有 MAC ,则调用二层的函数, neigh 其实就是邻居系统,是二层的意思。

int neigh_output(sruct neighbour *n, sruct sk_buf *skb)

接收的时候,会调用这里的接收函数。

int netif_receive_skb(sruct sk_buf *skb)

这个函数会根据是 ARP 或者 IP 等,选择调用不同的函数。如果是 IP 协议的话,就调用这里的函数。

int ip_rcv(sruct sk_buf *skb, sruct net_device *dev, sruct packet_type *pt, sruct net_device *orig_dev)

这里也有路由判断。如果是本地的,则继续往上提交这个结构。

int ip_local_deliver(sruct sk_buf *skb)

接着,还是根据 IP 头里面的协议号,来判断是什么协议,从而调用什么函数。下面这个是对 UDP 的调用。

int udp_rcv(sruct sk_buf *skb)

3. 什么情况下会有下层没上层?
在这里插入图片描述
有时候我们自己写应用的时候,不一定是直接调用应用层协议的接口,例如 HTTP 等,而是自己写 Socket 编程,来约定应用层的协议。再如, ping 也是一个应用,但是它没有用传输层的协议,而是用了 ICMP 的协议。

发布了39 篇原创文章 · 获赞 1 · 访问量 6109

猜你喜欢

转载自blog.csdn.net/aha_jasper/article/details/105576274