一、前言
网络编程作为移动应用开发的一个重要内容,虽然现在有很多的开源库可以帮我们方便快捷的访问网络。但我们仍要去了解其网络访问的原理,这是基础,也是优秀的开发者必备素质。
二、Http
1、概念
HTTP,全称HyperText Transfer Protocol,超文本传输协议,即使用超文本标记语言(HTML)的一种文本传输协议。它是应用层协议,规定了数据交互的格式内容。
2、特点
支持C/S(客户/服务器)模式。
简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST,每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后, 即断开连接。采用这种方式可以节省传输时间。
无状态:HTTP协议是无状态协议,无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
3、协议
1)Http Url
http://<主机>:<端口>/<路径>
2)请求报文
请求报文包括 请求行、请求头部、请求数据
请求方法有多种,GET和POST是最主要的两种方式:
GET:从指定的资源请求数据,查询参数(键值对)包含在URL中
POST:向指定的资源提交要被处理的数据,查询参数(键值对)放在 HTTP 消息主体中
HEAD:请求读取由URL所标志的信息的首部
OPTION:请求一些选项的信息
PUT:在指明的URL下存储一个文档
DELETE:删除指明的URL所标志的资源
TRACE:用来进行环回测试的请求报文
CONNECT:用于代理服务器
请求报头有许多种,常见的请求头有:
Host域:指定请求的服务器地址,在HTTP/1.1中请求必须包含主机头域,否则系统会以400状态码返回。
Connection域:指定与连接相关的属性,比如上面例子中Connection: keep-alive,代表保持连接。
User-Agent域:发送请求的应用程序名称。
Accept-Charset域:通知服务端可以发送的编码格式。
Accept-Encoding域:通知服务端可以发送的数据压缩格式。
Accept-Language域:通知服务器可以发送的语言。
Accept域: 告诉WEB服务器自己接受什么介质类型,/ 表示任何类型,type/* 表示该类型下的所有子类型。
Referer域:发送请求页面URL。浏览器向 WEB 服务器表明自己是从哪个 网页/URL 获得/点击 当前请求中的网址/URL。
Pramga域:主要使用 Pramga: no-cache,相当于 Cache-Control: no-cache。
Date域:表示消息发出的时间。
Cookie域:设置Cookie相关的。
Cache-Control域:使用的缓存机制(在请求时和响应时它的值不同,响应的下文会说到)。
no-cache(不要缓存的实体,要求现在从服务器去取)
max-age:(只接受 Age 值小于 max-age 值,并且没有过期的对象)
max-stale:(可以接受过去的对象,但是过期时间必须小于 max-stale 值)
min-fresh:(接受其新鲜生命期大于其当前 Age 跟 min-fresh 值之和的缓存对象)。
3)响应报文
类比请求报文,响应报文同样也包括 响应行、响应头、响应数据
状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:
100~199:指示信息,表示请求已接收,继续处理
200~299:请求成功,表示请求已被成功接收、理解、接受
300~399:重定向,要完成请求必须进行更进一步的操作
400~499:客户端错误,请求有语法错误或请求无法实现
500~599:服务器端错误,服务器未能实现合法的请求
常见的状态码如下:
200 OK:客户端请求成功
400 Bad Request:客户端请求有语法错误,不能被服务器所理解
401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden:服务器收到请求,但是拒绝提供服务
500 Internal Server Error:服务器发生不可预期的错误
503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常
响应头,列举一下常用的头部:
Age域:当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时间了。
Accept-Ranges域:WEB服务器表明自己是否接受获取其某个实体的一部分(比如文件的一部分)的请求。bytes:表示接受,none:表示不接受。
Content-Type域:服务器通知客户端它响应的对象类型,如Content-Type:application/json。
Content-Range域:服务器表明该响应包含的部分对象为整个对象的哪个部分。
Content-Length:服务器通知响应包含对象的长度。
Content-Language:服务器通知客户端响应的语言。
Content-Encoding:服务器通知客户端响应数据的压缩格式,在上面的例子中可以看出压缩格式为gzip。
Connection:代表是否需要持久连接。
Expired:WEB服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟WEB服务器验证了其有效性后,才能用来响应客户请求。
Last-Modified: 服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。
Location:服务器告诉浏览器,试图访问的对象已经被移到别的位置了,到该头部指定的位置去取。
Proxy-Authenticate:代理服务器响应浏览器,要求其提供代理身份验证信息。
Server: 服务器表明自己是什么软件及版本等信息。
Refresh:表示浏览器应该在多少时间之后刷新文档,以秒计。
响应数据:
一般来说响应数据也会有一定的格式,上面的例子中因为数据用gzip压缩了,所以显示的为乱码。现在用的最火的为json格式数据,下面的例子为一个请求的响应数据,为json格式:
{“status”:true,”error”:”“,”data”:{“id”:010101,”url”:”http://blog.csdn.net/liushuaiq/article/details/52779689“}}
4、HttpClient与HttpURLConnection
Android SDK中包含了HttpClient,在Android6.0版本直接删除了HttpClient类库,如果仍想使用则解决方法是:
android {
useLibrary 'org.apache.http.legacy'
}
1)HttpClient
要使用HttpClient,先创建一个HttpClient,配置好请求参数:
private HttpClient createHttpClient() {
HttpParams mDefaultHttpParams = new BasicHttpParams();
//设置连接超时
HttpConnectionParams.setConnectionTimeout(mDefaultHttpParams, 15000);
//设置请求超时
HttpConnectionParams.setSoTimeout(mDefaultHttpParams, 15000);
HttpConnectionParams.setTcpNoDelay(mDefaultHttpParams, true);
HttpProtocolParams.setVersion(mDefaultHttpParams, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(mDefaultHttpParams, HTTP.UTF_8);
//持续握手
HttpProtocolParams.setUseExpectContinue(mDefaultHttpParams, true);
HttpClient mHttpClient = new DefaultHttpClient(mDefaultHttpParams);
return mHttpClient;
}
GET请求
//创建HttpGet和HttpClient,请求网络并得到HttpResponse
private void useHttpClientGet(String url) {
HttpGet mHttpGet = new HttpGet(url);
mHttpGet.addHeader("Connection", "Keep-Alive");
try {
HttpClient mHttpClient = createHttpClient();
HttpResponse mHttpResponse = mHttpClient.execute(mHttpGet);
HttpEntity mHttpEntity = mHttpResponse.getEntity();
int code = mHttpResponse.getStatusLine().getStatusCode();
if (null != mHttpEntity) {
InputStream mInputStream = mHttpEntity.getContent();
String respose = converStreamToString(mInputStream);
Log.i("wangshu", "请求状态码:" + code + "\n请求结果:\n" + respose);
mInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
POST请求,需要修改为HttpPost,增加键值对参数:
List<NameValuePair> postParams = new ArrayList<>();
//要传递的参数
postParams.add(new BasicNameValuePair("username", "admin"));
postParams.add(new BasicNameValuePair("password", "123"));
mHttpPost.setEntity(new UrlEncodedFormEntity(postParams));
HttpResponse mHttpResponse = mHttpClient.execute(mHttpPost);
Get和Post请求区别:
- GET 请求可被缓存,保留在浏览器历史记录中,不应在处理敏感数据时使用,有长度限制
- POST 请求不会被缓存,不会保留在浏览器历史记录中,对数据长度没有要求
3)HttpURLConnection
在Android 2.3版本及以后,HttpURLConnection相比HttpClient是最佳的选择,它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。另外在Android 6.0版本中,HttpClient库被移除了,HttpURLConnection则是以后我们唯一的选择。
创建HttpURLConnection,配置参数:
public static HttpURLConnection getHttpURLConnection(String url){
HttpURLConnection mHttpURLConnection=null;
try {
URL mUrl=new URL(url);
mHttpURLConnection=(HttpURLConnection)mUrl.openConnection();
//设置链接超时时间
mHttpURLConnection.setConnectTimeout(15000);
//设置读取超时时间
mHttpURLConnection.setReadTimeout(15000);
//设置请求参数
mHttpURLConnection.setRequestMethod("POST");
//添加Header
mHttpURLConnection.setRequestProperty("Connection","Keep-Alive");
//接收输入流
mHttpURLConnection.setDoInput(true);
//传递参数时需要开启
mHttpURLConnection.setDoOutput(true);
} catch (IOException e) {
e.printStackTrace();
}
return mHttpURLConnection ;
}
组装请求参数
public static void postParams(OutputStream output,List<NameValuePair>paramsList) throws IOException{
StringBuilder mStringBuilder=new StringBuilder();
for (NameValuePair pair:paramsList){
if(!TextUtils.isEmpty(mStringBuilder)){
mStringBuilder.append("&");
}
mStringBuilder.append(URLEncoder.encode(pair.getName(),"UTF-8"));
mStringBuilder.append("=");
mStringBuilder.append(URLEncoder.encode(pair.getValue(),"UTF-8"));
}
BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(output,"UTF-8"));
writer.write(mStringBuilder.toString());
writer.flush();
writer.close();
}
请求网络连接,处理返回结果:
private void useHttpUrlConnectionPost(String url) {
InputStream mInputStream = null;
HttpURLConnection mHttpURLConnection = UrlConnManager.getHttpURLConnection(url);
try {
List<NameValuePair> postParams = new ArrayList<>();
//要传递的参数
postParams.add(new BasicNameValuePair("username", "moon"));
postParams.add(new BasicNameValuePair("password", "123"));
UrlConnManager.postParams(mHttpURLConnection.getOutputStream(), postParams);
mHttpURLConnection.connect();
mInputStream = mHttpURLConnection.getInputStream();
int code = mHttpURLConnection.getResponseCode();
String respose = converStreamToString(mInputStream);
Log.i("wangshu", "请求状态码:" + code + "\n请求结果:\n" + respose);
mInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
三、Http版本
实际上,HTTP协议是有 0.9,1.0,1.1,2四个版本的。
最早版本是1991年发布的0.9版,该版本只有一个请求,GET请求。而且服务器只能回应HTML格式的字符串,不能回应别的格式。
1996年5月,HTTP/1.0 版本发布。支持了多种格式,包括文字、图像、视频、二进制文件等。请求方式有GET,POST,HEAD,同时增加了状态码、多字符集支持、多部分发送、权限、缓存、内容编码等功能。
这个版本缺点:每个TCP连接只能发送一个请求。发送数据完毕,连接就关闭。TCP连接的新建成本很高,因为需要客户端和服务器三次握手。所以性能较差,在请求资源较多时较为明显。
1997年1月1.1版本进一步完善了 HTTP 协议,同时也是目前的主流版本。1.引入了持久连接(persistent connection),TCP连接默认不关闭,可以被多个请求复用。2.管道机制(pipelining),在同一个TCP连接里面,客户端可以同时发送多个请求。3.支持流模式,取代缓存模式, 通过Transfer-Encoding字段使用分块传输。4.增加PUT,OPTIONS,DELETE等更多请求方式,增加HOST字段,指定服务器,可以将请求发往同一台服务器上的不同网站,为虚拟主机的兴起打下了基础。
1.1版本的缺点:允许复用TCP连接,但请求和响应需要同步处理。要是如果某个请求处理特别慢,后面就会有许多请求排队等着。
HTTP/2是最新版本,规范还没有最终完成,基于 SPDY 协议(基于TCP的应用层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验)。打开一个 TCP 连接并重复使用,这使得许多请求得以并行发送,而无需等待响应。
和1.1版本相比, HTTP/2采用二进制格式而非文本格式。HTTP/2是完全多路复用的,而非有序并阻塞的——只需一个连接即可实现并行。使用报头压缩,压缩头信息,客户端和服务器维护一张头信息表,通过索引,HTTP/2降低了开销。HTTP/2让服务器可以将响应主动“推送”到客户端缓存中。
在开放互联网上HTTP 2.0将只用于https网址,而 http网址将继续使用HTTP/1,目的是在开放互联网上增加使用加密技术,以提供强有力的保护去遏制主动攻击。
四、HTTP安全
HTTP协议版本的更迭,除了功能上的扩展和丰富,更实质上是性能上的优化,已适应越来越高速和频繁的网络请求。
但在互联网时代,HTTP如何保证安全性呢?HTTP 具有相当优秀和方便的一面,但在安全性上也有不足:
1)通信使用明文( 不加密) , 内容可能会被窃听
2)不验证通信方的身份, 因此有可能遭遇伪装
3)无法证明报文的完整性, 所以有可能已遭篡改
这就要说到HTTPS了。
HTTPS可以理解为HTTP+SSL/TLS, 即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL,用于安全的 HTTP 数据传输。
内容加密建立一个信息安全通道,来保证数据传输的安全;
身份认证确认网站的真实性
数据完整性防止内容被第三方冒充或者篡改
不足:对数据进行加解密决定了它比http慢。需要进行非对称的加解密,且需要三次握手。首次连接比较慢点,当然现在也有很多的优化。
TLS(Transport Layer Security,传输层安全协议),及其前身SSL(Secure Sockets Layer,安全套接层)是一种安全协议,目的是为互联网通信,提供安全及数据完整性保障。
至于TLS/SSL怎么保障安全性,有个博主写得非常好,此处贴一下跳转:https://www.jianshu.com/p/24af67c40e8d
五、补充
以上的针对性能和安全性的优化,实际上是基于协议层面的优化,开发者在实际项目中关于网络请求实际上还可以做一些优化。
在APP使用中,网络连接对用户的影响主要有三点:
- 流量:App的流量消耗对用户来说是比较敏感的, 毕竟流量是花钱的嘛. 现在大部分人的手机上都有安装流量监控的工具App,
用来监控App的流量使用. 如果我们的App这方面没有控制好, 会给用户不好的使用体验. - 电量:电量相对于用户来说, 没有那么明显. 一般用户可能不会太注意. 但是如前文电量优化中说的那样,
网络连接(radio)是对电量影响很大的一个因素. 所以我们也要加以注意. - 用户等待:也就是用户体验, 良好的用户体验, 才是我们留住用户的第一步. 如果App请求等待时间长, 会给用户网络卡, 应用反应慢的感觉,
如果有对比, 有替代品, 我们的App很可能就会被用户无情抛弃.
从哪些方面去优化网络进而减少甚至消灭这些影响
1)减少Radio活跃时间。也就是减少网络数据获取的频次.这就减少了radio的电量消耗, 控制电量使用.
2)减少获取数据包的大小。可以减少流量消耗,也可以让每次请求更快, 在网络情况不好的情况下也有良好表现, 提升用户体验.
具体入手:
1)API设计:App与Server之间的API设计要考虑网络请求的频次, 资源的状态等. 以便App可以以较少的请求来完成业务需求和界面的展示.
2)Gzip压缩:使用Gzip来压缩request和response, 减少传输数据量, 从而减少流量消耗.
3)图片的Size:图片相对于接口请求来说, 数据量要大得多.我们可以在获取图片时告知服务器需要的图片的宽高, 以便服务器给出合适的图片, 避免浪费.
4)网络缓存:适当的缓存, 既可以让我们的应用看起来更快, 也能避免一些不必要的流量消耗.
5)通过监听设备的状态:休眠,充电,网络状态,根据网络状态对网络请求进行区别对待,对应的网络策略也应该是不一样的。
例如Splash闪屏广告图片, 我们可以在连接到Wifi时下载缓存到本地; 新闻类的App可以在充电, Wifi状态下做离线缓存。
6)弱网测试&优化:本质上是在弱网的情况下能让用户流畅的使用我们的App.
主要可以从以下方面入手:压缩/减少数据传输量,利用缓存减少网络传输,针对弱网(移动网络), 不自动加载图片,界面先反馈, 请求延迟提交
7)预取:我们需要预先判断用户在此次操作之后,后续零散的请求是否很有可能会马上被触发,可以把后面几分钟有可能会使用到的零散请求都一次集中执行完毕。
8)IP直连:DNS解析的失败率占联网失败中很大一种,而且首次域名解析一般需要几百毫秒。针对此,我们可以不用域名,才用IP直连省去 DNS 解析过程,节省这部分时间。
9)增量更新:数据更新采用增量,而不是全量,仅将变化的数据返回,客户端进行合并,减少流量消耗。
10)Protocol Buffer:Protocol Buffer是Google的一种数据交换的格式,它独立于语言,独立于平台。相较于目前常用的Json,数据量更小,意味着传输速度也更快。
11)断点续传:文件、图片等的下载,采用断点续传,不浪费用户之前消耗过的流量;
12)重试策略:一次网络请求的失败,需要多次的重试来断定最终的失败。
分析网络连接和弱网测试工具的使用,可以参考博客:https://www.jianshu.com/p/d4c2c62ffc35