“ 复制-粘贴 ”,相信是广大程序员朋友在日常开发中最常用也是最快速的编码方式了,这种方式无可厚非,优点是方便快捷,网上的代码块也是经过别人实践过的,稍微改动下便可符合我们的需求。但是这种做法的弊端也很明显,那就是“ 记不住、不理解 ”。
如何跳出“ Hello world ”的水平怪圈,对广大朋友来说是很痛苦和迷茫的,古人说“ 读万卷书,不如行万里路 ” , 特别对是对程序员来说,脚踏实地,一步一个脚印才是最重要的,有时候放慢下速度,追求下细节和品质也不妨是提升自我能力的一个好方法。
闲话少说,接下来步入正题。
网络请求是开发中经常用到的,在Java和android开发中,我们经常会采用一些http框架(async、aFinal、volley、okhttp等等)来帮助我们实现网络功能,因为这些框架功能强大,用起来方便快捷,但是正因为如此,才导致我们失去了对真理的追求和渴望。一次网络请求中用到了哪些知识?经历了哪些步骤?服务端和客户端是怎么处理的?这些都是我们需要了解的,下面就让我们一起来剖析一次网络请求,看看它的真面目。
当我们在app或者浏览器中发起一个http请求到页面渲染完成,整个阶段大致可以分为以下几个步骤:
1. 域名解析
域名解析就是把人们注册的域名转换成IP地址的过程。域名解析一般情况下是由DNS服务器负责处理,但是以下几种情况下除外(优先级a>b>c>d>e)。
a. 浏览器存在DNS缓存:如果自身的缓存中存在域名对应的IP地址,并且在有效期内,则解析成功。但是浏览器自身的DNS缓存有效期比较短,且容纳有限,大概是1000条。
b. 操作系统的DNS缓存:如果a步骤无法解析,此时会查询系统的DNS缓存(windows: ipconfig /displaydns),如果找到,则解析成功。
c. hosts DNS配置:如果b步骤无法解析,此时会查询系统hosts文件配置(android:system/etc/hosts),如果找到,则解析成功。
d. TCP/IP配置:如果c步骤无法解析,此时系统会查询本地连接中的TCP/IPv4/v6本地DNS服务器,如果要查询的域名包含在本地配置的区域资源中,则完成域名解析,否则根据本地DNS服务器会请求根DNS服务器。
e. 本地DNS会把请求发至13台根DNS,根DNS服务器收到请求后会返回负责这个域名(.net)的服务器的一个IP,本地DNS服务器使用该IP信息联系负责.net域的这台服务器。这台负责.net域的服务器收到请求后,如果自己无法解析,会返回.net域的下一级DNS服务器地址(blog.csdn.net)给本地DNS服务器。以此类推,直至找到。(这就是为什么说美国一拉闸中国就断网)
2. TCP三次握手建立连接
为了提供可靠的传送,TCP在发送新的数据之前,以特定的顺序将数据包的序号,并需要这些包传送给目标机之后的确认消息。简单来说就是:“ hello,我能连你吗? OK没问题。好,那我连了啊 ”
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据。那么TCP该如何关闭呢?
这里需要提到:改进的三次握手
当客户端没有东西要发送要释放连接的时候,TCP使用改进的三次握手也就是四次挥手来释放连接(使用一个带有FIN附加标记的报文段)。
第一步:当客户端的应用程序通知TCP数据已经发送完毕时,TCP向服务器发送一个带有FIN附加标记的报文段(FIN表示英文finish)。
第二步:服务器收到这个FIN报文段之后,并不立即用FIN报文段回复客户端,而是先向客户端发送一个确认序号ACK,同时通知自己相应的应用程序:对方要求关闭连接(先发送ACK的目的是为了防止在这段时间内,对方重传FIN报文段)。
第三步:服务器的应用程序告诉TCP:我要彻底的关闭连接,TCP向客户端送一个FIN报文段。
第四步:客户端收到这个FIN报文段后,向服务端发送一个ACK表示连接彻底释放。
为什么连接的时候是三次握手,关闭的时候却是四次挥手?
关闭连接时,当Server端收到FIN报文时,很可能数据信息没有传完,所以只能先回复一个ACK报文(告诉Client端,"你发的FIN报文我收到了")。只有等到Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步挥手。
3. 发起http请求
TCP三次握手成功后开始向服务端按照固定格式发起请求。
http的请求包含三个部分:请求行 、 请求头 、 数据体。
请求行:包含三个内容: method(GET/POST/DELETE/PUT/HEAD等等) + request-URI + http-version
请求头:缓存相关信息(Cache-Control,If-Modified-Since)、客户端身份信息(User-Agent)等键值对信息。
数据体:客户端发给服务端的请求数据。
4. 服务器响应请求
客户端成功发起请求后,服务端接收数据处理完成后将响应信息返回给客户端。
http响应由三个部分组成分别是:状态行、响应头、响应正文。
状态行是由:HTTP-Version+Status-Code+Reason-Phrase
比如:HTTP/1.1 200 ok ,分别表示http版本 + 状态码 + 状态代码的文本描述
其中状态码Status-Code:
响应头:包含服务器类型,日期,长度,内容类型等
Server:Apache Tomcat/5.0.12
Date:Mon,6Oct2003 13:13:33 GMT
Content-Type:text/html
Last-Moified:Mon,6 Oct 2003 13:23:42 GMT
Content-Length:112
响应正文:服务端返回给客户端的HTML文本内容。或者其他格式的数据,比如:视频流、图片或者音频数据。
5. 客户端接收数据并渲染
客户端拿到响应正文后,如果是html文本内容,那么就开始解析其中的html代码,遇到js/css/image等静态资源时,向服务器端发起一个HTTP请求,如果服务器端返回304状态码(告诉浏览器服务器端没有修改该资源),那么浏览器会直接读取本地的该资源的缓存文件。否则开启新线程向服务器端去请求下载。(这个时候就用上keep-alive特性了,建立一次HTTP连接,可以请求多个资源。)最后,浏览器利用自己内部的工作机制,把请求到的静态资源和html代码进行渲染,再呈现给用户。如果是其他格式的数据,如json数据、视频流等,则需要客户端特别处理。
综上所述,了解了整个流程后,相信会对网络这块会有个更全面的认识,对自己的知识结构也是一个补充。
百舸争流,奋楫者先;千帆竟发,勇进者胜。
在这个充满机遇和挑战的时代,希望我们能够正视自己,迎接挑战,创造辉煌!