作为一个软件开发者,你一定会对网络应用如何工作有一个完整的层次化的认知,同样这里也包括这些应用所用到的技术:像浏览器,HTTP,HTML,网络服务器,需求处理等等。
本文将更深入的研究当你访问一个URL时,到底发生了一件件什么样的事。
1.输入URL地址
一切将从这里开始
2.浏览器根据域名查询IP地址
第一步是根据一定步骤从要访问的域名中获取IP地址,DNS查询的步骤如下:
1. 从浏览器缓存中查询。浏览器会存储一定时间的DNS记录,操作系统不会告诉浏览器每个DNS记录的保存时限,不同浏览器设置保存时限为一个固定值(不同浏览器情况不同,一般在2-30分钟)。
2. 从操作系统缓存中查询。如果浏览器中没有包含想要的缓存记录,那浏览器就会发起操作系统请求,继续查询操作系统缓存
3. 从路由器中查询DNS缓存。请求持续发送到你的路由,它通常会有自己的DNS缓存。
4. 从ISP中查询DNS缓存。下一个被查询地方是ISP缓存DNS的服务器。
5. 域名服务器递归查询。首先从root域名服务器中查询如.com域名服务器,然后逐步向前查询,.com顶级域名服务器到Facebook的域名服务器。一般来说,.com级别的都已经在缓存中了,所以一般不会进行对root域名服务器的查询。下面给出一张递归查询的图。
往往像wikipedia.org或者facebook.com整个域都映射到不止一个IP地址,幸运的是我们有一些解决方法
- Round-robin DNS,DNS轮询是其中一种方法,是DNS查找时返回多个IP时的解决方案。举例来说,Facebook.com实际上就对应了四个IP地址。
- Load-balancer,大型的网站一般都会使用高性能的负载均衡器来平衡流量。负载均衡器一直监听一个特殊的IP地址,并转发请求到其他的服务器。(译者注:简单粗暴点理解就是在用户和服务器之间加了个中间层,利用监听和转发请求达到用户相对快速访问,资源最优化使用的目的)
- Geographic DNS,基于地理的DNS,依赖客户端的地理位置,这是一个很好的存储静态资源的方法,不同的服务器可以不更新共享状态。
- Anycast,一种单个IP地址映射多个物理服务器的技术。
3. 浏览器发送HTTP请求到web服务器
因为Facebook的首页是动态变化的,所以我们肯定无法从浏览器缓存中获取整个首页。所以浏览器将会发送请求到Facebook的服务器
GET http://facebook.com/ HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...]
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...]
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: facebook.com
Cookie: datr=1265876274-[...]; locale=en_US; lsd=WW[...]; c_user=2101[...]
向“http://facebook.com/”发出GET请求,浏览器用User-Agent header说明自己的身份,Accept和Accept-Encoding header表明其接收何种形式的响应,Connection header要求服务器为了进一步的请求发送保持TCP连接。 请求当然少不了浏览器在这个域下保存的cookies,cookies是保留不同网站状态的键值对,常常保存着用户登录信息,用户在服务器保存的密码,一些用户设置等等。cookies将会被保存在客户端的一个文件里,它会被每次请求带到服务器。
看原始HTTP请求及其相应的工具很多。我最喜欢使用的是fiddler,当然也有像FireBug这样其他的工具。这些软件在网站优化时会帮上很大忙。
除了获取请求,还有一种是发送请求,它常在提交表单用到。发送请求通过URL传递其参数(e.g.: http://robozzle.com/puzzle.aspx?id=85)。发送请求在请求正文头之后发送其参数。
在URL末尾的"/"是很重要的,在"http://facebook.com/"例子中,浏览器能够安全的添加"/"。但对于表单的URLs"http://example.com/folderOrFile"来说,浏览器不能自动添加斜杠,因为并不清楚folderOrFile是一个文件夹还是文件,在这样的例子中,浏览器将会先不带"/"访问这个URL,然后服务器会回应一个重定向,导致一个不必要通信来回。
4. Facebook服务器返回一个永久重定向响应
Facebook服务器会回应浏览器请求发送回一个响应
HTTP/1.1 301 Moved Permanently
Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Expires: Sat, 01 Jan 2000 00:00:00 GMT
Location: http://www.facebook.com/
P3P: CP="DSP LAW"
Pragma: no-cache
Set-Cookie: made_write_conn=deleted; expires=Thu, 12-Feb-2009 05:09:50 GMT;
path=/; domain=.facebook.com; httponly
Content-Type: text/html; charset=utf-8
X-Cnection: close
Date: Fri, 12 Feb 2010 05:09:51 GMT
Content-Length: 0
服务器会发送一个301永久重定向响应来告诉浏览器访问“http://www.facebook.com/而不是“http://facebook.com/”。为什么服务器坚持重定向而不是直接给予浏览器用户需要的结果,这有很多有意思原因。
一个原因是搜索引擎排名,如果有两个URLs指向同一个页面,比如http://www.igoro.com/ 和http://igoro.com/,搜索引擎会认为这是两个不同的网站,结果他们两个每个都有一部分访问量,但是也只能拥有更低的搜索引擎排名。如果使用了301定位,搜索引擎将会识别重定向,进而将同一来源的多个链接算作一个。
另一个原因是,同样的内容多个URLs还不利于缓存,同样的内容拥有多个名字,潜在造成缓存浪费。
5.浏览器会跟踪重定向地址
浏览器知道了“http://www.facebook.com/” 是真正应该访问的URL,所以就发送了另外一个GET请求
GET http://www.facebook.com/ HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...]
Accept-Language: en-US
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...]
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Cookie: lsd=XW[...]; c_user=21[...]; x-referer=[...]
Host: www.facebook.com
这个头的含义与第一个请求相同
6.服务器处理请求
服务器会接收这个GET请求,并且返回一个响应结果
这可能看着像一个很简单的任务,但即使我们在访问一个很简单网站(像我们的博客一样),在这个过程中实际上也会发生很多有趣的事情,更不用说访问Facebook这样大访问量的网站了
- web服务器软件
web服务器软件(像apache和IIS等)接收HTTP请求并决定使用哪个请求处理程序处理这个请求。一个请求处理程序是能够读取请求并且为响应结果生成HTML的一段程序(ASP.NET, PHP, Ruby, …)
在最简单的例子中,这个请求处理程序会储存在一个文件中,而这个文件的分布会像URL的结构的投影一样,比如说http://example.com/folder1/page1.aspx 这个URL就映射着路径为/httpdocs/folder1/page1.aspx的文件,web服务器软件能配置为将每个URLs手动映射到请求处理程序,所以page1.aspx的公有URL应该是http://example.com/folder1/page1.
- 请求处理程序
请求处理程序读取请求和参数以及cookies。它会读取以及可能更新一些存储在服务器的数据,接着请求处理程序会生成一个HTML响应
怎么存储数据是每个动态网站都会面临的有趣难题
小网站会经常有一个SQL数据库来存储他们的数据,但是网站存储数据量过大或者流量过大后就必须将数据库分布在多台机器,解决的方法有很多种
包括sharding(在主键基础上划分表到多个数据库中),复制和使用简化的弱语义一致性数据库
推迟一些任务到批处理作业是廉价保持数据更新的的一种技术。
例如,Facebook必须尽快更新新闻供应,但数据支持的“你可能认识的人”功能可能只需要每晚进行更新(作者猜测是这样)。批处理作业的更新导致存在一些旧的相对不重要的数据,但是使数据更新更快更简单。
7.服务器返回一个HTML响应
这里有一个服务器生成和送回的响应
HTTP/1.1 200 OK
Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Expires: Sat, 01 Jan 2000 00:00:00 GMT
P3P: CP="DSP LAW"
Pragma: no-cache
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
X-Cnection: close
Transfer-Encoding: chunked
Date: Fri, 12 Feb 2010 09:05:55 GMT
...
整个完整的响应是36KB,其中大部分处理后由blob类型传送 内容编码头部告诉浏览器响应体使用了gzip压缩算法。在解析blob后,你就会看到你期望的HTML了
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en" id="facebook" class=" no_js">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-language" content="en" />
...
除了压缩信息之外,头部还详细说明了是否和怎么缓存页面、设置cookies(在这个响应中没有)、隐秘信息等
或许有人注意到了设置了内容类型为text/html,这部分头部说明了浏览器将响应内容作为HTML渲染,而不是作为文件下载。浏览器将使用头部决定如何解释响应结果,当然也会考虑其他因素,比如URL的扩展情况
8.浏览器开始渲染HTML
在浏览器接收完整HTML文件前,浏览器就开始渲染页面了
9.浏览器发送嵌入在HTML中的对象的请求
随着浏览器渲染HTML,浏览器会注意到有些标签需要请求其他URLs的资源,浏览器将会发送一个GET请求来重新获取每个文件 下面是一些我们访问Facebook.com获取的文件
- Images http://static.ak.fbcdn.net/rsrc.php/z12E0/hash/8q2anwu7.gif http://static.ak.fbcdn.net/rsrc.php/zBS5C/hash/7hwy7at6.gif …
- CSS style sheets http://static.ak.fbcdn.net/rsrc.php/z448Z/hash/2plh8s4n.css http://static.ak.fbcdn.net/rsrc.php/zANE1/hash/cvtutcee.css …
- JavaScript files http://static.ak.fbcdn.net/rsrc.php/zEMOA/hash/c8yzb6ub.js http://static.ak.fbcdn.net/rsrc.php/z6R9L/hash/cq2lgbs8.js …
每个URLs会像获取HTML页面的过程一样获取相应资源。所以,浏览器会在DNS中查询域名,并向URL发送请求,进行重定向等等以上步骤
当然,静态文件和动态网站不一样,它们允许被浏览器缓存。一些文件可能会根本不经过服务器,直接被从缓存中取出。因为响应结果中返回一个包含着Expires头的文件,所以浏览器知道要缓存一个文件多久。另外每个响应可能包含着ETag头,其作用类似版本号,如果浏览器发现已经拥有了一个文件的ETag,那么就会立即停止此文件传输。
你能猜到“fbcdn.net”在这个URLs代表着什么吗?一个保险的说法是“Facebook内容分发网络”。Facebook使用CDN来部署静态资源--图片、样式表、JS文件,所以这些文件将被复制到遍布全球的CDN机器上。
静态资源通常占用了一个网站大部分的带宽,通过CDN也很容易获得。通常,网站会使用第三方提供者的CDN,而不是自己运作一个CDN。例如,Facebook的静态文件是委托给最大的CDN提供者Akamai
作为示范,当你ping static.ak.fbcdn.net时,你将得到一个从akamai.net服务器来的响应结果。有趣的是,如果你ping这个URL两次,你可能会从不同的服务器得到响应结果,这也正是幕后负载平衡的作用的结果。
10.浏览器发送异步请求
在web2.0时代,即使在页面渲染后客户端还是持续与服务器端通信。
例如,当你的朋友上线或下线时,Facebook聊天功能将会持续更新你已经登录的朋友列表。为了更新这个列表,你浏览器上运行的JS将会发送异步请求到服务器,异步请求是发送给特殊URL的GET或POST请求。在Facebook的例子中,客户端会发送一个POST请求到http://www.facebook.com/ajax/chat/buddy_list.php,获取你在线的朋友列表
这个模式被称为AJAX,是“Asynchronous JavaScript And XML”,的缩写,虽然不太清楚为什么服务器必须将响应格式化为xml。再举个例子,在回应异步请求时,Facebook会返回JS代码片段。
除了其他的,fiddler这个工具能够让你看到浏览器发送的异步请求。事实上,你不是只能被动的观察这些请求,还能主动修改和重新发送它们。AJAX请求这么容易被蒙,可着实让那些计分的在线游戏开发者们郁闷的了。(当然,可别这样骗人家)
Facebook聊天功能提供了一个很有意思的关于AJAX的问题案例:把数据从服务器端推送到客户端。因为HTTP是一个请求-响应协议,所以聊天服务器不能把新消息发给客户。取而代之的是客户端不得不隔几秒就轮询下服务器端看自己有没有新消息。
在这些情况下,长轮询是减轻服务器负载的有用技术。如果当被轮询时服务器没有新消息,它就不理这个客户端请求。而当尚未超时的情况下收到了该客户的新消息,服务器就会找到未完成的请求,把新消息做为响应返回给客户端。
总结
希望这篇文章给了你一些有关不同web模块合作的更好思路。
到此原文就正式结束了,下面是我个人的一些补充和想法
总结一下上文十个步骤:
1. 输入URL 2. 浏览器查询域名IP地址 3. 浏览器发送HTTP请求到服务器 4. 服务器发送重定向到浏览器 5. 浏览器重新发送HTTP请求到重定向地址 6. 服务器处理HTTP请求 7. 服务器发送响应到浏览器 8. 浏览器根据响应结果渲染页面 9. 浏览器发送请求获取嵌入HTML页面对象请求 10. 浏览器发送异步请求
扩展
一:什么是DNS?
DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。
通俗的讲,我们更习惯于记住一个网站的名字,比如www.baidu.com,而不是记住它的ip地址,比如:167.23.10.2。而计算机更擅长记住网站的ip地址,而不是像www.baidu.com等链接。因为,DNS就相当于一个电话本,比如你要找www.baidu.com这个域名,那我翻一翻我的电话本,我就知道,哦,它的电话(ip)是167.23.10.2。
二:DNS查询的两种方式:递归查询和迭代查询
1、递归解析
当局部DNS服务器自己不能回答客户机的DNS查询时,它就需要向其他DNS服务器进行查询。此时有两种方式,如图所示的是递归方式。局部DNS服务器自己负责向其他DNS服务器进行查询,一般是先向该域名的根域服务器查询,再由根域名服务器一级级向下查询。最后得到的查询结果返回给局部DNS服务器,再由局部DNS服务器返回给客户端。
2、迭代解析
当局部DNS服务器自己不能回答客户机的DNS查询时,也可以通过迭代查询的方式进行解析,如图所示。局部DNS服务器不是自己向其他DNS服务器进行查询,而是把能解析该域名的其他DNS服务器的IP地址返回给客户端DNS程序,客户端DNS程序再继续向这些DNS服务器进行查询,直到得到查询结果为止。也就是说,迭代解析只是帮你找到相关的服务器而已,而不会帮你去查。比如说:baidu.com的服务器ip地址在192.168.4.5这里,你自己去查吧,本人比较忙,只能帮你到这里了。
3、DNS域名称空间的组织方式
我们在前面有说到根DNS服务器,域DNS服务器,这些都是DNS域名称空间的组织方式。按其功能命名空间中用来描述 DNS 域名称的五个类别的介绍详见下表中,以及与每个名称类型的示例
4、TCP三次握手
第一次握手:客户端A将标志位SYN置为1,随机产生一个值为seq=J(J的取值范围为=1234567)的数据包到服务器,客户端A进入SYN_SENT状态,等待服务端B确认;
第二次握手:服务端B收到数据包后由标志位SYN=1知道客户端A请求建立连接,服务端B将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端A以确认连接请求,服务端B进入SYN_RCVD状态。
第三次握手:客户端A收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务端B,服务端B检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端A和服务端B进入ESTABLISHED状态,完成三次握手,随后客户端A与服务端B之间可以开始传输数据了。
5、为什需要三次握手?
《计算机网络》第四版中讲“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”
书中的例子是这样的,“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。
假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”。主要目的防止server端一直等待,浪费资源。
6、TCP四次挥手
第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
7、为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。
8、301和302的区别。
301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)——这是它们的共同点。
他们的不同在于。301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址换为重定向之后的网址;
302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。 SEO 302好于301
9、重定向原因:
(1)网站调整(如改变网页目录结构);
(2)网页被移到一个新地址;
(3)网页扩展名改变(如应用需要把.php改成.Html或.shtml)。
这种情况下,如果不做重定向,则用户收藏夹或搜索引擎数据库中旧地址只能让访问客户得到一个404页面错误信息,访问流量白白丧失;再者某些注册了多个域名的网站,也需要通过重定向让访问这些域名的用户自动跳转到主站点等。
10、什么时候进行301或者302跳转呢?
当一个网站或者网页24—48小时内临时移动到一个新的位置,这时候就要进行302跳转,而使用301跳转的场景就是之前的网站因为某种原因需要移除掉,然后要到新的地址访问,是永久性的。
清晰明确而言:使用301跳转的大概场景如下:
1、域名到期不想续费(或者发现了更适合网站的域名),想换个域名。
2、在搜索引擎的搜索结果中出现了不带www的域名,而带www的域名却没有收录,这个时候可以用301重定向来告诉搜索引擎我们目标的域名是哪一个。
3、空间服务器不稳定,换空间的时候。
11、什么是反向代理?
客户端本来可以直接通过HTTP协议访问某网站应用服务器,网站管理员可以在中间加上一个Nginx,客户端请求Nginx,Nginx请求应用服务器,然后将结果返回给客户端,此时Nginx就是反向代理服务器。
12、浏览器渲染过程
详情可以看另一篇文章从web浏览器的渲染到性能优化