目录
序言
在浏览器输入一个网址url到底发生了什么?网上应该有很多答案,不过我还是想从自己的角度上去说一下,整个过程绝不仅仅包含于以下内容。
网址与域名
网址(Website)的定义是因特网上网页的地址。域名(Domain Name)是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识。IP地址虽然能唯一地标记网络上的计算机,但记忆不方便,所以提出使用域名地址来替代。IP地址和域名是一一对应的,这份域名地址的信息存放在域名服务器(DNS,Domain Name Server)的主机内,其对应的转换工作就交给了域名服务器。域名的入网结构:主机名、机构名、网络名、最高层域名。下面举几个例子,
URL(Uniform Resource Locator) | 顶级域名(Top-level Domain Name) | 二级域名(Second-level Domain Name) | 三级域名(Third-level Domain Name) | 域名(Domain Name) |
---|---|---|---|---|
http://www.baidu.com/ | com | baidu.com | www.baidu.com | www.baidu.com |
http://m.hao123.com/ | com | hao123.com | m.hao123.com | m.hao123.com |
http://www.people.com.cn/ | cn | com.cn | people.com.cn | www.people.com.cn |
域名由两组或两组以上的ASCII码或各国语言字符构成各组字符间由点号分隔开,最右边的字符组称为顶级域名(一级域名),倒数第二组为二级域名,以此类推。
比如使用因特网包探索器Ping向百度(www.baidu.com)发送ICMP请求报文,返回的数据包里会有IP地址、存活时间(Time To Live,数据包被路由器丢弃之前允许通过的网段数量,初始值可以ping本机localhost,本机ttl=64,到达百度服务器ttl=53,说明中转了64-53=11次到达百度服务器)等。
看到上面百度的IP地址是14.215.177.38,那么在浏览器中输入此IP地址可以访问直接百度。这是因为直接访问IP地址则不需要经过DNS服务器解析域名。但如果使用人民网的IP地址219.138.180.22输入到浏览器中为什么不能直接访问呢?其实在浏览器中输入IP地址后,它会默认的端口号80(Http协议默认端口号)。你访问百度套接字Socket(14.215.177.38:80)是一样的效果。端口号只具有本地意义,即端口号只是为了标志本计算机在应用层中的各进程。端口号分下面几种:
服务器端使用的端口号 | 客户端使用的端口号 | |
---|---|---|
熟知端口号 | 登记端口号(IANA登记) | 短暂端口号 |
数值:0~1023 | 数值:1024~49151 | 数值:49152~65535 |
下面给出常用的熟知端口,比如HTTP默认端口80,Telnet默认端口23,HTTPS默认端口443等等。仔细观察,大家会发现百度和人民网都是使用的443端口,而hao123才是使用的80端口。
补充一下,上面的IP和UDP/TCP分别是网络层和传输层的协议,国际标准化组织制定的网络标准体系OSI七层模型中的两层,目前主流的TCP/IP体系的四层模型中也有这关键的两层,在底层的数据被俄罗斯套娃一样的封装起来,下层实现不了“不重复,不丢失,不失序”的任务则交由上层去完成,如下图,
有人可能就说了,现在还是没有搞清楚为什么人民网的IP地址加端口号就不能访问了呢?那么我们打开开发者工具去看看请求www.baidu.com是不是按我们想的那样请求的http协议+80端口呢?打开开发者工具然后去看请求百度的网页是用的https协议+443端口!如下图。我们使用套接字14.215.177.38:443直接在浏览器访问应用进程是访问不通的,因为https是安全性的加密协议,需要客户端传上一些必要的信息,比如请求头里带上了cookies。
其实这里百度之前是用的http协议的,为了防止黑客攻击,它们采用了更为安全的https协议。输入网址http://www.baidu.com/也能访问是为了兼容原来的页面请求,最终重定向请求的地址仍然是https://www.baidu.com/。既然百度是做了重定向,那么就涉及到跨域问题。浏览器的安全功能中具有同源策略(Same Origin Policy),即会阻止一个域的javascript脚本和另外一个域的内容进行交互,只要是请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。这里重定向的协议不同,所以百度是处理过跨域的问题。人民网也是如此。细心的小伙伴会发现每次访问时Remote Address可能是不一样的IP,这其实是因为它们的服务器不止一台,采用的服务器集群提高服务的可用性,所以每次访问请求是随机请求到其中的一台服务器。
缓存
拿谷歌浏览器为例,输入chrome://net-internals/#dns可以查看各DNS信息,chrome对每个域名默认缓存60s。
客户端进行访问时是先去查看浏览器DNS缓存,再去看操作系统DNS缓存,最后看Hosts文件的DNS配置。
有经验的小伙伴应该都知道我们有时候不想直接去访问一些不太容易记住的私网地址,可以在/etc下的hosts文件添加一些自己的域名,然后直接在浏览器上访问,如下图,
这里需要大家注意的一点是:DNS的缓存和浏览器的缓存是不一样的概念,DNS的缓存——缓存的是域名解析的内容,浏览器的缓存——缓存的是请求服务器后返回的请求结果和缓存标识,这里小伙伴不要搞混了。浏览器中的缓存分四种Service Worker、Memory Cache、Disk Cache、Push Cache,都没有命中才去请求网络,如果使用了内容分发网络(CDN,Content Delivery Network)除了浏览器缓存也会有静态资源的缓存,可参看:https://blog.csdn.net/csdnnews/article/details/89324384
DNS解析过程
DNS域名解析过程如下图,浏览器先向本地DNS服务器发起请求,如果本地没有缓存记录则不能直接将域名转换成IP地址,需要采用递归或迭代查询的方式依次向根域名服务器、顶级域名服务器(一级域名服务器)、二级域名服务器发起查询请求并获取IP地址组返回给浏览器。有的人写的是13台根域名服务器,其实这个是错误的理解,书上定义的是13套根域名服务器,服务器台数远超过1000台,其中根域名服务器可在互联网数字分配机构(IANA,The Internet Assigned Numbers Authority)的网站上获取:http://www.internic.net/domain/root.zone。
mac可用nslookup www.baidu.com命令查看域名解析情况。
TCP三报文握手(TCP连接建立)
上面的域名解析让客户端最终找到了要请求的服务器的地址了,那么我们就要为通信做准备了。一个网址前面的是HTTP协议或者HTTPS协议,这是属于应用层的协议,但是上层协议依赖于下层协议的给过来的数据,那么传输层到底是TCP还是UDP呢?我们都知道TCP是面向连接的协议,通信前需要建立虚电路以确保双方通信的网络资源,提供全双工的可靠通信。而UDP是无连接的、不可靠的,不需要在通信前建立连接,直接发送UDP报文即可,可以一对一、一对多、多对一、对对多的进行通信。针对不同的需求它们有各自的应用场景,不过HTTP为了可靠传输通常都是在传输层使用TCP来完成。那么就涉及到TCP的三次握手,这个过程如下图
- 客户端向服务器端发出连接请求报文段,其首部中的同步位SYN=1,确认位ACK=0,并选择序号seq=x,表明传送数据时的第一个数据字节序号是x。
- 服务端接收到客户端的连接请求报文段后,服务端给客户端发送确认报文段,SYN=1,ACK=1,ack=x+1,并且自己发送自己的序号seq=y。
- 客户端收到了服务端的请求报文段后给出确认,即ACK=1,ack=y+1。
为什么要进行三次握手呢?那是为了防止已经失效的连接请求报文段突然又传到了服务器,因而产生错误。还有,有的小伙伴可能对上面的标志位和序号啥的忘记的差不多了,可以回过头去看看《计算机网络》,在传输层TCP报文段也是通过首部部分+数据部分构成。TCP报文段结构如下图,
下面解释一下TCP首部中各字段的含义:
- 源端口和目的端口:对应的应用进程的端口号,当然在实际传输中端口号只具有本地意义,所以有源IP地址和目的IP地址才有用,不过这些信息是封装在网络层的IP首部。源端口和目的端口各占2个字节。
- 序号:TCP发送的数据部分的第一个字节的序号。
- 数据偏移:占4比特,表明数据开始的地方离TCP报文段起始的位置的偏移量,也可以看作是TCP首部的长度,TCP首部前面20个字节固定,后面有一些可变的字段。
- URG标志位:即urgency的缩写,为1时紧急指针有效,告诉系统报文段中有紧急数据,应尽快传送。URG=0时,紧急指针无效。
- ACK标志位:ACK=1时确认号字段有效,当ACK=0时确认号字段无效。
- PSH标志位:接收到TCP的PSH=1的报文段时,就将缓存的数据直接推送给应用进程。默认PSH=0,等到整个缓存填满后再向应用进程交付。
- RST标志位:RST=1表明TCP出现差错,必须释放链接,重新再建立传输连接。
- SYN标志位:SYN=1表示这是一个连接请求或连接请求报文。
- FIN标志位:表明此报文的发送端的数据已发送完毕,并要求释放连接。
- 窗口:占2字节,用来让对方设置发送窗口的依据。由于停止等待协议的信道利用率太低,TCP采用流水线传输方式,通过滑动窗口协议(连续ARQ协议)进行分组发送,于是就有了发送窗口和接收窗口,发送窗口/接收窗口大小小于等于发送缓存/接收缓存大小。
- 校验和:校验和字段的检验范围包括首部和数据两部分。在计算校验和时,要在TCP报文段的前面加上12字节的伪首部(含源IP地址和目的IP地址)。
- 紧急指针:占2个字节,指出本报文段中紧急数据共有多少字节。
- 选项:长度可变,前面20字节固定,后面一些额外信息。TCP最初只规定了一种选项——最大报文段长度MSS(Maximum Segment Size),后面有窗口扩大选项、时间戳选项、选择确认选项等。
TCP协议是全双工模式,客户端既可以做发送方也可以做接收方,发送数据的同时也能接收数据。而且TCP协议的设计里是在逻辑正确的前提条件下提高效率,所以使用了滑动窗口协议,发送方和接收方各自有各自的发送窗口和接收窗口。因为接收到信号后要确认,不能让一方一直等待,所以增加了超时重传机制并设置合理的超时重传时间(自适应算法设置的超时重传时间)。自己可以使用sudo tcpdump host 本机IP地址(ifconfig查看)去抓包看TCP数据报文段的一些信息。
总结一下TCP为了建立可靠的数据连接所做的包含:
- 滑动窗口机制:基于序号的分组发送,发送窗口+接收窗口。
- 确认机制:发送数据后通过接收ACK确认包。
- 流量控制:让发送数据速度不要过快,让接收方来得及接收,利用滑动窗口机制可以实现。
- 拥塞控制:对资源的需求的总和大于可用资源则会造成拥塞。TCP拥塞控制采用基于窗口的方法,TCP发送方维持一个可控的拥塞窗口(CWnd,Congestion Window)真正的发送窗口值=Min(接收窗口值,拥塞窗口值)。
- 超时重传机制:设置自适应算法的超时重传时间。
HTTP请求报文和HTTP响应报文
上面建立了TCP连接之后,客户端和服务端就有了一条传输信道,然后客户端发送HTTP请求报文,服务器端响应HTTP请求报文,如下图。HTTP1.0版本协议是无状态的(Stateless),即在交互时没有记忆能力。HTTP1.1版本协议在前面的基础上增加持续连接(Persistent Connection),因为如果每一次请求都需要建立TCP和释放TCP连接,那么对系统资源极大浪费,所以通过设置一个持续连接时间来保证连接的一定时间维持,HTTP请求头里会有Connection: keep-alive参数表示默认支持持续连接的。HTTP1.2版本协议更是增加了多路复用机制,即对服务器发送了多个请求报文时,它们是使用了复用机制来提高平均响应速度。
另外,为了提高整个网络的性能,还使用了代理服务器(Proxy Server),也叫万维网高速缓存(Web Cache),如下图,左边网络不会直接把请求通过路由打到右边网络,而是先和代理服务器建立TCP连接,把请求打到代理服务器那边,然后代理服务器把请求统一转发到服务器网络中。代理服务器缓存最近的一些请求暂存在本地磁盘中。除此之外,有些为了提高网络响应效率增加了CDN(Content Delivery Network)服务器,CDN即用来存放一些图片、css样式等静态资源。
TCP四报文握手(TCP连接释放)
- 发送数据完毕之后,客户端向服务端发送连接释放请求报文段,结束位FIN=1,序号seq=u。
- 服务端接收到连接释放请求报文段后,向客户端发送确认报文段,ack=u+1,自己的序号seq=v,并且通知高层应用进程从客户端到服务端方向的连接释放了(表明客户端没有要向服务端发送的数据了)。
- 服务端向客户端发送连接释放请求报文段,结束位FIN=1,确认位ACK=1,序号seq=w,ack=u+1。
- 客户端收到连接释放请求报文段后,发出确认。
需要注意的点:
- 有人可能在想TCP四报文握手(挥手)为什么不能把2、3步骤合并在一起呢?因为客户端和服务端的连接释放不是同时的,所以确认报文段和连接释放请求报文段是分开的两个报文段。
- 第四步骤在客户端向服务端发送确认报文段后,客户端等待两个传输报文的时间(2MSL)后服务端都没有发任何重复的数据包过来则TCP连接才真正释放掉。因为如果对方收到了客户端的确认报文段的话就不会再发送任何数据报,双方通信时设置了超时重传时间,超过服务端的超时重传时间服务端会认为对方没有收到数据而重发。设置等待2MSL可以应对SYN flood攻击。
- 上面的TCP连接建立和TCP连接释放里的状态构成了闭环的TCP的有限状态机,有兴趣的可以自己去看一下。
结语
有朋友说了CSDN算是比较不入流的社区,相比较Stack Overflow、SegmentFault等社区可能整体文章质量水平不是很高(类似于IEEE Transaction期刊和中国知网上的文章对比),但不是说所有的文章都不好,有一些还是有一定的参考价值吧,需要大家以自己的知识储备去判断理解,有一些东西别人可能写的并不对,如果看到别人写的不好的地方,你也可以发表自己的见解。我算是个不太会表达的人,但是对于某些东西也还是想表达一下自己的看法。
谁都知道和越优秀的人在一起,自己成长越快的可能性就越高,但是不要一棒子打死。就像之前听别人说“Java是最好的语言”,“python是最好的语言”,“go是最好的语言”... ...,我觉得你是把所有语言都学透了才得出这样的结论吗?如果只是学了一点皮毛就得出这样的结论不是很可笑嘛。
所以,我也不保证我自己上面这篇文章是完全正确的,需要大家以自己的基础知识加上实践来检验,如有错漏之处,敬请指正!