浏览器与服务器响应流程
前言
大三注定是一段不安定的日子。恰逢腾讯实习生招聘,这么好的机会,自己也是不甘人后,心里也很想通过这次机会来检验自己的学习成果的,年轻人,总得试试看。虽然最终进不了二面,但还是有所收获的,写下一些经验看法,就当作是为未来攒RP吧。1. 不得不佩服大公司的面试流程很规范,虽然去笔试的时候,出现了来霸笔的同学拿不到试卷的情况(估计tx也没想到来霸笔的人会有这么多),其他一切还是挺好的。2. 这次笔试的内容主要考了C,数据结构和算法,操作系统,计算机网络,软件工程(印象中没有考数据库,不过个人感觉数据库也挺重要的),都是基础,难度不是很大,可见大公司还是非常重视基础的,我自己在做题的过程中,还是感觉基础不够扎实,特别是C这一块,还有一些其他基本概念也没有记牢,本以为笔试就不过的,后来发短信告诉我过了,我还是比较惊讶的(也许我还是有点基础的,哈)。笔试的附加题是关于海量数据的处理的,第一道题是说QQ有30亿用户,有一个log文件是专门来储存QQ登录记录的,记录行包括QQ号码,登录时间,登出时间,IP地址。这个log文件非常大(具体我也忘了题目说的有多大),有一小题是这样问的:如何找出一天登录的用户有多少(不重复),大概需要多少内存空间。我的大概思路是这样的(没看过正确答案,估计这种开放题也没有所谓的正确答案,如果你有更好的实现方案,欢迎留言告诉我!):根据登录时间找出所有当天登录用户QQ号码 --> 剔除重复项 --> 统计个数。至于需要多少内存空间,我觉得统计的时候应该是在内存中统计的,所以我使用了位图的数据结构,因为QQ号码有一个特性:唯一不重复数字,这样就可以用位置来表示QQ号码,第1个比特位表示QQ号码0,...第N个比特位表示QQ号码N-1,如果这个号码在当天登录就把这个比特位记为1,没有就为0,所以大概需要(30*10^9/8)个字节的存储内存。3. 整个面试感觉自己的表达能力还行,也不是很紧张,但出现了两个致命伤,一个是问数据库物理索引和聚集索引区别是什么(这个我是真的没接触过),另一个是问TCP/IP三次握手的(!!我竟然回答我忘了,天呐,我真的忘了,其实去面试的时候我基本只准备了数据结构,结果数据结构一点也没有问!),对后一个问题的回答基本宣告自挂东南枝了。(所以这篇文章的目的更多是计算机网络方面的,也算是亡羊补牢吧。)4. .NET是我接触比较多的技术,但现实情况是大公司招.NET的真的比较少(甚至不招),所以对于立志进大公司的人来说,.NET还是慎入,毕竟靠.NET的经历进大公司难度更大。
不知不觉就吹水了这么多,还是直接进入主题吧。当我们在浏览器中输入了一个URL地址(如www.cnblogs.com),从等待到页面显示的过程中,后台究竟发生了什么处理呢?
一. 解析域名地址为IP地址
浏览器DNS缓存:以Chrome为例,在浏览器窗口中输入chrome://net-internals/#dns,就可以查看当前浏览器DNS缓存记录,chrome的DNS缓存过期时间还是比较短的,大约为1分钟。
本机DNS缓存:在Windows命令行模式下输入ipconfig /displaydns,就可以查看本机DNS缓存记录。许多主机在启动时从本地域名服务器下载域名和地址的全部数据库,维护存放自己最近使用的域名的缓存。
本地域名服务器:在Windows命令行模式下输入ipconfig /all,就可以查看本地DNS服务器的IP地址,一般而言本地域名服务器都是由ISP提供,主机通过UDP和本地域名服务器通信。如果在本地域名服务器高速缓存中搜索不到要转换的域名,就向更高级别的域名服务器发起迭代查询,按根域名服务器(一般查询根域名服务器都是遵循就近原则,中国有3个根服务器,位置分别为北京,香港,台北)-> 顶级域名服务器-> 权限域名服务器的顺序查询。假如本地域名服务器高速缓存中存在顶级域名服务器.com的IP地址,那么本地域名服务器可以不向根域名服务器进行查询,而是直接向com顶级域名服务器发送UDP请求报文,这样就可以大大减轻根域名服务器的负荷。维护本地域名服务器数据库的主机自然应该定期地检查域名服务器以获取新的映射信息,而且主机必须从缓存中删掉无效的项。
二. TCP 连接建立
由上一步获取www.cnblogs.com的IP地址42.121.252.58后,客户端主机就会选择一个未使用的端口与42.121.252.58:80通信,在Windows命令行模式下输入netstat,就可以查看当前正在活动的TCP连接。
Client首先发送一个连接试探,ACK=0 表示确认号无效,SYN = 1 表示这是一个连接请求或连接接受报文,同时表示这个数据报不能携带数据,seq = x 表示Client自己的初始序号。
Server监听到连接请求报文后,如同意建立连接,则向Client发送确认。TCP报文首部中的SYN 和 ACK都置1 ,ack = x + 1表示期望收到对方下一个报文段的第一个数据字节序号是x+1,同时表明x为止的所有数据都已正确收到,seq = y 表示Server 自己的初始序号。
Client收到确认后还需再次发送确认,同时携带要发送给Server的数据。ACK 置1 表示确认号ack= y + 1 有效,Client自己的序号seq= x + 1。
三. 浏览器给服务器发送一个http请求
上一步中有提到Client收到确认后还需再次发送确认,这时就可以携带要发送给Server的数据,这个数据就是HTTP请求报文。
HTTP请求行:GET http://www.cnblogs.com/ HTTP/1.1
HTTP请求首部:
Accept: | text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 |
Accept-Charset: | GBK,utf-8;q=0.7,*;q=0.3 |
Accept-Encoding: | gzip,deflate,sdch |
Accept-Language: | zh-CN,zh;q=0.8 |
Connection: | keep-alive |
Cookie: | 省略 |
Host: | www.cnblogs.com |
If-Modified-Since: | Mon, 22 Apr 2013 09:12:11 GMT |
User-Agent: | Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.21 (KHTML, like Gecko) Chrome/25.0.1354.0 Safari/537.21 |
这里重点提一下Connection。
Connection设置为keep-alive,就是Server在发送响应后仍然在一段时间内保持这条连接,使同一Client可以继续在这条连接上传送后续的HTTP请求报文和响应报文。对于不同的web服务器,如IIS,Apache,都有不同的keep-alive 过期时间,当然如果过期时间太短,就达不到重用连接的效果,如果过期时间太长,则会造成占用资源的坏处。
四. 服务器给浏览器发送一个http响应
当Server接收到http请求的时候,就把请求交给专门的web服务器处理,并最终把数据发送给Client,这个数据就是HTTP响应报文。
HTTP状态行:HTTP/1.1 200 OK
HTTP响应首部:
Cache-Control: | public, max-age=62 |
Connection: | keep-alive |
Content-Encoding: | gzip |
Content-Type: | text/html; charset=utf-8 |
Date: | Wed, 24 Apr 2013 08:14:03 GMT |
Expires: | Wed, 24 Apr 2013 08:15:04 GMT |
Last-Modified: | Wed, 24 Apr 2013 08:13:04 GMT |
Server: | Tengine |
Transfer-Encoding: | chunked |
Vary: | Accept-Encoding |
X-AspNet-Version: | 4.0.30319 |
X-AspNetMvc-Version: | 3.0 |
X-Powered-By: | ASP.NET |
X-UA-Compatible: | IE=edge |
最后就是HTTP响应主体内容--html文档了,如果需要继续获取嵌套在html中的对象,且这些对象都存在于同一个服务器上时,这些HTTP请求就会重用现存TCP连接。
五. TCP 连接释放
在正常情况下,服务器端的keep-alive过时了,就会主动发出请求释放这条TCP连接。
Server发送一个连接释放报文,FIN = 1 表示Server的数据已发送完毕,seq = v ,v等于前面已传送过的数据的最后一个字节加1。
Client收到Server的连接释放请求后,发送一个确认报文。ACK = 1 表示确认后ack有效,ack = u+1表示期望收到对方下一个报文段的第一个数据字节序号是u+1 ,seq = v , v等于前面Client已传送过的数据的最后一个字节加1。
由于HTTP协议是基于请求-响应模型,所以这时Client再发送请求数据给Server已经无效了,因为Server到Client的TCP连接已关闭,不会再发送响应了。这里的Close-Wait大概是等待主机通知关闭这次TCP连接。
接下来Client就发送一个连接释放给Server,FIN=1,ACK=1,ack = u + 1 与之前发送给Server的确认号一样。这里的seq = v 也与之前的一样。(个人认为Close-Wait期间Client不会再发送数据给Server,所有数据序号并没有发生改变,正确与否有待考证)
Server收到连接释放报文后,便发送一个确认报文。然后进入Time-Wait,而不是立即关闭连接,原因是不保证这个确认报文没有丢失,而Client收不到确认报文则执行超时重传FIN+ACK,这时Server还未关闭,就可以重传ACK。