网络是如何连接的?
从浏览器地址栏输入后敲下回车,直到浏览器呈现信息,这个过程到底发生了什么?
这一过程涉及宽带接入、浏览器、前端技术,DNS,TCP/IP,操作系统,网卡,驱动程序,传输设备,交换机、路由器,服务器等等网络、通信、web相关的几乎所有技术
在前文中,我有上面两幅图,网络的通信类似快递的运输
计算机网络传输的核心是TCP/IP协议,分为四层结构
应用层产生需要传输的真正数据。
传输层、网际层、网络接口层由操作系统以及网卡驱动程序和物理网卡实现,负责将数据从计算机中发送出去,经过路由器(网际层)等网络设置到达最终的目的地。
浏览器发起请求
解析URL
打开浏览器之后,可以在地址栏输入网址(或者点击某个超链接)本质是一样的,网址就是URL
URL是Uniform Resource Locator的缩写,译为“统一资源定位符”,通常由三部分组成,协议,ip地址(或者域名)以及资源的具体目录(第三个也可以省略)
https是协议,www.cnblogs.com是域名,FondWang是资源具体路径
输入网址,按下回车后,浏览器的目的就是请求这一“URL”的资源,并将解析呈现出来。
浏览器首先要解析URL,进一步确定通信协议
应用层不止一种协议,有http、ftp、file、https 等,每种协议自然有不同的约定方式
所以需要先确定协议,协议是什么?协议就是相互之间的一个约定,比如我给你一块钱,你给我一瓶矿泉水,这就是约定,约定好了大家就按照协议进行执行。
解析URL之后,浏览器解析出URL给出的信息
HTTP请求
我们输入的网址是https协议,简单起见以HTTP来了解
HTTP 协议定义了客户端和服务器之间交互的消息内容和步骤,大家按照固定的步骤和格式进行通信
根据URL可以定位请求资源的位置,但是对于这个资源可能还有多种处理方法,比如是请求资源还是要删除指定资源?
所以HTTP请求也规定了方法,用于指定请求的类型
HTTP主要请求方法为 GET 和 POST
如上图所示,HTTP主要有两类报文,一个是从客户端向服务器发送请求,一个是服务器到客户端的应答
下面就是请求和响应的格式
请求和响应都由三部分组成,首行、头部以及实体
请求报文中分别叫做,请求行,请求头,请求体
响应报文分别叫做:状态行(响应行),响应头,响应体
每一部分又都由多个字段约定了更加详细具体的通信规则
在浏览器中可以查看到这些信息,比如chrome中查看https://www.cnblogs.com/FondWang/ 的请求和响应信息
总之
浏览器将需要请求的信息,按照HTTP协议约定的格式,封装成为HTTP请求报文
DNS解析
HTTP请求是应用层协议,本身不具备将消息发送到网络的功能
想要进一步进行通信,还需要调用操作系统提供的socket接口,与服务器建立连接进行通信
socket是操作系统对TCP/IP的封装,提供应用编程接口,想要依赖socket进行通讯,需要ip地址和端口号
此时,我们仅仅知道服务器的名称,也就是域名www.cnblogs.com
需要获取到服务器的真实IP地址,才能够真正的进行通信
解析的过程:
- 操作系统会先检查自己本地的hosts文件,如果hosts里没有这个域名的映射,则查找本地DNS解析器缓存
- 如果hosts与本地DNS解析器缓存都没有相应的网址映射关系,首先会找TCP/IP参数中设置的首选DNS服务器,叫做本地DNS服务器
- 本地DNS解析器是操作系统中的DNS客户端程序,负责DNS的解析管理
所以,以上的步骤就是
- hosts文件有没有?
- 操作系统的客户端--本地DNS解析器缓存 有没有?
- 还没有?去配置的DNS服务器中进行查找!
如果要查询的域名,不由本地DNS服务器区域解析
但该服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析,此解析不具有权威性
如果本地DNS服务器本地区域文件与缓存解析都失效,则根据本地DNS服务器的设置(是否设置转发)进行查询
如果未用转发模式,那么迭代查询
- 本地DNS就把请求发至 “根DNS服务器”,“根DNS服务器”收到请求后会判断这个域名(.com)是谁来授权管理,并会返回一个负责该顶级域名服务器的一个IP。
- 本地DNS服务器收到IP信息后,将会联系负责.com域的这台服务器。
- 这台负责.com域的服务器收到请求后,如果自己无法解析,它就会找一个管理.com域的下一级DNS服务器地址(cnblogs.com)给本地DNS服务器。
- 当本地DNS服务器收到这个地址后,就会找cnblogs.com域服务器,重复上面的动作,进行查询,直至找到www.cnblogs.com主机。
如果用的是转发模式,那么递归查询
- 此DNS服务器就会把请求转发至上一级DNS服务器,由上一级服务器进行解析
- 上一级服务器如果不能解析,或找根DNS或把转请求转至上上级,以此循环。
不管本地DNS服务器是否使用转发,最后都是把结果返回给本地DNS服务器,然后由此DNS服务器再返回给客户机。
本地DNS服务器解析的过程,就是前面提到的DNS的查询
主机到本地域名服务器的查询是递归查询
本地域名服务器的查询过程,可以是递归查询,也可以是迭代查询
DNS解析之后就获得了请求域名的IP地址,对于HTTP请求,如果没有设置那么默认是80端口
如果设置的话,那么就是使用指定的端口
端口号会设置在URL中,浏览器客户端解析URL就可以获得,不需要专门的解析
所以此时,拥有了Ip地址和端口号
socket连接
socket是操作系统提供的TCP/IP的薄层封装,大大简化了TCP/IP的使用
借助于socket通信的过程大致分为下面四个过程
web服务器会先创建套接字,然后进行监听,等待客户端进行连接
下图是一个简化版,简化版,简化版,socket连接请求的过程
socket是对TCP/IP协议的封装,所以本质还是TCP/IP,接下来的TCP/IP中会继续介绍
TCP处理
tcp报文格式、字段
如果应用层的数据过大,会将数据进行分块,每个块都会添加TCP头部信息
如下图所示
TCP协议与HTTP协议一样,协议就是一种约定好的格式
TCP的头部的所有字段信息,体现了TCP协议所有的功能,TCP协议模块的实现就是对这些字段的解析使用处理
TCP三次握手
TCP运输连接有三个阶段:连接建立、数据传送、连接释放
客户端有CLOSED、SYN-SEND、ESTABLISHED三种状态
客户端有CLOSED、LISTEN、SYN-RCVD、ESTABLISHED四种状态
服务器会首先创建连接,并且进入监听等待阶段,等待客户端的请求
当需要发送请求时,浏览器客户端主动打开连接,然后服务器被动打开连接
建立起一个TCP连接需要经过“三次握手”:
-
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
-
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
-
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。
理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。
断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”
TCP四次挥手
连接建立以后就可以进行数据通信传输了
通信结束后,需要断开连接,断开连接需要四次交互,常被称为四次挥手
最初状态均为ESTABLISHED,客户端与服务器相互进行数据传送
下图假设客户端无数据发送,请求断开连接
四次挥手也是一个互相确认的过程,你说不玩了,别人答应了,还要等别人都搞好了再告诉你可以走了,你才能走
客户端:我不想玩了
服务器:好的我知道了
服务器:你可以走了
客户端:好的我走了
这个过程很好理解,客户端发出请求后,并不意味着服务器都已经完成响应
所以当客户端请求断开时,并不能立即断开,还需要等待服务器那边处理妥当,再来通知你的确是可以断开了
为什么还需要等待2MSL?
为了保证客户端发送的最后一个确认,能够达到服务器
这个ACK可能丢失,就会导致服务器在LAST-ACK状态,没办法正常结束,那么服务器收不到就会超时重传可以断开的消息
那么A就能够在这个2MSL中收到这个重传的消息,并且重新计时2MSL
而且,客户端持续2MSL时间后断开,就可以保证这个连接的所有报文都会死亡,可以看下MSL的含义,也就是2MSL之后,断开这个连接之后,肯定不会还存在这个连接的旧的报文了
上面说的过程虽然说得是TCP的交流,但是尽管同层之间看似同层交换,但是能不用底层的协议嘛,TCP的数据也还都是要经过IP层,链路层、网卡、路由,一样不缺
数据的接收
从网卡到操作系统
计算机数据的一部分接收过程与路由器逻辑是一模一样的
通过网线的光/电信号,达到计算机的网口,转换为数字信号
会通过包的帧校验序列(FCS)来校验错误,如果是错误的也会丢弃
当 FCS 一致,即确认数据没有错误时,接下来需要检查 MAC 头部中的接收方 MAC 地址,如果是发给自己的,就缓存下来,通知操作系统进行处理
路由器此时的处理是丢掉MAC头部,然后解析IP数据报信息,然后会查找转发表开始准备转发了
而我们的计算机操作系统作为最终目的地,当然不再需要转发,会根据收到的MAC数据进行处理
网卡每收到一个MAC帧就先用硬件检查MAC帧中的目的地址。
如果是发往本站的帧则收下,然后再进行其他的处理。否则就将此帧丢弃,不再进行其他的处理。这样做就不浪费主机的处理机和内存资源。
这里“发往本站的帧”包括以下三种帧:
(1)单播(unicast)帧(一对一),即收到的帧的MAC地址与本站的硬件地址相同。
(2)广播(broadcast)帧(一对全体),即发送给本局域网上所有站点的帧(全1地址)。
(3)多播(multicast)帧(一对多),即发送给本局域网上一部分站点的帧。
所有的适配器都至少应当能够识别前两种帧,即能够识别单播和广播地址。有的适配器可用编程方法识别多播地址。
当操作系统启动时,它就把适配器初始化,使适配器能够识别某些多播地址。显然,只有目的地址才能使用广播地址和多播地址。
网卡需要通过中断将网络包到达的事件通知给 CPU之后,接下来,CPU 就会暂停当前的工作,并切换到网卡的任务。
然后,网卡驱动会开始运行,从网卡缓冲区中将接收到的包读取出来,根据 MAC头部的以太类型字段判断协议的种类,并调用负责处理该协议的软件。
这里,以太类型的值应该是表示 IP 协议,因此会调用 TCP/IP 协议栈,并将包转交给它。
简言之
网卡接收到数据之后,产生中断,汇报操作系统,操作系统过来读取数据,根据类型交给指定的协议栈实现部分
至此,数据已经从另一端达到了目的地计算机了
IP模块接收
当网络包转交到协议栈时,IP 模块会首先开始工作,检查 IP 头部。
IP模块首先会检查 IP 头部的格式是否符合规范,然后检查接收方 IP 地址,看包是不是发给自己的
确认包是发给自己的之后, 接下来需要检查包有没有被分片
检查 IP头部的内容就可以知道是否分片,如果是分片的包,则将包暂时存放在内存中,等所有分片全部到达之后将分片组装起来还原成原始包;
如果没有分片,则直接保留接收时的样子,不需要进行重组。
到这里,我们就完成了包的接收。
IP是对运输层负责的,运输层也就是TCP或者UDP
所以接下来需要检查 IP 头部的协议号字段,并将包转交给相应的模块。
例如,如果协议号为06(十六进制),则将包转交给 TCP 模块;如果是 11(十六进制),则转交给 UDP 模块。
简言之
协议栈的IP模块会检查IP头部,
(1) 判断是不是发给自己的;
(2) 判断网络包是否经过分片;
(3) 将包转交给TCP模块或UDP模块。
TCP模块接收-连接
当 TCP 头部中的控制位 SYN 为 1 时,表示这是一个发起连接的包。
这时,TCP 模块会执行接受连接的操作
不过在此之前,需要先检查包的接收方端口号,并确认在该端口上有没有与接收方端口号相同且正在处于等待连接状态的套接字。
如果指定端口号没有等待连接的套接字,则向客户端返回错误通知的包。
如果存在等待连接的套接字,则为这个套接字复制一个新的副本,并将发送方 IP 地址、端口号、序号初始值、窗口大小等必要的参数写入这个套接字中
同时分配用于发送缓冲区和接收缓冲区的内存空间。
然后生成代表接收确认的 ACK 号,用于从服务器向客户端发送数据的序号初始值,表示接收缓冲区剩余容量的窗口大小,并用这些信息生成 TCP 头部,委托IP 模块发送给客户端 。
这不就是上面说过的TCP的三次握手嘛,这是其中的第二步,收到“TCP 头部中的控制位 SYN 为 1 时”是第一步
这个包到达客户端之后,客户端会返回表示接收确认的 ACK 号,当这个ACK 号返回服务器后,连接操作就完成了
TCP模块接收-传输
然后就是数据的传送阶段了
数据包重复着上面的过程,TCP 模块会检查收到的包对应哪一个连接
在服务器端,可能有多个已连接的套接字对应同一个端口号,因此仅根据接收方端口号无法找到特定的套接字。
这时我们需要根据 IP 头部中的发送方 IP 地址和接收方 IP 地址,以及 TCP 头部中的接收方端口号和发送方端口号共 4 种信息,找到上述4 种信息全部匹配的套接字。
简言之,发送方 IP 地址和接收方 IP,发送方端口和接收方端口唯一确定一条连接(另外其实包含协议,应该是五个确定一条连接)
找到 4 种信息全部匹配的套接字之后,TCP 模块会对比该套接字中保存的数据收发状态和收到的包的 TCP 头部中的信息是否匹配,以确定数据收发操作是否正常。
具体来说,就是根据保存的上一个序号和数据长度计算下一个序号
并检查与收到的包的 TCP 头部中的序号是否一致 。如果两者一致,就说明包正常到达了服务器,没有丢失。
这时,TCP模块会从包中提出数据,并存放到接收缓冲区中,与上次收到的数据块连接起来。这样一来,数据就被还原成分包之前的状态了。
当收到的数据进入接收缓冲区后,TCP 模块就会生成确认应答的 TCP头部,并根据接收包的序号和数据长度计算出 ACK 号,然后委托 IP 模块发送给客户端。
这将又是一条漫长的旅途同发送数据类似,不过我们根本感觉不到。
web应用程序接收
收到的数据块进入接收缓冲区,意味着数据包接收的操作告一段落了。
接下来,应用程序会调用 Socket 库的 read来获取收到的数据,这时数据会被转交给应用程序。如果应用程序不来获取数据,则数据会被一直保存在缓冲区中
但一般来说,应用程序会在数据到达之前调用 read 阻塞式的等待数据到达,在这种情况下,TCP 模块在完成接收操作的同时,就会执行将数据转交给应用程序的操作。
接收到请求后,会根据URI转换为实际的文件,这些事情web服务器会进行处理
如果 URI 指定的文件内容为 HTML 文档或图片,那么只要直接将文件内容作为响应消息返回客户端就可以了。
但 URI 指定的文件内容不仅限于 HTML 文档,也有可能是一个程序。
在这个情况下,服务器不会直接返回文件内容,而是会运行这个程序,然后将程序输出的数据返回给客户端
而响应的内容就是HttP协议中对于响应的约束。