以在浏览器输入http://www.myApp.com:8000/mainPage/index.html为例,说明HTTP请求的过程。
1、域名解析
1) 搜索浏览器自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存),看自身的DNS缓存中是否有www.myApp.com对应的条目,而且没有过期,如果有且没有过期,则解析到此结束。
2) 如果在浏览器自身的DNS缓存里没有找到,则搜索操作系统自身的DNS缓存,如果找到且没有过期,则解析到此结束。
3) 如果在操作系统自身的DNS缓存里也没有找到,则尝试读取hosts文件(位于C:\Windows\System32\drivers\etc),如果找到,则解析到此结束。
4) 如果在hosts文件里也没有找到,则会发起一个DNS的系统调用,根据本地配置网络时填写的DNS地址向首选DNS服务器(一般是电信运营商提供的,也可以使用像Google提供的DNS服务器)发起域名解析请求(通过UDP协议向DNS的53端口发起请求,这个请求是递归的请求)。
4.1) 首选DNS服务器首先查找自身的缓存,如果找到且没有过期,则解析到此结束。
4.2) 如果没有找到,则由首选DNS服务器代浏览器发起迭代的DNS解析请求,首先会找根域的DNS服务器的IP地址,找到根域的DNS服务器后,就会向其发起请求(请问www.myApp.com这个域名的IP地址是多少啊?)
4.3) 根域的DNS服务器发现这是一个顶级域com域的一个域名,于是就告诉首选DNS服务器我不知道这个域名的IP地址,但是我知道com域的DNS服务器的IP地址,于是首选DNS服务器就得到了com域的DNS服务器的IP地址,又向com域的DNS服务器发起了请求(请问www.myApp.com这个域名的IP地址是多少?)
4.4)com域的DNS服务器告诉首选DNS服务器我不知道这个域名的IP地址,但是我知道myApp.com这个域的DNS服务器的IP地址,于是首选DNS服务器又向myApp.com这个域的DNS服务器发起请求(请问www.myApp.com这个域名的IP地址是多少啊?),这时linux178.com域的DNS服务器一查,找到了www.myApp.com这个域名对应的IP地址,于是把找到的结果发送给首选DNS服务器,这时首选DNS服务器就拿到了www.myApp.com这个域名对应的IP地址,并返回给Windows系统内核,内核又把结果返回给浏览器,终于浏览器拿到了www.myApp.com对应的IP地址。
2、客户端发起TCP的3次握手,通过socket与服务器建立一个TCP连接
拿到域名对应的IP地址之后,现在已经拥有了目标ip和端口号8000,这样我们就可以打开socket连接了。浏览器会以一个随机端口(1024 ~ 65535之间)向服务器端的Web程序发起TCP的连接请求。这个连接请求(原始的HTTP请求经过TCP/IP协议的4层模型的层层封包)到达服务器端后(这中间通过各种路由设备,局域网内除外),进入到网卡,然后是进入到TCP/IP协议栈(一层一层的解封包),最终到达服务器端的Web程序,最终建立了TCP/IP的连接。
TCP的三次握手:
1) Client首先发送一个连接试探,ACK=0 表示确认号无效,SYN = 1 表示这是一个连接请求报文,同时表示这个数据报不能携带数据,seq = x 表示Client自己的初始序号,表示客户端等待服务器的回复。
2) Server监听到连接请求报文后,如同意建立连接,则向Client发送确认。SYN 和 ACK都置1 ,ack = x + 1表示期望收到对方下一个报文段的第一个数据字节序号是x + 1,同时表明x为止的所有数据都已正确收到,seq = y 表示Server 自己的初始序号,表示Server已经收到Client的连接请求,等待Client的确认。
3) Client收到确认后还需再次发送确认,同时携带要发送给Server的数据。ACK 置1 ,ack = y + 1表示期望收到对方下一个报文段的第一个数据字节序号是y + 1,seq= x + 1 表示Client自己的初始序号,一旦收到Client的确认之后,TCP连接就建立完毕,可以发起HTTP请求了。
3、客户端向服务器发送HTTP请求
TCP连接成功建立后,客户端开始向Web服务器发送请求,这个请求一般是GET或POST命令。
GET命令的格式为:GET 路径/文件名 HTTP/1.0,其中,路径/文件名指出所访问的文件,HTTP/1.0指出Web浏览器使用的HTTP版本。
1次完整的HTTP请求消息包括:一个请求行、若干消息头以及实体内容,而消息头和实体内容可以没有,消息头和实体内容间有一个空行。
Get /mattmarg/ HTTP/1.0
Accept: text/html
Referer: http://www.myApp.com:8000/mainPage/index.html
Accept-Lanague: zh-CN
Content-Type: text/html
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Host: http://www.myApp.com
Connection: keep-alive
Cookie: name = value
#空行
<html><head></head><body>...</body></html>
第一行是HTTP请求的请求行:GET /mainPage/index.html HTTP/1.0。
接下来的几行是消息头,消息头主要是用来向服务器传达某种信息或指示。如告诉服务器自己的终端(User-Agent)(如果是浏览器则返回相应的浏览器型号),终端可以解释的类型(Accept),是从哪个页面提交的请求(Referer)以及浏览器缓存的Cookie等等。
Accept | 告诉服务器自己接受的MIME类型 |
Referer |
告诉服务器请求是从哪个页面提交的 |
Accept-Lanague |
告诉服务器自己接受的语言 |
Content-Type |
告诉服务器自己发送的MIME类型 |
Accept-Encoding |
告诉服务器自己接受的文件压缩方式 |
User-Agent |
告诉服务器自己的终端,如果是浏览器则返回相应的浏览器型号 |
Host |
用来标识请求服务器的主机 |
Connection |
告诉服务器自己是否支持Keep-Alive特性 |
Cookie |
每次请求时都会携带Cookie以方便服务器识别是否是同一个客户端 |
MIME,Multipurpose Internet Mail Extesions多用途互联网邮件扩展,是一个互联网标准,它扩展了电子邮件标准,使其能够支持非ASCII字符、二进制格式附件等多种格式的邮件消息,这个标准被定义在RFC 2045、RFC 2046、RFC 2047、RFC 2048、RFC 2049等RFC中。由RFC 822转变而来的RFC 2822,规定电子邮件标准并不允许在邮件消息中使用7位ASCII字符集以外的字符。正因如此,一些非英语字符消息和二进制文件,图像,声音等非文字消息都不能在电子邮件中传输。MIME规定了用于表示各种各样的数据类型的符号化方法。此外,在万维网中使用的HTTP协议中也使用了MIME的框架,标准被扩展为互联网媒体类型。
MIME 遵循以下格式:主类型/次类型。如:
1) image/jpg
2) image/gif
3) text/html
4) video/quicktime
5) appliation/x-httpd-php
4、服务器向客户端发送HTTP响应
Web服务器收到HTTP请求,进行处理。从它的文档空间中搜索子目录mainPage的文件index.html。如果找到该文件,Web服务器把该文件内容传送给相应的浏览器。
1次完整的HTTP响应消息包括:一个请求行、若干消息头以及实体内容,而消息头和实体内容可以没有,消息头和实体内容间有一个空行。
HTTP 1.0 200 OK
Accept: text/html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN
Content-Type: text/html
Cache-Control: max-age=0
Connection: keep-alive
Host: http://www.myApp.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
#空行
<html><head></head><body>...</body></html>
HTTP 1.0 200 OK是HTTP响应的第一行,列出服务器正在运行的HTTP版本号和应答代码,代码"200 OK"表示请求完成。
5、服务器发起TCP的4次挥手,关闭与客户端的TCP连接
应答结束后,若Connection为close,则服务器主动关闭TCP连接,客户端被动释放TCP 连接,若Connection为keepalive,则该连接会保持一段时间,在该时间内,下次的请求可以继续使用此连接。
TCP的四次挥手:
1) Server向Client发送一个FIN报文,序列号为i,FIN和ACK都置1 ,用来关闭Server到Client的数据传送,也就是告诉Client,Server已经不会再发数据了(当然,在FIN包之前发送出去的数据,如果没有收到对应的ACK确认报文,Client依然会重发这些数据),但此时Server还可以接收数据。
2) Client接到FIN(i)报文后,但是如果还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据,Client发送ACK,序列号为i + 1,ACK置1,告诉Server请求已经收到了,但Client还没准备好,Server继续等待Client的FIN报文。
3) 当Client确定数据已发送完成,则向Server发送FIN报文,序列号为j,FIN和ACK都置1,告诉Server,Client数据发完了,准备好关闭连接了。
4) Server收到FIN(j)报文后,就知道可以关闭连接了,Server发送ACK,序列号为j + 1,ACK置1, Client收到ACK后,就知道可以断开连接了,至此,TCP连接就已经完全关闭了!
6、浏览器解析HTML代码,并请求HTML代码中的资源
浏览器拿到文件后,就开始解析其中的HTML代码,遇到.js、.css及图片等静态资源时,就向服务器端发起一个HTTP请求,询问从上一次修改到现在有没有对资源进行修改,如果服务器端返回304状态码(告诉浏览器没有对资源进行修改),那么浏览器会直接读取本地的该资源的缓存文件。
7、浏览器对页面进行渲染呈现给用户
浏览器把请求到的静态资源和HTML代码进行渲染,渲染之后呈现给用户。