TCP/IP 网络模型
- 应用层
最上层的,也是我们能直接接触到的就是应用层(Application Layer),我们电脑或手机使用的应用软件都是在应用层实现。那么,当两个不同设备的应用需要通信的时候,应用就把应用数据传给下一层,也就是传输层。
所以,应用层只需要专注于为用户提供应用功能,比如 HTTP、FTP、Telnet、DNS、SMTP等。不需要关心数据如何传输,只需要把数据进一步往下传给传输层;应用层是工作在操作系统中的用户态,传输层及以下则工作在内核态。
- 传输层
应用层的数据包会传给传输层,传输层(Transport Layer)是为应用层提供网络支持的。
在传输层会有两个传输协议,分别是 TCP 和 UDP。
- TCP 的全称叫传输控制协议(Transmission Control Protocol),大部分应用使用的正是 TCP 传输层协议,比如 HTTP 应用层协议。TCP 相比 UDP 多了很多特性,比如流量控制、超时重传、拥塞控制等,这些都是为了保证数据包能可靠地传输给对方。
- UDP 相对来说就很简单,简单到只负责发送数据包,不保证数据包是否能抵达对方,但它实时性相对更好,传输效率也高。当然,UDP 也可以实现可靠传输,把 TCP 的特性在应用层上实现就可以,不过要实现一个商用的可靠 UDP 传输协议,也不是一件简单的事情。
当传输层的数据包大小超过 MSS(TCP 最大报文段长度) ,就要将数据包分块,这样即使中途有一个分块丢失或损坏了,只需要重新发送这一个分块,而不用重新发送整个数据包。在 TCP 协议中,我们把每个分块称为一个 TCP 段(TCP Segment)。
当设备作为接收方时,传输层则要负责把数据包传给应用,但是一台设备上可能会有很多应用在接收或者传输数据,因此需要用一个编号将应用区分开来,这个编号就是端口。比如 80 端口通常是 Web 服务器用的,22 端口通常是远程登录服务器用的。
- 网络层
不希望传输层协议处理太多的事情,只需要服务好应用即可,让其作为应用间数据传输的媒介,帮助实现应用到应用的通信,而实际的传输功能就交给下一层,也就是网络层(Internet Layer)。
网络层最常使用的是 IP 协议(Internet Protocol),IP 协议会将传输层的报文作为数据部分,再加上 IP 包头组装成 IP 报文,如果 IP 报文大小超过 MTU(以太网中一般为 1500 字节)就会再次进行分片,得到一个即将发送到网络的 IP 报文。
网络层需要有区分设备的编号。一般用 IP 地址给设备进行编号,对于 IPv4 协议, IP 地址共 32 位,分成了四段(比如,192.168.100.1),每段是 8 位。IP 地址一般分为两种意义:网络号负责标识 IP 地址属于哪个「子网」;主机号标识同一「子网」下不同主机。这两者的区分方法需要配合子网掩码才能算出 IP 地址 的网络号和主机号。一般子网掩码就是 255.255.255.0,按位与运算即可得到网络号,取反后按位与运算,就可以得到主机号。寻址过程中,先匹配到同样的网络号才会去匹配主机号。
IP 协议还有另一个重要的能力就是路由。路由器寻址工作中,就是要找到目标地址的子网,找到后进而把数据包转发给对应的网络内。
总结:IP 协议的寻址作用是告诉我们去往下一个目的地该朝哪个方向走,路由则是根据「下一个目的地」选择路径。寻址更像在导航,路由更像在操作方向盘。
- 网络接口层
生成了 IP 头部之后,接下来要交给网络接口层(Link Layer)在 IP 头部的前面加上 MAC 头部,并封装成数据帧(Data frame)发送到网络上。
以太网就是一种在「局域网」内,把附近的设备连接起来,使它们之间可以进行通讯的技术。以太网在判断网络包目的地时和 IP 的方式不同,因此必须采用相匹配的方式才能在以太网中将包发往目的地,而 MAC 头部就是干这个用的,所以,在以太网进行通讯要用到 MAC 地址。MAC 头部是以太网使用的头部,它包含了接收方和发送方的 MAC 地址等信息,我们可以通过 ARP 协议获取对方的 MAC 地址。
网络接口层主要为网络层提供「链路级别」传输的服务,负责在以太网、WiFi 这样的底层网络上发送原始数据包,工作在网卡这个层次,使用 MAC 地址来标识网络上的设备。
TCP/IP 网络通常是由上到下分成 4 层,分别是应用层,传输层,网络层和网络接口层。
从网址到网页
HTTP
浏览器做的第一步工作就是要对 URL 进行解析,从而生成发送给 Web 服务器的请求信息。
当没有路径名时,就代表访问根目录下事先设置的默认文件。
URL 解析完成,就会生成 HTTP 请求消息。如下:
真实地址查询——DNS
在发送之前,还有一项工作需要完成,那就是查询服务器域名对应的 IP 地址,因为委托操作系统发送消息时,必须提供通信对象的 IP 地址。有一种服务器就专门保存了 Web 服务器域名与 IP 的对应关系,它就是 DNS 服务器。
DNS 中域名靠句点分割,句点代表不同层次的界限。越靠右则层级越高。一般域名最后还有一个点,代表根域名。他的层级关系类似树状结构。
- 根 DNS 服务器(.)
- 顶级域 DNS 服务器(.com)
- 权威 DNS 服务器(server.com)
解析流程:
- 客户端发送 DNS 请求,询问 IP,发给本地 DNS 服务器
- 本地 DNS 在缓存中寻找,找到就返回 IP;找不到就去问根域名服务器
- 根域名收到请求,会根据顶级域名,发送顶级域 DNS 给本地 DNS
- 本地收到后会再次发出请求
- 顶级域 DNS 解析找到权威 DNS 返回给本地 DNS
- 本地 DNS 询问权威 DNS
- 权威 DNS 查询后发送给本地 DNS
- 本地 DNS 返回给到客户端查询到的 IP 地址
流程图如下:
指南——协议栈
通过 DNS 获取到 IP 后,就可以把 HTTP 的传输工作交给操作系统中的协议栈。
协议栈内部有好几个部分,有上下关系。应用程序(浏览器)通过调用 Socket 库,来委托协议栈工作。协议栈的上半部分有两块,分别是负责收发数据的 TCP 和 UDP 协议,这两个传输协议会接受应用层的委托执行收发数据的操作。下面一半是用 IP 协议控制网络包收发操作,在互联网上传数据时,数据会被切分成一块块的网络包,而将网络包发送给对方的操作就是由 IP 负责的。
IP 中还包括 ICMP 协议和 ARP 协议。
- ICMP 用于告知网络包传送过程中产生的错误以及各种控制信息。
- ARP 用于根据 IP 地址查询相应的以太网 MAC 地址。
最后到了网卡驱动程序控制网卡硬件,最终完成实际的收发操作。
以上过程如下图所示:
可靠传输——TCP
源端口号和目标端口号是不可少的,如果没有这两个端口号,数据就不知道应该发给哪个应用。接下来有包的序号,这个是为了解决包乱序的问题。还有应该有的是确认号,目的是确认发出去对方是否有收到。如果没有收到就应该重新发送,直到送达,这个是为了解决丢包的问题。接下来还有一些状态位。例如 SYN 是发起一个连接,ACK 是回复,RST 是重新连接,FIN 是结束连接等。TCP 是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。还有一个重要的就是窗口大小。TCP 要做流量控制,通信双方各声明一个窗口(缓存大小),标识自己当前能够的处理能力。除了做流量控制以外,TCP还会做拥塞控制,对于真正的通路堵车不堵车,它无能为力,唯一能做的就是控制自己,也即控制发送的速度。
在 HTTP 传输数据之前,首先需要 TCP 建立连接,TCP 连接的建立,通常称为三次握手。超级重要,之后应该是有具体的文章学习的,这里就是简单过一遍。
简单而言的三次握手如下,通过这些操作保证双方均能发送和接收:
- 一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态。
- 然后客户端主动发起连接 SYN,之后处于 SYN-SENT 状态。
- 服务端收到发起的连接,返回 SYN,并且 ACK 客户端的 SYN,之后处于 SYN-RCVD 状态。
- 客户端收到服务端发送的 SYN 和 ACK 之后,发送对 SYN 确认的 ACK,之后处于 ESTABLISHED 状态,因为它一发一收成功了。
- 服务端收到 ACK 的 ACK 之后,处于 ESTABLISHED 状态,因为它也一发一收了。
如果 HTTP 请求消息比较长,超过了 MSS 的长度,这时 TCP 就需要把 HTTP 的数据拆解成一块块的数据发送,而不是一次性发送所有数据。
- MTU:网络包的最大长度,一般为 1500 字节
- MSS:除去 IP 和 TCP 后的数据长度
如果需要拆分,那就是把数据拆分,分别添加 IP 和 TCP 头部之后进行发送,就是完成切片操作。
TCP 协议里面会有两个端口,一个是浏览器监听的端口(通常是随机生成的),一个是 Web 服务器监听的端口(HTTP 默认端口号是 80, HTTPS 默认端口号是 443)。建立连接后,TCP 报文的数据就是存放 HTTP 头部和数据,组装好 TCP 报文后交给网络层处理。
远程定位——IP
HTTP 是经过 TCP 传输的,所以在 IP 包头的协议号,要填写为 06(十六进制),表示协议为 TCP。
如果客户端有多个网卡,就需要根据路由表规则判断选择网卡,可以使用 route -n 命令查看当前系统的路由表。通过与 Genmask 按位与运算计算并与 Destination 比对来找到匹配网卡的 IP 地址。
两点传输——MAC
生成了 IP 头部之后,接下来网络包还需要在 IP 头部的前面加上 MAC 头部。
在 MAC 包头里需要发送方 MAC 地址和接收方目标 MAC 地址,用于两点之间的传输。一般在 TCP/IP 通信里,MAC 包头的协议类型只使用:
- 0800 : IP 协议
- 0806 : ARP 协议
发送方的 MAC 地址获取就比较简单了,MAC 地址是在网卡生产时写入到 ROM 里的,只要将这个值读取出来写入到 MAC 头部就可以了。
接收方的 MAC 地址就有点复杂了,只要告诉以太网对方的 MAC 的地址,以太网就会帮我们把包发送过去,那么很显然这里应该填写对方的 MAC 地址。所以先得搞清楚应该把包发给谁,这个只要查一下路由表就知道了。在路由表中找到相匹配的条目,然后把包发给 Gateway 列中的 IP 地址就可以了。通过 ARP 协议来找到路由器 MAC 地址。ARP 协议在以太网中广播对所有设备发送 IP 地址匹配,若路由器匹配则会回发 MAC 地址,同时查询结果会存入 ARP 缓存中。
网络包数据现在如下所示:
出口——网卡
需要将数字信息转换为电信号,才能在网线上传输,也就是说,这才是真正的数据发送过程。负责执行这一操作的是网卡,要控制网卡还需要靠网卡驱动程序。网卡驱动获取网络包之后,会将其复制到网卡内的缓存区中,接着会在其开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列 FCS。
至此,网络包报文如下所示:
送别者——交换机
交换机的设计是将网络包原样转发到目的地。交换机工作在 MAC 层,也称为二层网络设备。
首先,电信号到达网线接口,交换机里的模块进行接收,接下来交换机里的模块将电信号转换为数字信号。然后通过包末尾的 FCS 校验错误,如果没问题则放到缓冲区。这部分操作基本和计算机的网卡相同,但交换机的工作方式和网卡不同。
计算机的网卡本身具有 MAC 地址,并通过核对收到的包的接收方 MAC 地址判断是不是发给自己的,如果不是发给自己的则丢弃;相对地,交换机的端口不核对接收方 MAC 地址,而是直接接收所有的包并存放到缓冲区中。因此,和网卡不同,交换机的端口不具有 MAC 地址。
接下来需要查询一下这个包的接收方 MAC 地址是否已经在 MAC 地址表中有记录了。交换机根据 MAC 地址表查找 MAC 地址,然后将信号发送到相应的端口。交换机的 MAC 地址表主要包含两个信息:设备的 MAC 地址以及该设备在交换机上的端口号。如果地址表中找不到,那就直接发送到所有端口,然后只有相应的接收者才接收包,而其他设备则会忽略这个包,且成功后会返回响应包,把地址写入 MAC 地址表。
特殊的,如果接收方 MAC 地址是一个广播地址,那么交换机会将包发送到除源端口之外的所有端口。以下两个属于广播地址:
- MAC 地址中的 FF:FF:FF:FF:FF:FF
- IP 地址中的 255.255.255.255
出境大门——路由器
网络包经过交换机之后,现在到达了路由器,并在此被转发到下一个路由器或目标设备。这一步转发的工作原理和交换机类似,也是通过查表判断包转发的目标。不过在具体的操作过程上,路由器和交换机是有区别的。
因为路由器是基于 IP 设计的,俗称三层网络设备,路由器的各个端口都具有 MAC 地址和 IP 地址;而交换机是基于以太网设计的,俗称二层网络设备,交换机的端口不具有 MAC 地址。
包接受:
首先,电信号到达网线接口部分,路由器中的模块会将电信号转成数字信号,然后通过包末尾的 FCS 进行错误校验。如果没问题则检查 MAC 头部中的接收方 MAC 地址,看看是不是发给自己的包,如果是就放到接收缓冲区中,否则就丢弃这个包。
完成包接收操作之后,路由器就会去掉包开头的 MAC 头部。接下来会根据 MAC 头部后方的 IP 头部中的内容进行包的转发操作。
转发操作分为几个阶段,首先是查询路由表判断转发目标。这个跟之前是类似的,通过 服务器 IP 地址和 GateWay 做按位与运算并判断是否与 Destination 符合,符合就发送,如果没有符合的就发送到默认路由 0.0.0.0。
接下来就会进入包的发送操作。需要根据路由表的网关列判断对方的地址。如果网关是一个 IP 地址,则这个IP 地址就是我们要转发到的目标地址,还未抵达终点,还需继续需要路由器转发。如果网关为空,则 IP 头部中的接收方 IP 地址就是要转发到的目标地址,也是就终于找到 IP 包头里的目标地址了,说明已抵达终点。
知道对方的 IP 地址之后,接下来需要通过 ARP 协议根据 IP 地址查询 MAC 地址,并将查询的结果作为接收方 MAC 地址。路由器也有 ARP 缓存,因此首先会在 ARP 缓存中查询,如果找不到则发送 ARP 查询请求。
接下来是发送方 MAC 地址字段,这里填写输出端口的 MAC 地址。还有一个以太类型字段,填写 0800 (十六进制)表示 IP 协议。网络包完成后,接下来会将其转换成电信号并通过端口发送出去。这一步的工作过程和计算机也是相同的。发送出去的网络包会通过交换机到达下一个路由器。由于接收方 MAC 地址就是下一个路由器的地址,所以交换机会根据这一地址将包传输到下一个路由器。
在网络包传输的过程中,源 IP 和目标 IP 始终是不会变的,一直变化的是 MAC 地址,因为需要 MAC 地址在以太网内进行两个设备之间的包传输。
服务器 与 客户端
服务器会先扒开数据包的 MAC 头部,查看是否和服务器自己的 MAC 地址符合,符合就将包收起来。接着继续扒开数据包的 IP 头,发现 IP 地址符合,根据 IP 头中协议项,知道自己上层是 TCP 协议。于是,扒开 TCP 的头,里面有序列号,需要看一看这个序列包是不是我想要的,如果是就放入缓存中然后返回一个 ACK,如果不是就丢弃。TCP头部里面还有端口号, HTTP 的服务器正在监听这个端口号。于是,服务器自然就知道是 HTTP 进程想要这个包,于是就将包发给 HTTP 进程。
服务器的 HTTP 进程看到,原来这个请求是要访问一个页面,于是就把这个网页封装在 HTTP 响应报文里。HTTP 响应报文也需要穿上 TCP、IP、MAC 头部,不过这次是源地址是服务器 IP 地址,目的地址是客户端 IP 地址。
总结
这里有几个小问题记录一下。
现在家里的路由器其实有了交换机的功能了,所以可能找不到交换机这个东西。
在发送数据包时,如果目标主机不是本地局域网,填入的 MAC 地址是路由器,也就是把数据包转发给路由器,路由器一直转发下一个路由器,直到转发到目标主机的路由器,发现 IP 地址是自己局域网内的主机,就会 ARP 请求获取目标主机的 MAC 地址,从而转发到这个服务器主机。
Linux 收发网络包
国际标准化组织制定了开放式系统互联通信参考模型(Open System Interconnection Reference Model),也就是 OSI 网络模型,该模型主要有 7 层,分别是应用层、表示层、会话层、传输层、网络层、数据链路层以及物理层。这个模型比较复杂,常用的就是四层的 TCP/IP 模型,LInux 也是这么实现网络协议栈的。
这里要注意,操作系统获知网络包到达的方式最简单的就是中断,但是网络包数量大就会导致中断频繁,影响操作系统效率。为了解决频繁中断带来的性能开销,Linux 内核在 2.6 版本中引入了 NAPI 机制,它是混合「中断和轮询」的方式来接收网络包,它的核心概念就是不采用中断的方式读取数据,而是首先采用中断唤醒数据接收的服务程序,然后 poll 的方法来轮询数据。
网络包到达后,就会通过 DMA 写入指定内存地址( Ring Buffer ,这个是一个环形缓冲区),然后网卡向 CPU 发起硬件中断,CPU 收到后调用对应的中断处理函数。硬件中断会首先屏蔽中断,并发起软中断;软中断会调用内核的 ksoftirqd 线程,轮询处理数据。ksoftirqd 线程会从 Ring Buffer 中获取一个数据帧,用 sk_buff 表示,从而可以作为一个网络包交给网络协议栈进行逐层处理。
具体的流程可以看这里:Linux 网络包收发
这里留意的点,数据会拷贝到 sk_buff 内存,层级传递会通过其中的 data 指针一步步添加协议栈中的各个头部信息,避免多次数据拷贝。一共拷贝三次:数据到 sk_buff;TCP 协议从传输层到网络层的 sk_buff 副本;IP 层 sk_buff 大于 MTU 申请额外 sk_buff。