1.常规的网络交互过程是从客户端发起网络请求,用户态的应用程序(浏览器)会生成 HTTP 请求报文、并通过 DNS 协议查找到对应的远端 IP 地址。
2.在套接字生成之后进入内核态,浏览器会委托操作系统内核协议栈中的上半部分,也就是 TCP/UDP 协议发起连接请求。
3.然后经由协议栈下半部分的 IP 协议进行封装,使数据包具有远程定位能力。
4.经过 mac 层处理,找到接收方的目标 MAC 地址。
5.最终数据包在经过网卡转化成电信号经过交换机、路由器发送到服务端,服务端经过处理拿到数据,再通过各种网络协议把数据响应给客户端。
6.客户端拿到数据进行渲染
7.客户端和服务端之间反复交换数据,客户端的页面数据就会发生变化。刚才的过程中,提到了多个层级和网络协议,那么网络为什么要分层呢?网络协议又是什么?
在计算机网络时代初期,各大厂商推出了不同的网络架构和标准,为统一标准,国际标准化组织 ISO 推出了统一的 OSI 参考模型。
当前网络主要遵循的 IEEE 802.3 标准,就是基于 OSI 模型提出的,主要定义的是物理层和数据链路层有线物理数据流传输的标准。
那网络为什么要分层?
通过分层处理简化问题难度,降低复杂度,由于分层后的各层之间相互独立,我们可以把大问题分割成小问题。同样,分层也保证了网络的松耦合和相对的灵活,分层拆分后易于各层的实现和维护,也方便了各层的后续扩展。网络分层解决了网络复杂的问题,在网络中传输数据中,我们对不同设备之间的传输数据的格式,需要定义一个数据标准,所以就有了网络协议。网络协议是双方通信的一种约定,以便双方都可以理解对方的信息。接下来我们就用 OSI 协议体系中广泛应用的 TCP/IP 层的体系结构来分析整个过程,需要重点关注数据处理的过程和网络协议
ISO_OSI通信流转示意图:
先来看下网络应用层的工作流程,依然以浏览器中输入URL开始。
在浏览器中输入URL,浏览器会根据输入内容,先匹配对应的URL以及关键字,给出输入建议,同时校验URL的合法性,并且会在URL前后补全URL
以输入 cosmos.com 为例,首先浏览器会判断出这是一个合法的 URL,并且会补全为 http://www.cosmos.com。
其中 http 为协议,cosmos.com 为网络地址,每个网络栏的地址都符合通用 URI 的语法。URI 一般语法由五个分层序列组成。后面的第一行内容我给你列了 URL 的格式,第二行做了行为说明。
URI = scheme:[//authority]path[?query][#fragment]
URI = 方案:[//授权]路径[?查询][#片段ID]
接着,浏览器从 URL 中会提取出网络的地址,也叫做主机名(host),一般主机名可以为域名或 IP 地址,此处使用域名。
对 URL 进行解析之后,浏览器确定了服务器的主机名和请求路径,接下来就是根据这些信息来生成 HTTP 请求消息了,但此时并未将HTTP请求发送出去
浏览器在 HTTP 报文生成完成后,它并不是马上就开始网络请求的。
在请求发出之前,浏览器首先会检查保存在本地计算机中的缓存,如果访问过当前的 URL,会先进入缓存中查询是否有要请求的文件。此时存在的缓存有路由器缓存、DNS 缓存、浏览器缓存、Service Worker、Memory Cache、Disk Cache、Push Cache、系统缓存等。
如果在浏览器缓存里没有命中缓存,浏览器会做一个系统调用获得系统缓存中的记录,就是我们的 gethostbyname 方法,它的作用是通过域名获取 IP 地址。这个方法会返回如下结构。
struct hostent
{
char *h_name;// 主机的别名.www.cosmos.com就是google他自己的别名
char **h_aliases;// 主机ip地址的类型,到底是ipv4(AF_.NET),还是pv6(AF_INET6)
int h_addrtype;// 主机ip地址的长度
int h_length;// 主机ip地址的长度
char **h_addr_list; // 主机的ip地址,注意,这个是以网络字节序存储的
#define h_addr h_addr_list[0] 这个函数,是将类型为af的网络地址结构src,转换成主机序的字符串形式,存放在长度为cnt的字符串中。返回指向dst的一个指针。如果函数调用错误,返回值是NULL
};
如果没有访问过当前的 URL,就会跳过缓存这一步,这时我们就会进入网络操作了。
接着上一小节在浏览器确认了输入的 URL 之前没有访问,浏览器就会生成对应的 HTTP 请求,这时浏览器需要委托操作系统将 HTTP 报文发送到对应的服务端。在发送消息之前,还有一个工作需要做,就是查找服务端的 IP 地址,因为操作系统在发送消息时,必须知道对方的 IP 地址才可以发送。
但是由于 IP 地址由一串数字组成,不够语义化,为方便你记忆,我们将 IP 地址映射为域名,于是就有这样一个服务,维护了 IP 和域名的映射关系,它就是非常重要的基础设施——DNS 服务器。DNS 服务器是一个分布式数据库,分布在世界各地。
为提高效率,DNS 是按照一定的结构进行组织的,不同层次之间按照英文句点. 来分割。
在域名中,我们的层级关系是按照从左到右、从低到高排列的,不同层级由低到高维护了一个树形结构,最高一级的根节点为 root 节点,就是我们所谓的根域名服务器,因此 cosmos.com 完整的域名应该是 cosmos.com.,后面的 . 相当于.root。
但是所有域名的顶级域名都一样,因此被省略;再下一级.com 为顶级域名;再下一级的 cosmos 为权威域名。
因为这是一个树形结构,所以客户端只要请求到一个 DNS 服务器,就可以一层层递归和迭代查找到所有的 DNS 服务器了。按照由高到低的优先级,DNS 域名解析的过程排列如下。
DNS解析 > 浏览器DNS缓存 > hosts文件 > 本地DNS服务器 > ISP DNS服务器
已经根据 URL 拿到需要请求的唯一地址了,接下来就要委托操作系统将 HTTP 报文发送出去了,这个过程由操作系统中的协议栈负责处理。
TCP/IP 协议栈是现在使用最广泛的网络协议栈,Internet 就是建立在 TCP/IP 协议栈基础上的。除 TCP/IP 协议栈外,我们的操作系统内核可以支持多个不同的协议栈,如后续我们将会用到的 LwIp。
协议栈内部分为几部分,分别承担着不同的作用。
协议栈的上半部分负责和应用层通过套接字(Socket)进行交互,它可以是 TCP 协议或 UDP 协议。应用层会委托协议栈的上部分完成收发数据的工作
协议栈的下半部分则负责把数据发送给到指定方的 IP 协议,由 IP 协议连接下层的网卡驱动。
浏览器通过 DNS 解析拿到 Cosmos 的 IP 地址后, 浏览器取出 URL 的端口(HTTP 默认 80,HTTPS 默认 443)。随即浏览器会委托操作系统协议栈的上半部分创建新的套接字(Socket)向对应的 IP 发起 TCP 连接请求。
为了确保通信的可靠性,建立 TCP 首先会先进行三次握手的操作,可结合下面的图示理解。
那么 TCP 的三次握手操作,是如何进行的呢?具体的操作步骤如下。
1.首先浏览器作为客户端会发送一个小的 TCP 分组,这个分组设置了一个特殊的 SYN 标记,用来表示这是一条连接请求。同时设置初始序列号为 x 赋值给 Seq (这次捕获组的数据为: SYN=1, Seq=1)。
2.服务器接受到客户端的 SYN 连接后,会选择服务器初始序号 y。同时向客户端发送含有连接确认(SYN+ACK)、Seq=0(本例中的服务器初始序号)、Ack=1(客户端的序号 x +1)等信息的 TCP 分组。
3.客户端收到了服务器的确定字段后,向服务器发送带有 ACK=1、Seq=1 (x+1)、Ack=1 (服务器 Ack 信息的拷贝)等字段的 TCP 分组给服务器。即使是发送一个 TCP 分组,也是一次网络通信,那么对于 TCP 层来说,这一次通信的数据前面就要包含一个 TCP 包头,向下层表明这是个 TCP 数据包。TCP 包头其实是一个数据结构,我为你准备了一幅图,以便理解。
下图就是 TCP 的包头,对于 TCP 头部来说,以下几个字段是很重要的,你要格外关注。
TCP包头图示:
首先,源端口号(Source port)和目标端口号(Destinantion port)是不可少的,如果没有这两个端口号,数据就不知道应该发给哪个应用。
其次,你需要注意的是一串有序数字 Sequence number,这个序号保证了 TCP 报文是有序被接受的,解决网络包的乱序问题。
之后的 Acknowledgement number 是确认号,只有对方确认收到,否则会一直重发,这个是防止数据包丢失的。
紧接着还有一些状态位,由于 TCP 是有状态的,是用于维护双方连接的状态,状态发生变更会更新双方的连接状态。后面还有一个,窗口大小 Window Size,用于流量控制。
TCP 层封装好了数据包,会将这个 TCP 数据包向下层发送,而 TCP 层的下层就是 IP 层,下面我们一起去瞧一瞧完成目的地定位的 IP 层。
在IP协议里面需要有源地址IP和目标地址IP:
源地址IP,就是客户端输出的IP地址
目标地址IP,即通过DNS域名解析得到的web服务器ip
因为HTTP是经过TCP传输的,所以IP包头的协议号,要填写06,表示协议为TCP
假设客户端有多个网卡,就会有多个IP地址,那IP头部的源地址应该选择哪个IP呢?
多个网卡需要填写源地址时,需要判断应该是用哪个一块网卡来发送包。
这个时候就需要路由表规则,来判断哪一个网卡作为源地址IP
在linux中可以 使用route -n 命令查看当前系统的路由表
根据上边的路由表 假设Web服务器的目标地址是192.168.10.200
TCP在维护状态的过程中,都需要委托 IP 层将数据封装,发送和处理网络数据包进入网络层。IP 协议是 TCP/IP 协议栈的核心,IP 协议中规定了在 Internet 上进行通信时应遵循的规则,包括 IP 数据包应如何构成、数据包的路由等,而 IP 层实现了网络上的点对点通信。
首先来看看 IP 层处理上层网络数据包的过程,网络数据包(无论输入数据包还是输出数据包)进入网络层后,IP 层协议的函数都要对网络数据包做后面这 5 步操作。
1、数据包校验和检验
2、防火墙对数据包过滤
3、IP 选项处理
4、数据分片和重组
5、接收、发送和前送
为了完成上述操作,IP 层被设计成三个部分,分别是 IP 寻址、路由和分包组包。
其实在网络通信的过程中,每个设备都必须拥有自己的 IP 地址才可以完成通信,我们的** IP 地址是以四组八位的组合进行约定,每组以. 号隔开,再转化为十进制的方式。这里要注意,IP 地址并不是以主机数目进行配置的,而是根据网卡数**来进行。
有了 IP 地址,就可以通信了,但 IP 层仍然是一个软件实现的功能逻辑层,那它如何完成通信呢,答案是不能直接完成通信,它只是把 IP 地址及相关信息组装成一个 IP 头,把这个 IP 头放在网络数据的前面,形成了 IP 包,最后把这个 IP 包发送给 IP 层的下一层组件就行了,IP 头的格式如下所示。
IP头部:
有了 IP 头的网络数据,就有了发送目的地的信息,那么该如何才能将报文发送到目的地呢?这就要请 MAC 出场了,这个 MAC 层,就是 IP 层的下一层组件
两点传输 —— MAC
生成了IP头部之后,接下来网络包需要在IP头部加上MAC头部
MAC包头里面有发送方MAC地址 接收方MAC地址 用于两点之间的传输
MAC包头的协议类型只有 IP协议(0800) ARP协议(0806)
发送方的MAC地址可以直接读出来
接收方的MAC地址 需要通过ARP协议帮助我们路由器的MAC地址
ARP协议会在以太网中以广播的形式 ,对以太网所有设备喊出,这个IP地址是谁的,请把你的MAC地址告诉我
对方回答之后,如果对方和自己处于同一个子网中,就可以将获得的MAC地址写入头部
之后会把查询结果放到一块ARP缓存的内存
所以说:
先查询ARP缓存,如果其中已经保存了对方的MAC地址,就不需要发送ARP查询,可以直接使用ARP缓存中的地址
而当ARP缓存中不存在对方的MAC地址时,则发送了ARP广播查询
linux中可以通过arp -a
之后获得了MAC头部的数据包就可以开始走啦
发送方的 MAC 头比较容易获取,读取当前设备网卡的 MAC 地址就可以获取,而接收方的 MAC 头则需要通过 ARP 协议在网络中携带 IP 地址,在一个网络中发送广播信息,这样就能获取这个网络中的 IP 地址对应的 MAC 地址,然后就能给我们的 IP 包加上 MAC 头了,最后这个加上 MAC 头的 IP 包,成为一个 MAC 数据包,就可以准备发送出去了
下面就进入最后的阶段,数据的发送,即网络层中的最低层——物理层
现在拿到了经过层层处理过的数据包,数据包只是一串二进制数据,网络上的数据传送,是依赖电信号的,所以我们现在需要将数据包转化为电信号,才能在物理的网线上面传输。
那么数据包是如何被转换电信号的呢
数据包通过网络协议栈的层层处理,最终得到了 MAC 数据包,这个 MAC 数据包会交给网卡驱动程序,而网卡驱动程序会将 MAC 数据包写入网卡的缓冲区(网卡上的内存).
然后,网卡会在 MAC 数据包的起止位置加入起止帧和校验序列,最后网卡会将加入起止帧和校验序列的 MAC 数据包转化为电信号,发送出去。
互相扒皮——服务器和客户端
数据包到了之后,看MAC是否符合,符合就收起来,
扒开IP的头,发现IP地址符合,根据IP头中的协议项,知道上层是TCP
扒开TCP的头,里面有序列号,需要看一看这个是不是我想要的,如果是就放入缓存中返回一个ACK,如果不是就丢弃。TCP头部还有端口号,HTTP的服务器正在监听这个端口号。
于是服务器就知道HTTP进程要这个包,于是就把这个包发给HTTP进程。
服务器的HTTP进程看到,原来这个请求是要访问一个页面,于是就把网页封装在HTTP相应报文里。
之后相应报文也要穿上TCP IP MAC头部,不过这次是源地址时服务器的IP地址,目的地址是客户端的IP地址。
现在,数据终于通过网卡离开了计算机,进入到局域网,通过局域网中的设备,集线器、交换机和路由器等,数据会进入到互联网,最终到达目标服务器。
接着,服务器就会先取下数据包的 MAC 头部,查看是否匹配自己 MAC 地址。然后继续取下数据包的 IP 头,数据包中的目标 IP 地址和自己的 IP 地址匹配,再根据 IP 头中协议项,知道自己上层是 TCP 协议。
之后,还要继续取下数据包 TCP 的头。完成一系列的顺序校验和状态变更后,TCP 头部里面还有端口号,此时我们的 HTTP 的 server 正在监听这个端口号,就把数据包再发给对应的 HTTP 进程。
HTTP 进程从服务器中拿到对应的资源(html 文件),再交给操作系统对数据进行处理。然后再重复上面的过程,层层携带 TCP、IP、MAC 头部。接下来数据从网卡出去,到达客户端,再重复刚才的过程拿到相应数据。客户端拿到对应的 HTML 资源,浏览器就可以开始解析渲染了,这步操作完成后,用户最终就能通过浏览器看到相应的页面。
画了两幅图,来描述上述过程,第一幅是网络协议各层之间封装与拆封数据的过程,如下所示
TCP_IP协议栈:
下面的第二幅图,是描述客户端与服务器之间用网络协议连接通信的过程,如下所示
此时客户端和服务端之间通过 TCP 协议维护了一个连接状态,如果客户端需要关闭网络,那么会进行四次挥手,两边的网络传输过程至此完成。