前言
从浏览器输入一个URL后到底发生了什么呢?这个问题可以用两句话简单概括为:浏览器向Web服务器发送请求,接收来自服务器的响应并将内容展现在浏览器中。但是这样的回答并不能体现出中间的重要过程,而这些过程会覆盖到计算机网络大部分的知识,所以我们对于这个问题回答的深度,体现了我们对计算机网络功力的深厚。而在互联网公司的面试中,也经常会通过这个问题来考察我们对网络的掌握情况。接下来我们便通过《网络是怎样连接的》一书中的思路来解答这个问题!!
一、浏览器生成消息
1.生成HTTP请求
我们的探索之旅从在浏览器中输入网址开始A,在介绍浏览器的工作方式之前,让我们先来介绍一下网址。网址,准确来说应该叫URL,如果我说它就是以http://开头的那一串东西,恐怕大家一下子就明白了,但实际上除了“http:”, 网址还可以以其他一些文字开头,例如“ftp:” “file:” “mailto:” 等。之所以有各种各样的URL,是因为尽管我们通常是使用浏览器来访问Web 服务器的,但实际上浏览器并不只有这一个功能,它也可以用来在FTP服务器上下载和上传文件,同时也具备电子邮件客户端的功能。可以说,浏览器是一个具备多种客户端功能的综合性客户端软件,因此它需要一些东西来判断应该使用其中哪种功能来访问相应的数据,而各种不同的URL就是用来干这个的,比如访问Web服务器时用“http:”, 而访问FTP服务器时用“ftp:”。
我们常用的请求方法是GET和POST:
对URL进行解析之后,浏览器确定了Web服务器和文件名,接下来就是根据这些信息来生成HTTP请求消息了。实际上,HTTP消息在格式上是有严格规定的:
2.向DNS服务器查询Web服务器的IP地址
生成HTTP消息之后,接下来我们需要委托操作系统将消息发送给Web 服务器。尽管浏览器能够解析网址并生成HTTP消息,但它本身并不具备将消息发送到网络中的功能,因此这一功能需要委托操作系统来实现。在委托操作系统发送消息时,必须要提供的不是通信对象的域名,而是它的IP地址。因此,在生成HTTP消息之后,下一个步骤就是根据域名查询IP地址。
那么如果我们不用IP地址而是改用名称会怎么样呢?IP地址的长度为32比特,也就是4字节,相对地,域名最短也要几十个字节,最长甚至可以达到255字节。换句话说,使用IP地址只需要处理4字节的数字,而域名则需要处理几十个到255个字节的字符,这增加了路由器的负担,传送数据也会花费更长的时间。可能有人会说:“ 那使用高性能路由器不就能解决这个问题了吗?” 然而,路由器的速度是有极限的,而互联网内部流动的数据量已然让路由器疲于应付了,因此我们不应该再采用效率更低的设计。
查询IP地址的方法非常简单,只要询问最近的DNS服务器“www. lab.glasscom.com 的 IP 地址是什么”就可以了,DNS服务器会回答说“该服务器的IP地址为xxx.xxx.xxx.xxx”。 这一步非常简单,很多读者也都很熟悉,那么浏览器是如何向DNS服务器发出查询的呢?
向DNS服务器发出查询,也就是向DNS服务器发送查询消息,并接收服务器返回的响应消息。换句话说,对于DNS服务器,我们的计算机上一定有相应的DNS客户端,而相当于DNS客户端的部分称为DNS解析器,或者简称解析器。通过DNS查询IP地址的操作称为域名解析,因此负责执行解析(resolution)这一操作的就叫解析器(resolver)了。
解析器实际上是一段程序,它包含在操作系统的Socket库中。
下面是解析器的工作流程:
如果要访问的Web服务器已经在DNS服务器上注册,那么这条记录就能够被找到,然后其IP地址会被写入响应消息并返回给客户端。 接下来,消息经过网络到达客户端,再经过协议栈被传递给解析器, 然后解析器读取出消息取出IP地址,并将IP地址传递给应用程序。
3.全世界DNS服务器的大接力
DNS服务器就是根据这些记录查找符合查询请求的内容并对客户端作出响应的。
如果是在像公司内部网络这样Web和邮件服务器数量有限的环境中,所有的信息都可以保存在一台DNS服务器中,其工作方式也就完全符合我们前面讲解的内容。然而,互联网中存在着不计其数的服务器,将这些服务器的信息全部保存在一台DNS服务器中是不可能的,因此一定会出现在DNS服务器中找不到要查询的信息的情况。下面来看一看此时DNS服务器是如何工作的。
首先,DNS服务器中的所有信息都是按照域名以分层次的结构来保存的。
一个域的信息是作为一个整体存放在DNS服务器中的,不能将一个域拆开来存放在多台DNS服务器中。不过,DNS服务器和域之间的关系也并不总是一对一的,一台DNS服务器中也可以存放多个域的信息。
互联网中有数万台DNS服务器,肯定不能一台一台挨个去找。我们可以采用下面的办法。首先,将负责管理下级域的DNS服务器的IP地址注册到它们的上级DNS服务器中,然后上级DNS服务器的IP地址再注册到更上一级的DNS服务器中,以此类推。
除此之外还需要完成另一项工作,那就是将根域的DNS服务器信息保存在互联网中所有的DNS服务器中。这样一来,任何DNS服务器就都可以找到并访问根域DNS服务器了。因此,客户端只要能够找到任意一台DNS服务器,就可以通过它找到根域DNS服务器,然后再一路顺藤摸瓜找到位于下层的某台目标DNS服务器。 分配给根域DNS服务器的IP地址在全世界仅有13个,而且这些地址几乎不发生变化,因此将这些地址保存在所有的DNS服务器中也并不是一件难事。实际上,根域DNS服务器的相关信息已经包含在DNS服务器程序的配置文件中了,因此只要安装了DNS服务器程序,这些信息也就被自动配置好了。
下图便是找到DNS服务器的过程:
4.将HTTP消息委托给协议栈
知道了IP地址之后,就可以委托操作系统内部的协议栈向这个目标IP地址,也就是我们要访问的Web服务器发送消息了。要发送给Web服务器的HTTP消息是一种数字信息(digital data), 因此也可以说是委托协议栈来发送数字信息。
二、用电信号传输TCP/IP数据
操作系统通过网络控制软件(协议栈)和网络硬件(网卡)将浏览器的消息发送给服务器。
1.创建套接字
套接字中记录了用于控制通信操作的各种控制信息,协议栈则需要根据这些信息判断下一步的行动,这就是套接字的作用。
首先是创建套接字的阶段。应用程序调用socket申请创建套接字,协议栈根据应用程序的申请执行创建套接字的操作。在这个过程中,协议栈首先会分配用于存放一个套接字所需的内存空间。用于记录套接字控制信息的内存空间并不是一开始就存在的,因此我们先要开辟出这样一块空间来,这相当于为控制信息准备一个容器。但光一个容器并没有什么用,还需要往里面存入控制信息。套接字刚刚创建时,数据收发操作还没有开始,因此需要在套接字的内存空间中写入表示这一初始状态的控制信息。
2.将握手信息封装成TCP报文
创建套接字之后,浏览器就会调用connect,随后协议栈会将本地的套接字与服务器的套接字进行连接。话说,以太网的网线都是一直连接的状态,我们并不需要来回插拔网线,那么这里的“连接”到底是什么意思呢?连接实际上是通信双方交换控制信息。
套接字刚刚创建完成的时候,里面并没有存放任何数据,也不知道通信的对象是谁。在这个状态下,即便应用程序要求发送数据,协议栈也不知道数据应该发送给谁。浏览器可以根据网址来查询服务器的IP地址,而且根据规则也知道应该使用80号端口,但只有浏览器知道这些必要的信息是不够的,因为在调用socket创建套接字时,这些信息并没有传递给协议栈。因此,我们需要把服务器的IP地址和端口号等信息告知协议栈,这是连接操作的目的之一。
服务器上也会创建套接字,但服务器上的协议栈和客户端一样,只创建套接字是不知道应该和谁进行通信的。而且,和客户端不同的是,在服务器上,连应用程序也不知道通信对象是谁,这样下去永远也没法开始通信。于是,我们需要让客户端向服务器告知必要的信息,比如“我想和你开始通信,我的IP地址是xxx.xxx. xxx.xxx,端口号是yyyy。” 可见,客户端向服务器传达开始通信的请求,也是连接操作的目的之一。
上面的调用提供了服务器的IP地址和端口号,这些信息会传递给协议栈中的TCP模块。
首先,客户端先创建一个包含表示开始数据收发操作的控制信息的头部。然后,我们将头部中的控制位的SYN比特设置为1,此外还需要设置适当的序号和窗口大小。
3.TCP模块将报文委托给IP模块
当TCP头部创建好之后,接下来TCP模块会将信息传递给IP模块并委托它进行发送。
而此时IP模块会将这些控制信息加上以太网头部形成TCP/IP包:
IP头部中的接收方IP地址表示网络包的目的地,通过这个地址我们就可以判断要将包发到哪里,但在以太网的世界中,TCP/ IP 的这个思路是行不通的。以太网在判断网络包目的地时和TCP/IP的方式不同,因此必须采用相匹配的方式才能在以太网中将包发往目的地,而MAC头部就是干这个用的。
其中以太网的头部中发送方的MAC地址,填写网卡本身的MAC地址。MAC地址是在网卡生产时写入ROM里的,只要将这个值读取出来写入MAC头部就可以了!
而接收方MAC地址就有点复杂了。然而,在这个时间点上,我们还没有把包发送出去,所以先得搞清楚应该把包发给谁,这个只要查一下路由表就知道了。在路由表中找到相匹配的条目,然后把包发给Gateway列中的IP 地址就可以了。
既然已经知道了包应该发给谁,那么只要将对方的MAC地址填上去就好了,但到这里为止根本没有出现对方的MAC地址,也就是说我们现在根本不知道对方的MAC地址是什么。因此,我们还需要执行根据IP地址查询MAC地址的操作。
这里我们需要使用ARP,它其实非常简单。在以太网中,有一种叫作广播的方法,可以把包发给连接在同一以太网中的所有设备。如果对方和自己处于同一个子网中,那么通过上面的操作就可以得到对方的MAC地址。
下面我们来看看以太网的基本结构:
下面来看看以太网的包收发操作。IP生成的网络包只是存放在内存中的一串数字信息,没有办法直接发送给对方。因此,我们需要将数字信息转换为电或光信号,才能在网线上传输,也就是说,这才是真正的数据发送过程。负责执行这一操作的是网卡,但网卡也无法单独工作,要控制网卡还需要网卡驱动程序。
5.IP模块将报文委托给网卡进行发送
网卡驱动从IP模块获取包之后,会将其复制到网卡内的缓冲区中,然后向MAC模块发送发送包的命令。接下来就轮到MAC模块进行工作了。
首先,MAC模块会将包从缓冲区中取出,并在开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列。
报头是一串像10101010…这样1和0交替出现的比特序列,长度为56比特,它的作用是确定包的读取时机。当这些1010的比特序列被转换成电信号后,会形成下图这样的波形。接收方在收到信号时,遇到这样的波形就可以判断读取数据的时机。
加上报头、起始帧分界符和FCS之后,我们就可以将包通过网线发送出去了。 发送信号的操作分为两种,一种是使用集线器的半双工模式,另一种是使用交换机的全双工模式。
在半双工模式中,为了避免信号碰撞,首先要判断网线中是否存在其他设备发送的信号。如果有,则需要等待该信号传输完毕,因为如果在有信号时再发送一组信号,两组信号就会发生碰撞。当之前的信号传输完毕,或者本来就没有信号在传输的情况下,我们就可以开始发送信号了。首先MAC模块从报头开始将数字信息按每个比特转换成电信号,然后由PHY,或者叫MAU的信号收发模块发送出去。
接下来,PHY(MAU)模块会将信号转换为可在网线上传输的格式,并通过网线发送出去。
三、从网线到网络设备
从计算机发送出来的网络包会通过集线器、路由器等设备被转发,最终到达目的地。
1.集线器将信号发往所有路线
网卡中的PHY(MAU)模块负责将包转换成电信号,信号通过RJ-45接口进入双绞线,信号从这个接口中的1号和2号针脚流入网线。然后,信号会通过网线到达集线器的接口,这个过程就是单纯地传输电信号而已。
但是,信号到达集线器的时候并不是跟刚发送出去的时候一模一样。集线器收到的信号有时会出现衰减。 信号在网线的传输过程中,能量会逐渐损失。网线越长,信号衰减就越严重。
局域网网线使用的是双绞线,其中“双绞”的意思就是以两根信号线为一组缠绕在一起,这种拧麻花一样的设计是为了抑制噪声的影响。在一根网线中,每一对信号线的扭绞间隔(节距)都有一定的差异,这使得在某些地方正信号线距离近,另一些地方则是负信号线距离近。由于正负信号线产生的噪声影响是相反的,所以两者就会相互抵消.
当信号到达集线器后,会被广播到整个网络中。以太网的基本架构就是将包发到所有的设备,然后由设备根据接收方MAC地址来判断应该接收哪些包,而集线器就是这一架构的忠实体现,它就是负责按照以太网的基本架构将信号广播出去。
信号到达集线器的PHY(MAU)模块后,会进入中继电路。中继电路的基本功能就是将输入的信号广播到集线器的所有端口上。
接下来,信号从所有接口流出,到达连接在集线器上的所有设备。然后,这些设备在收到信号之后会通过MAC头部中的接收方MAC地址判断是不是发给自己的,如果是发给自己的就接受,否则就忽略。这样,网络包就能够到达指定MAC地址的接收方了。
2.交换机的包转发操作
交换机的设计是将网络包原样转发到目的地。
首先,信号到达网线接口,并由PHY(MAU)模块进行接收,这一部分和集线器是相同的。也就是说,它的接口和PHY(MAU)模块也是以MDI-X 模式进行连接的,当信号从双绞线传入时,就会进入PHY(MAU)模块的接收部分。
接下来,PHY(MAU)模块会将网线中的信号转换为通用格式,然后传递给MAC模块。MAC模块将信号转换为数字信息,然后通过包末尾的FCS 校验错误,如果没有问题则存放到缓冲区中。这部分操作和网卡基本相同,大家可以认为交换机的每个网线接口后面都是一块网卡。网线接口和后面的电路部分加在一起称为一个端口,也就是说交换机的一个端口就相当于计算机上的一块网卡。但交换机的工作方式和网卡有一点不同。网卡本身具有MAC地址,并通过核对收到的包的接收方MAC地址判断是不是发给自己的,如果不是发给自己的则丢弃;相对地,交换机的端口不核对接收方MAC地址,而是直接接收所有的包并存放到缓冲区中。因此,和网卡不同,交换机的端口不具有MAC地址。
将包存入缓冲区后,接下来需要查询一下这个包的接收方MAC地址是否已经在MAC地址表中有记录了。MAC地址表主要包含两个信息,一个是设备的MAC地址,另一个是该设备连接在交换机的哪个端口上。MAC地址和端口是一一对应的,通过这张表就能够判断出收到的包应该转发到哪个端口。
当网络包通过交换电路到达发送端口时,端口中的MAC模块和PHY(MAU)模块会执行发送操作,将信号发送到网线中,这部分和网卡发送信号的过程是一样的。
3.路由器的包的转发操作
网络包经过集线器和交换机之后,现在到达了路由器,并在此被转发到下一个路由器。这一步转发的工作原理和交换机类似,也是通过查表判断包转发的目标。不过在具体的操作过程上,路由器和交换机是有区别的。因为路由器是基于IP设计的,而交换机是基于以太网设计的。
路由器的内部结构如下:
以以太网端口为例,路由器的端口具有MAC地址,因此它就能够成为以太网的发送方和接收方当转发包时,端口还具有IP地址,从这个意义上来说,它和计算机的网卡是一样的。首先路由器端口会接收发给自己的以太网包,然后查询转发目标,再由相应的端口作为发送方将以太网包发送出去。这一点和交换机是不同的,交换机只是将进来的包转发出去而已,它自己并不会成为发送方或者接收方。
在“查表判断转发目标”这一点上,路由器和交换机的大体思路是类似的,不过具体的工作过程有所不同。交换机是通过MAC头部中的接收方MAC地址来判断转发目标的,而路由器则是根据IP头部中的IP地址来判断的。由于使用的地址不同,记录转发目标的表的内容也会不同。
a.路由器的包的接收操作
首先,信号到达网线接口部分,其中的PHY(MAU)模块和MAC模块将信号转换为数字信息,然后通过包末尾的FCS进行错误校验,如果没问题则检查MAC头部中的接收方MAC地址,看看是不是发给自己的包,如果是就放到接收缓冲区中,否则就丢弃这个包。如果包的接收方MAC地址不是自己,说明这个包是发给其他设备的,如果接收这个包就违反了以太网的规则。
b.查询路由表确定输出端口
完成包接收操作之后,路由器就会丢弃包开头的MAC头部。MAC头部的作用就是将包送达路由器,其中的接收方MAC地址就是路由器端口的MAC地址。因此,当包到达路由器之后,MAC头部的任务就完成了,于是MAC头部就会被丢弃。
接下来,路由器会根据MAC头部后方的IP头部中的内容进行包的转发操作。转发操作分为几个阶段,首先是查询路由表判断转发目标。
如果在路由表中无法找到匹配的记录,路由器会丢弃这个包,并通过ICMP消息告知发送方。
c.找不到匹配路由时选择默认路由
路由表中有一行的子网掩码为0.0.0.0,关键就在这里,子网掩码0.0.0.0的意思是网络包接收方IP地址和路由表目标地址的匹配中需要匹配的比特数为0,换句话说,就是根本不需要匹配。只要将子网掩码设置为0.0.0.0,那么无论任何地址都能匹配到这一条记录,这样就不会发生不知道要转发到哪里的问题了。
d.包的有效期
从路由表中查找到转发目标之后,网络包就会被转交给输出端口,并最终发送出去,但在此之前,路由器还有一些工作要完成。
第一个工作是更新IP头部中的TTL,这个机制是为了防止包在一个地方陷入死循环。
e.通过分片功能拆分大网络包
路由器的端口并不只有以太网一种,也可以支持其他局域网或专线通信技术。不同的线路和局域网类型各自能传输的最大包长度也不同,因此输出端口的最大包长度可能会小于输入端口。遇到这种情况,可以使用IP协议中定义的分片功能对包进行拆分,缩短每个包的长度。
f.路由器的发送操作和计算机相同
这一步操作取决于输出端口的类型。如果是以太网端口,则按照以太网的规则将包转换为电信号发送出去;首先,为了判断MAC头部中的MAC地址应该填写什么值,我们需要根据路由表的网关列判断对方的地址。如果网关是一个IP地址,则这个IP 地址就是我们要转发到的目标地址;如果网关为空,则IP头部中的接收方IP地址就是要转发到的目标地址。知道对方的IP地址之后,接下来需要通过ARP根据IP地址查询MAC地址,并将查询的结果作为接收方MAC地址。路由器也有ARP缓存,因此首先会在ARP缓存中查询,如果找不到则发送ARP查询请求。
路由器与交换机的关系
IP协议本身没有传输包的功能,因此包的实际传输要委托以太网来进行。路由器是基于IP设计的,而交换机是基于以太网设计的,因此IP与以太网的关系也就是路由器与交换机的关系。IP并不是委托以太网将包传输到最终目的地,而是传输到下一个路由器。在创建MAC头部时,也是从IP的路由表中查找出下一个路由器的IP地址,并通过ARP查询出MAC地址,然后将MAC地址写入MAC头部中的,这表示IP对以太网的委托只是将包传输到下一个路由器就行了。当包到达下一个路由器后,下一个路由器又会重新委托以太网将包传输到再下一个路由器。随着这一过程反复执行,包就会最终到达IP的目的地,也就是通信的对象。
四、通过接入网进入互联网内部
如果网络包的目标服务器位于家庭、公司网络中的话,那么就不需要通过互联网接入路由器,而是直接转发给目标服务器,也不会进入互联网。所谓接入网,就是指连接互联网与家庭、公司网络的通信线路。(互联网接入路由器是按照接入网规则来发送包的)
五、到达服务端的局域网
接下来,网络包将继续朝服务器前进,并通过服务器前面的防火墙、缓存服务器、负载均衡器等。
1.防火墙的原理
无论服务器部署在哪里,现在一般都会在前面部署一个防火墙,如果包无法通过防火墙,就无法到达服务器。防火墙的基本思路,即只允许发往特定服务器中的特定应用程序的包通过,然后屏蔽其他的包。
在设置包过滤规则时,首先要观察包是如何流动的。通过接收方IP地址和发送方IP地址,我们可以判断出包的起点和终点。
当我们要限定某个应用程序时,可以在判断条件中加上TCP头部或者UDP头部中的端口号。
Web使用的TCP协议是双向收发网络包的,因此如果单纯地阻止从Web服务器发往互联网的包,则从互联网访问Web服务器的操作也会受到影响而无法进行。光判断包的流向还不够,我们必须要根据访问的方向来进行判断。这里就需要用到TCP头部中的控制位。
防火墙可以根据包的起点和终点来判断是否允许其通过,但仅凭起点和终点并不能筛选出所有有风险的包。
2.通过将请求平均分配给多台服务器来平衡负载
高速线路会传输大量的网络包,这会导致服务器的性能跟不上。在这种情况下,使用多台服务器来分担负载的方法更有效。这种架构统称为分布式架构,其中对于负载的分担有几种方法,最简单的一种方法就是采用多台Web服务器,减少每台服务器的访问量。假设现在我们有3台服务器,那么每台服务器的访问量会减少到三分之一,负载也就减轻了。要采用这样的方法,必须有一个机制将客户端发送的请求分配到每台服务器上。具体的做法有很多种,最简单的一种是通过DNS服务器来分配。当访问服务器时,客户端需要先向DNS服务器查询服务器的IP地址,如果在DNS服务器中填写多个名称相同的记录,则每次查询时DNS服务器都会按顺序返回不同的IP地址。
但这种方式是有缺点的。假如多台Web服务器中有一台出现了故障,这时我们希望在返回IP地址时能够跳过故障的Web服务器,然而普通的DNS服务器并不能确认Web服务器是否正常工作,因此即便Web服务器宕机了,它依然可能会返回这台服务器的IP地址。此外,轮询分配还可能会引发一些问题。在通过CGI等方式动态生成网页的情况下,有些操作是要跨多个页面的,如果这期间访问的服务器发生了变化,这个操作就可能无法继续。例如在购物网站中,可能会在第一个页面中输入地址和姓名,在第二个页面中输入信用卡号,这就属于刚才说的那种情况。
为了避免出现前面的问题,可以使用一种叫作负载均衡器的设备。使用负载均衡器时,首先要用负载均衡器的IP地址代替Web服务器的实际地址注册到DNS服务器上。
3.使用缓存服务器分担负载
除了使用多台功能相同的Web服务器分担负载之外,还有另外一种方法,就是将整个系统按功能分成不同的服务器,如Web服务器、数据库服务器。缓存服务器就是一种按功能来分担负载的方法。
六、请求到达Web服务器,响应返回浏览器
当客户端和服务端通信建立成功后,就可以进行通信了。然后将包去除头部,交付给上层。
网卡将接收到的信号转换成数字信息,在这个过程中,服务器的CPU并不是一直在监控网络包的到达,而是在执行其他的任务,因此CPU并不知道此时网络包已经到达了。但接下来的接收操作需要CPU来参与,因此网卡需要通过中断将网络包到达的事件通知给CPU。
接下来,CPU就会暂停当前的工作,并切换到网卡的任务。然后,网卡驱动会开始运行,从网卡缓冲区中将接收到的包读取出来,根据MAC头部的以太类型字段判断协议的种类,并调用负责处理该协议的软件。这里,以太类型的值应该是表示IP协议,因此会调用TCP/IP协议栈,并将包转交给它。
当网络包转交到协议栈时,IP模块会首先开始工作,检查IP头部。IP模块首先会检查IP头部的格式是否符合规范,然后检查接收方IP地址,看包是不是发给自己的。当服务器启用类似路由器的包转发功能时,对于不是发给自己的包,会像路由器一样根据路由表对包进行转发。
确认包是发给自己的之后,接下来需要检查包有没有被分片。检查IP头部的内容就可以知道是否分片,如果是分片的包,则将包暂时存放在内存中,等所有分片全部到达之后将分片组装起来还原成原始包;如果没有分片,则直接保留接收时的样子,不需要进行重组。到这里,我们就完成了包的接收。接下来需要检查IP头部的协议号字段,并将包转交给相应的模块。例如,如果协议号为0x06, 则将包转交给TCP模块;如果是0x11, 则转交给UDP模块。
当数据收发完成后,便开始执行断开操作。
最后Web服务器程序解释请求消息并作出响应。至此,整个流程就结束了!