网络一直是项目里比较重要的一个模块,Android开源项目上出现过不少优秀的网络框架。从一开始只是一些对HttpClient
和HttpUrlConnection
简易封装使用的工具类,到后来Google开源的比较完善丰富的Volley
,再到现在比较流行的Okhttp
、Retrofit
。他们之间存在异同,这个系列主要想经过对网络基础知识、Android网络框架的解析来理清他们的关系以及原理。java
整个系列主要会分为如下几个部分:算法
这是第一篇,主要介绍计算机网络的一些基础,以及在Android开发中的一些使用及遇到的问题和解决,本篇主要分为如下几部分:编程
对于计算机网络的一些基本知识,仍是须要作一些了解,事实证实在Android的平常开发和源码阅读中也会常常碰到相关知识,因此第一篇就写一下一些计算机网络的基础。浏览器
即常常看到的计算机网络体系的分层结构,理清这个仍是有必要的,防止对Http和Tcp两个根本不在同一层的协议纠缠不清。 根据不一样的参考模型,分层结构有几个不一样的版本,如OSI模型以及TCP/IP模型,下面就以比较常常看到的的5层结构为例:缓存
分层 |
---|
应用层 (HTTP、FTP、DNS、SMTP等等) |
运输层 (TCP、UDP) |
网络层 (IP等) |
数据链路层(ARP等) |
物理层 |
五层的体系结构至上往下,最终能够实现端对端之间的数据传输与通讯,他们各自负责一些什么,最终如何实现端对端之间的通讯?安全
1.应用层:如http协议,它其实是定义了如何包装和解析数据
,应用层是http协议的话,则会按照协议规定包装数据,如按照请求行、请求头、请求体包装,包装好数据后将数据传至运输层。服务器
2.运输层:运输层有TCP和UDP两种协议,分别对应可靠的运输和不可靠的运输,如TCP由于要提供可靠的传输,因此内部要解决如何创建链接、如何保证传输是可靠的不丢数据、如何调节流量控制和拥塞控制。关于这一层,咱们日常通常都是和Socket
打交道,Socket是一组封装的编程调用接口,经过它,咱们就能操做TCP、UDP进行链接的创建等。咱们日常使用Socket进行链接创建的时候,通常都要指定端口号
,因此这一层指定了把数据送到对应的端口号。markdown
3.网络层:这一层IP协议,以及一些路由选择协议等等,因此这一层的指定了数据要传输到哪一个IP地址
。中间涉及到一些最优线路,路由选择算法等等。cookie
4.数据链路层:印象比较深的就是ARP协议,负责把IP地址解析为MAC地址
,即硬件地址,这样就找到了对应的惟一的机器。网络
5.物理层:这一层就是最底层了,提供二进制流传输服务,也就是也就是真正开始经过传输介质(有线、无线)开始进行数据的传输了。
因此经过上面五层的各司其职,实现物理传输介质--MAC地址--IP地址--端口号--获取到数据根据应用层协议解析数据
最终实现了网络通讯和数据传输。
下面会着重讲一下HTTP和TCP相关的东西,关于其余层,毕业了这么久也忘的不少,若是想更加细致具体的了解像下面三层的如路由选择算法、ARP寻址以及物理层等等仍是要从新去看一下《计算机网络原理》~
这里主要讲一些关于Http的基础知识,以及在Android中的一些实际应用和碰到的问题和解决。
Http是无链接无状态的。
无链接
并非说不须要链接,Http协议只是一个应用层协议,最终仍是要靠运输层的如TCP协议向上提供的服务进行链接。无链接的含义是http约定了每次链接只处理一个请求,一次请求完成后就断开链接,这样主要是为了缓解服务器的压力,减少链接对服务器资源的占用。个人理解是,创建链接其实是运输层的事,面向应用层的http来讲的话,它就是无链接的,由于上层对下层无感知。
无状态
的指每一个请求之间都是独立的,对于以前的请求事务没有记忆的能力。因此就出现了像Cookie这种,用来保存一些状态的东西。
这里主要简单说一下请求报文和响应报文的格式:
请求报文:
名称 | 组成 |
---|---|
请求行 | 请求方法post/get、请求路径Url、协议版本等 |
请求头 | 即header,里面包含不少字段 |
请求体 | 发送的数据 |
响应报文:
名称 | 组成 |
---|---|
状态行 | 状态码如200、协议版本、等 |
响应头 | 即返回的header |
响应体 | 响应正文数据 |
关于Get和Post: 咱们都熟知的关于Get和Post的区别大体有如下几点:
问题:
对于第一点,若是是在浏览器里把隐私数据暴露在地址栏上确实不妥,可是若是是在App开发中呢,没有地址栏的概念,那么这一点是否是还会成为选择post仍是get的制约条件。
对于第二点,长度的限制应该是浏览器的限制,跟get自己无关,若是是在App开发中,这一点是否也能够忽略。
之因此想介绍如下Http的缓存机制,是由于Okhttp中对于网络请求缓存这一块就是利用了Http的的缓存机制,而不是像Volley等框架那样客户端彻底本身写一套缓存策略本身玩。
Http的缓存主要利用header里的两个字段来控制:
Cache-control
主要包含以及几个字段:对比缓存
来验证缓存数据实际上就是在这里面设置了一个缓存策略,由服务端第一次经过header下发给客户端,能够看到:
max-age
即缓存过时的时间,则以后再次请求,若是没有超过缓存失效的时间则能够直接使用缓存。
no-cache
:表示须要使用对比缓存来验证缓存数据,若是这个字段是打开的,则就算max-age缓存没有失效,则仍是须要发起一次请求向服务端确认一下资源是否有更新,是否须要从新请求数据,至于怎么作对比缓存,就是下面要说的Etag
的做用。若是服务端确认资源没有更新,则返回304,取本地缓存便可,若是有更新,则返回最新的资源。
no-store
:这个字段打开,则不会进行缓存,也不会取缓存。
2.ETag:
即用来进行对比缓存,Etag是服务端资源的一个标识码
当客户端发送第一次请求时服务端会下发当前请求资源的标识码Etag,下次再请求时,客户端则会经过header里的If-None-Match
将这个标识码Etag带上,服务端将客户端传来的Etag与最新的资源Etag作对比,若是同样,则表示资源没有更新,返回304。
经过Cache-control
和Etag
的配合来实现Http的缓存机制。
上面说了Http协议是无状态的,而Cookie就是用来在本地缓存记住一些状态的,一个Cookie通常都包含domin
(所属域)、path
、Expires
(过时时间)等几个属性。服务端能够经过在响应头里的set-cookies来将状态写入客户端的Cookie里。下次客户端发起请求时能够将Cookie带上。
Android开发中遇到的问题及解决:
提及Cookie,通常若是日常只是作App开发,比较不常常遇到,可是若是是涉及到WebView的需求,则有可能会遇到,下面就说一下我在项目里遇到过的一个关于WebView Cookie的揪心往事: 需求是这样的,加载的WebView中的H5页面须要是已登陆状态的,因此咱们须要在原生页面登陆后,手动将ticket写入WebView的Cookie,以后WebView里加载的H5页面带着Cookie里的ticket给服务端验证经过就行了。可是遇到一个问题:经过Chrome inspect
调试WebView,手动写的Cookie确实是已经写进去了,可是发起请求的时候,Cookie就是没有带上,致使请求验证失败,以后经过排查,是WebView的属性默认关闭引发,经过下面的代码设置打开便可:
CookieManager cookieManager = CookieManager.getInstance(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { cookieManager.setAcceptThirdPartyCookies(mWebView, true); } else { cookieManager.setAcceptCookie(true); } 复制代码
咱们都知道Https保证了咱们数据传输的安全,Https=Http+Ssl,之因此能保证安全主要的原理就是利用了非对称加密算法
,日常用的对称加密算法之因此不安全,是由于双方是用统一的密匙进行加密解密的,只要双方任意一方泄漏了密匙,那么其余人就能够利用密匙解密数据。
而非对称加密算法
之因此能实现安全传输的核心精华
就是: 公钥
加密的信息只能用私钥
解开,私钥
加密的信息只能被公钥
解开
服务端申请CA机构颁发的证书,则获取到了证书的公钥和私钥,私钥只有服务器端本身知道
,而公钥能够告知其余人,如能够把公钥传给客户端,这样客户端经过服务端传来的公钥来加密本身传输的数据,而服务端利用私钥就能够解密这个数据了。因为客户端这个用公钥加密的数据只有私钥能解密,而这个私钥只有服务端有,因此数据传输就安全了。
上面只是简单说了一下非对称加密算法是如何保证数据安全的,实际上Https的工做过程远比这要复杂(篇幅限制这里就不细说了,网上有不少相关文章):
一个是客户端还须要验证服务端传来的CA证书的合法性、有效性,由于存在传输过程CA证书被人调包的风险,涉及到客户端如何验证服务器证书的合法性的问题,保证通讯双方的身份合法。
另外一个是非对称算法虽然保证了数据的安全,可是效率相对于对称算法来讲比较差,如何来优化,实现既保证了数据的安全,又提升了效率呢?https传输的过程通常是第一次服务端发来证书后,客户端从中获取到公钥,而后客户端会生成一个对称密钥,以后用公钥加密该对称密钥,服务端接收到后用私钥来解密获取到对称密钥,以后服务端利用该对称密钥来加密后续的数据。
首先CA证书通常包括如下内容:
客户端验证服务端传过来的证书的合法性是经过
:先利用获取到的公钥来解密证书中的数字签名Hash值1(由于它是利用私钥加密的嘛),而后在利用证书里的签名Hash算法生成一个Hash值2,若是两个值相等,则表示证书合法,服务器端能够被信任。
https既然有一套安全的数据加密机制,那么咱们平时用的抓包工具(好比Charles)抓https报文是如何工做的呢
?
其实Charles至关于充当了一个“中间代理人”
的角色,当服务端下发证书时,Charles会截获证书并获取到公钥,而后把Charles本身的证书下发给客户端,以后客户端拿到的天然是Charles的证书,那么客户端发起请求时用的就是Charles的公钥来加密数据,而后Charles截获请求,用本身的私钥就能够解密请求数据了,以后再用服务端的公钥加密数据转发给服务端,服务端返回报文的过程相似。这样Charles掉包了证书而且充当中间人的角色就能够解密并获取到https报文了。固然这一切可以这么顺利最重要的仍是咱们手机上须要安装并信任Charles证书。
Android开发中遇到的问题及解决:
顺便说一个在项目开发中使用Android WebView加载公司测试服务器上网页证书过时致使网页加载不出来白屏的问题:
解决方案就是测试环境下暂时忽略SSL的报错,这样就能够把网页加载出来,固然在生产上不要这么作,一个是会有安全问题,一个是google play应该审核也不会经过。 重写WebViewClient的onReceivedSslError():
@Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { if (ContextHolder.sDebug) { handler.proceed(); return; } super.onReceivedSslError(view, handler, error); } 复制代码
Okhttp支持配置使用Http 2.0协议,Http2.0相对于Http1.x来讲提高是巨大的,主要有如下几点:
二进制格式:
http1.x是文本协议,而http2.0是二进制以帧为基本单位,是一个二进制协议,一帧中除了包含数据外同时还包含该帧的标识:Stream Identifier,即标识了该帧属于哪一个request,使得网络传输变得十分灵活。多路复用:
一个很大的改进,原先http1.x一个链接一个请求的状况有比较大的局限性,也引起了不少问题,如创建多个链接的消耗以及效率问题。http1.x为了解决效率问题,可能会尽可能多的发起并发的请求去加载资源,然而浏览器对于同一域名下的并发请求有限制,而优化的手段通常是将请求的资源放到不一样的域名下来突破这种限制。
而http2.0支持的多路复用能够很好的解决这个问题,多个请求共用一个TCP链接,多个请求能够同时在这个TCP链接上并发
,一个是解决了创建多个TCP链接的消耗问题,一个也解决了效率的问题。那么是什么原理支撑多个请求能够在一个TCP链接上并发呢?基本原理就是上面的二进制分帧,由于每一帧都有一个身份标识,因此多个请求的不一样帧能够并发的无序发送出去,在服务端会根据每一帧的身份标识,将其整理到对应的request中。
header头部压缩:
主要是经过压缩header来减小请求的大小,减小流量消耗,提升效率。由于以前存在一个问题是,每次请求都要带上header,而这个header中的数据一般是一层不变的。支持服务端推送
TCP面向链接,提供可靠的数据传输。在这一层,咱们一般都是经过Socket Api来操做TCP,创建链接等等。
第一次
:发送SNY=1表示这次握手是请求创建链接的,而后seq生成一个客户端的随机数X
第二次
:发送SNY=1,ACK=1表示是回复请求创建链接的,而后ack=客户端的seq+1(这样客户端收到后就能确认是以前想要链接的那个服务端),而后把服务端也生成一个表明本身的随机数seq=Y发给客户端。
第三次
:ACK=1。 seq=客户端随机数+1,ack=服务端随机数+1(这样服务端就知道是刚刚那个客户端了)
首先很是明确的是两次握手是最基本的, 第一次握手
,C端发了个链接请求消息到S端,S端收到后S端就知道本身与C端是能够链接成功的,可是C端此时并不知道S端是否接收到这个消息,因此S端接收到消息后得应答,C端获得S端的回复后,才能肯定本身与S端是能够链接上的,这就是第二次握手
。C端只有肯定了本身能与S端链接上才能开始发数据。因此两次握手确定是最基本的。
那么为何须要第三次握手呢?
假设一下若是没有第三次握手,而是两次握手后咱们就认为链接创建,那么会发生什么?
第三次握手是为了防止已经失效的链接请求报文段忽然又传到服务端,于是产生错误
具体状况就是:C端发出去的第一个网络链接请求因为某些缘由在网络节点中滞留了,致使延迟,直到链接释放的某个时间点才到达S端,这是一个早已失效的报文,可是此时S端仍然认为这是C端的创建链接请求第一次握手,因而S端回应了C端,第二次握手。若是只有两次握手,那么到这里,链接就创建了,可是此时C端并无任何数据要发送,而S端就会傻傻的等待着,形成很大的资源浪费。因此须要第三次握手,只有C端再次回应一下,就能够避免这种状况。
通过上面的创建链接图的解析,这个图应该不难看懂,这里主要有一个问题:
能够看到这里服务端的ACK(回复客户端)和FIN(终止)消息并非同时发出的,而是先ACK,而后再FIN,这也很好理解,当客户端要求断开链接时,此时服务端可能还有未发送完的数据
,因此先ACK,而后等数据发送完再FIN。这样就变成了四次握手了。
上面讲了TCP创建链接和断开链接的过程,TCP最主要的特色就是提供可靠的传输,那么他是如何保证数据传输是可靠的呢,这就是下面要讲的滑动窗口协议
滑动窗口协议是保证TCP的可靠传输的根本,由于发送窗口只有收到确认帧才会向后移动窗口继续发送其余帧。
下面举个例子:假如发送窗口
是3帧
一开始发送窗口在前3帧[1,2,3],则前3帧是能够发送的,后面的则暂时不能够发送,好比[1]帧发送出去后,收到了来自接收方的确认消息,则此时发送窗口才能够日后移1帧,发送窗口来到[2,3,4],一样只有发送窗口内的帧才能够被发送,一次类推。
而接收窗口
接收到帧后将其放入对应的位置,而后移动接收窗口,接口窗口与发送窗口同样也有一个大小,如接收窗口是5帧,则落在接收窗口以外的帧会被丢弃。
发送窗口和接收窗口大小的不一样设定就延伸出了不一样的协议:
协议 | 特色 |
---|---|
中止-等待协议 | 发送窗口大小=1,接收窗口大小=1 |
后退N帧协议 | 发送窗口大小>1,接收窗口大小=1, |
选择重传协议 | 发送窗口大小>1,接收窗口大小>1 |
中止-等待协议
:每发一帧都要等到确认消息才能发送下一帧,缺点:
效率较差。
后退N帧协议
:采起累计确认的方式,接收方正确的接受到N帧后发一个累计确认消息给发送窗口,确认N帧已正确收到,若是发送方规定时间内未收到确认消息则认为超时或数据丢失,则会从新发送确认帧以后的全部帧。缺点:
出错序号后面的PDU已经发送过了,可是仍是要从新发送,比较浪费。
选择重传协议
:若出现差错,只从新传输出现差错涉及须要的PDU,提升了传输效率,减小没必要要的重传。
到这里还剩下最后一个问题:因为发送窗口与接收窗口之间会存在发送效率和接收效率不匹配的问题,就会致使拥塞,解决这个问题TCP有一套
流量控制和拥塞控制
的机制。
流量控制是对一条通讯路径上的流量进行控制,就是发送方经过获取接收方的回馈来动态调整发送的速率,来达到控制流量的效果,其目的是保证发送者的发送速度不超过接收者的接收速度。
拥塞控制是对整个通讯子网的流量进行控制,属于全局控制。
①慢开始
+拥塞避免
先来看一张经典的图:
一开始使用慢启动
,即拥塞窗口设为1,而后拥塞窗口指数增加到慢开始的门限值(ssthresh=16),则切换为拥塞避免
,即加法增加,这样增加到必定程度,致使网络拥塞,则此时会把拥塞窗口从新降为1,即从新慢开始
,同时调整新的慢开始门限值为12,以后以此类推。
②快重传
+快恢复
快重传:
上面咱们说的重传机制都是等到超时还未收到接收方的回复,才开始进行重传。而快重传的设计思路是:若是发送方收到3个重复的接收方的ACK
,就能够判断有报文段丢失,此时就能够当即重传丢失的报文段,而不用等到设置的超时时间到了才开始重传,提升了重传的效率。
快恢复:
上面的拥塞控制会在网络拥塞时将拥塞窗口降为1,从新慢开始,这样存在的一个问题就是网络没法很快恢复到正常状态。快恢复就是来优化这个问题的,使用快恢复,则出现拥塞时,拥塞窗口只会下降到新的慢开始门阀值(即12),而不会降为1,而后直接开始进入拥塞避免加法增加,以下图所示:
快重传和快恢复是对拥塞控制的进一步改进。
Socket是一组操做TCP/UDP的API
,像HttpURLConnection和Okhttp这种涉及到比较底层的网络请求发送的,最终固然也都是经过Socket来进行网络请求链接发送,而像Volley、Retrofit则是更上层的封装,最后是依靠HttpURLConnection或者Okhttp来进行最终的链接创建和请求发送。
Socket的简单使用的话应该都会,两个端各创建一个Socket,服务端的叫ServerSocket,而后创建链接便可。
固然这些只是我本身知道的而且认为挺重要的计算机网络基础,还有很是多的网络基础知识须要去深刻了解去探索。写了不少,算是对本身网络基础的一个整理,可能也会有纰漏。接下来也会简单解析一下HttpUrlConnection
以及Volley
,源码解析一下Okhttp
以及Retrofit
。