You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

212 lines
13 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 协议专栏特别福利 | 答疑解惑第一期
你好,我是刘超。
首先感谢大家关注并在留言区写下近3000条留言。留言太多没有及时回复一是每周写三篇文章压力真的挺大的。为了保质保量地产出晚上和周末的时间基本上都搭进去了。二是很多人的留言非常有深度水平很高提的问题一两句话解释不清楚。
每一节结尾我基本都会留两个思考题,其中第一个问题是启发思考的,是对本节内容的延伸学习;第二个问题是为了引出下一节,下一节的内容其实就是答案。
所以我会回答一下每一节的第一个问题,并列出第一个同我的思路最相近的同学,并对留言中比较有代表性的问题,做一个统一的回答,顺便也实现之前要送知识图谱和奖励礼券的承诺。
当然,这并不能说明我的回答就是一定是正确的或者全面的,有很多同学的留言有非常大的信息量,甚至更广的思路,也对这些同学表示感谢。还有些同学指出了我的错误,也感谢你们。
### [《第1讲 | 为什么要学习网络协议?》](https://time.geekbang.org/column/article/7581)
### 课后思考题
当网络包到达一个城关的时候,可以通过路由表得到下一个城关的 IP 地址,直接通过 IP地址找就可以了为什么还要通过本地的MAC地址呢
![](https://static001.geekbang.org/resource/image/16/69/16e0b76fe90ce10d8e5c16cad0010e69.png)
徐良红同学说的比较接近。在网络包里有源IP地址和目标IP地址、源MAC地址和目标MAC地址。从路由表中取得下一跳的IP地址后应该把这个地址放在哪里呢如果放在目标IP地址里面到了城关谁知道最终的目标在哪里呢所以要用MAC地址。
所谓的下一跳看起来是IP地址其实是要通过ARP得到MAC地址将下一跳的MAC地址放在目标MAC地址里面。
### 留言问题
1.MAC地址可以修改吗
![](https://static001.geekbang.org/resource/image/b3/1b/b3591518b266a3558f9dd61061c6271b.png)
![](https://static001.geekbang.org/resource/image/3a/5d/3abde3f5607b0278cc87ccfadb88875d.png)
我查了一下MACMedia Access Control介质访问控制地址也叫硬件地址长度是48比特6字节由16进制的数字组成分为前24位和后24位。
前24位叫作**组织唯一标志符**Organizationally Unique IdentifierOUI是由IEEE的注册管理机构给不同厂家分配的代码用于区分不同的厂家。后24位是厂家自己分配的称为**扩展标识符**。同一个厂家生产的网卡中MAC地址后24位是不同的。
也就是说MAC本来设计为唯一性的但是后来设备越来越多而且还有虚拟化的设备和网卡有很多工具可以修改就很难保证不冲突了。但是至少应该保持一个局域网内是唯一的。
MAC的设计使得即便不能保证绝对唯一但是能保证一个局域网内出现冲突的概率很小。这样一台机器启动的时候就能够在没有IP地址的情况下先用MAC地址进行通信获得IP地址。
好在MAC地址是工作在一个局域网中的因而即便出现了冲突网络工程师也能够在自己的范围内很快定位并解决这个问题。这就像我们生成UUID或者哈希值大部分情况下是不会冲突的但是如果碰巧出现冲突了采取一定的机制解决冲突就好。
2.TCP重试有没有可能导致重复下单
![](https://static001.geekbang.org/resource/image/ce/71/ced92fc92921a47918704f1657fb2771.png)
答案是不会的。这个在[TCP](https://time.geekbang.org/column/article/8975)那一节有详细的讲解。因为TCP层收到了重复包之后TCP层自己会进行去重发给应用层、HTTP层。还是一个唯一的下单请求所以不会重复下单。
那什么时候会导致重复下单呢因为网络原因或者服务端错误导致TCP连接断了这样会重新发送应用层的请求也即HTTP的请求会重新发送一遍。
如果服务端设计的是无状态的,它记不住上一次已经发送了一次请求。如果处理不好,就会导致重复下单,这就需要服务端除了实现无状态,还需要根据传过来的订单号实现幂等,同一个订单只处理一次。
还会有的现象是请求被黑客拦截发送多次这在HTTPS层可以有很多种机制例如通过 Timestamp和Nonce随机数联合起来然后做一个不可逆的签名来保证。
3.TCP报平安的包是原路返回吗
![](https://static001.geekbang.org/resource/image/36/ac/361fee74d932bac74f0b26bb280bf2ac.png)
谢谢语鬼同学的指正。这里的比喻不够严谨,容易让读者产生误会,这里的原路返回的意思是原样返回,也就是返回也是这个过程,不一定是完全一样的路径。
4.IP地址和MAC地址的关系
![](https://static001.geekbang.org/resource/image/9c/e3/9c359d2c20ab3f77f5a7e87dcb6fd7e3.png)
芒果同学的理解非常准确,讲[IP和MAC的关系](https://time.geekbang.org/column/article/7772)的时候说了这个问题。IP是有远程定位功能的MAC是没有远程定位功能的只能通过本地ARP的方式找到。
我个人认为即便有了IPv6也不会改变当前的网络分层模式还是IP层解决远程定位问题只不过改成IPv6了到了本地还是通过MAC。
5.如果最后一跳的时候IP改变了怎么办
![](https://static001.geekbang.org/resource/image/08/c1/08029e8c3dce6413558c4819b380c5c1.png)
对于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层报平安怎么确认浏览器收到呢
![](https://static001.geekbang.org/resource/image/f3/48/f39ceca92d67db24b42ef9d31f9bff48.png)
TCP报平安只能保证TCP层能够收到不保证浏览器能够收到。但是可以想象如果浏览器是你写的一个程序你也是通过socket编程写的你是通过socket建立一个TCP的连接然后从这个连接里面读取数据读取的数据就是TCP层确认收到的。
这个读取的动作是本地系统调用大部分情况下不会失败的。如果读取失败呢当然本地会报错你的socket读取函数会返回错误如果你是浏览器程序的实现者你有两种选择一个是将错误报告给用户另一个是重新发送一次请求获取结果显示给用户。
7.ARP协议属于哪一层
![](https://static001.geekbang.org/resource/image/e7/9f/e77d85e7260df7611b90a45b7e77ce9f.png)
ARP属于哪个层一直是有争议的。比如《TCP/IP详解》把它放在了二层和三层之间但是既然是协议只要大家都遵守相同的格式、流程就可以了在实际应用的时候不会有歧义的唯一有歧义的是参加各种考试让你做选择题ARP属于哪一层平时工作中咱不用纠结这个。
## [《第2讲 | 网络分层的真实含义是什么?》](https://time.geekbang.org/column/article/7724)
### 课后思考题
如果你也觉得总经理和员工的比喻不恰当,你有更恰当的比喻吗?
![](https://static001.geekbang.org/resource/image/21/27/21cd2dba241413bcfc5bb74fc8dd2527.png)
![](https://static001.geekbang.org/resource/image/3b/3e/3b69f685c893bfd6746ef03c395e8e3e.png)
我觉得,寄快递和寄信这两个比喻都挺好的。关键是有了封装和解封装的过程。有的同学举了爬楼,或者公司各层之间的沟通,都无法体现封装和解封装的过程。
### 留言问题
1.为什么要分层?
![](https://static001.geekbang.org/resource/image/4d/92/4d6c7f391ca5a55fdde3dbf62d69ab92.png)
是的,仅仅用复杂性来解释分层,太过牵强了。
![](https://static001.geekbang.org/resource/image/85/78/856b8040cd0a9f2206ad6f27ad3e9078.png)
其实这是一个架构设计的通用问题,不仅仅是网络协议的问题。一旦涉及到复杂的逻辑,或者软件需求需要经常变动,一般都会通过分层来解决问题。
假如我们将所有的代码都写在一起但是产品经理突然想调整一下界面这背后的业务逻辑变不变那要不要一起修改呢所以会拆成两层把UI层从业务逻辑中分离出来调用API来进行组合。API不变仅仅界面变是不是就不影响后台的代码了
为什么要把一些原子的API放在基础服务层呢将数据库、缓存、搜索引擎等屏蔽到基础服务层以下基础服务层之上的组合逻辑层、API层都只能调用基础服务层的API不能直接访问数据库。
比如我们要将Oracle切换成MySQL。MySQL有一个库分库分表成为4个库。难道所有的代码都要修改吗当然只要把基础服务层屏蔽提供一致的接口就可以了。
网络协议也是这样的。有的想基于TCP自己不操心就能够保证到达有的想自己实现可靠通信不基于TCP而使用UDP。一旦分了层就好办了定制化后要依赖于下一层的接口只要实现自己的逻辑就可以了。如果TCP的实现将所有的逻辑耦合在了整个七层不用TCP的可靠传输机制都没有办法。
2.层级之间真实的调用方式是什么样的?
![](https://static001.geekbang.org/resource/image/95/57/95510f25eb463c4302fb58bd518a8957.png)
如果文中是一个逻辑图这个问题其实已经到实现层面上来了需要看TCP/IP的协议栈代码了。这里首先推荐一本书《深入理解Linux网络技术内幕》。
其实下层的协议知道上层协议的,因为在每一层的包头里面,都会有上一层是哪个协议的标识,所以不是一个回调函数,每一层的处理函数都会在操作系统启动的时候,注册到内核的一个数据结构里面,但是到某一层的时候,是通过判断到底是哪一层的哪一个协议,然后去找相应的处理函数去调用。
调用的大致过程我这里再讲一下。由于TCP比较复杂我们以UDP为例子其实发送的包就是一个sk\_buff结构。这个在[Socket](https://time.geekbang.org/column/article/9293)那一节讲过。
```
int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4)
```
接着UDP层会调用IP层的函数。
```
int ip_send_skb(struct net *net, struct sk_buff *skb)
```
然后IP层通过路由判断最终将包发给下一层。
```
int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb)
```
发送的时候要进行ARP。如果有MAC则调用二层的函数neigh其实就是邻居系统是二层的意思。
```
int neigh_output(struct neighbour *n, struct sk_buff *skb)
```
接收的时候,会调用这里的接收函数。
```
int netif_receive_skb(struct sk_buff *skb)
```
这个函数会根据是ARP或者IP等选择调用不同的函数。如果是IP协议的话就调用这里的函数。
```
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
```
这里也有路由判断。如果是本地的,则继续往上提交这个结构。
```
int ip_local_deliver(struct sk_buff *skb)
```
接着还是根据IP头里面的协议号来判断是什么协议从而调用什么函数。下面这个是对UDP的调用。
```
int udp_rcv(struct sk_buff *skb)
```
3.什么情况下会有下层没上层?
![](https://static001.geekbang.org/resource/image/f6/c3/f6414085ec89ba0dfae79436aad2b2c3.png)
有时候我们自己写应用的时候不一定是直接调用应用层协议的接口例如HTTP等而是自己写Socket编程来约定应用层的协议。再如ping也是一个应用但是它没有用传输层的协议而是用了ICMP的协议。
* * *
最后感谢留言次数前15名的同学谢谢你们持之以恒的学习相信你们一定有自己的收获。统计数据截止到2018年8月8日
![](https://static001.geekbang.org/resource/image/ca/91/caeec16c4e959e250da1db6c6543b791.jpg)
同时感谢第1讲、第2讲中对内容有深度思考和提出问题的同学。我会为你们送上奖励礼券和知识图谱。稍后运营同学会发送短信通知。
欢迎你继续提问!
![](https://static001.geekbang.org/resource/image/00/5b/0057c2d7c99924366c94c5ed58e3385b.jpg)