一、网络相关知识
1.1 网络分层
OSI七层模型 OSI七层协议模型主要是:应用层(Application)、表示层(Presentation)、会话层(Session)、传输层(Transport)、网络层(Network)、数据链路层(Data Link)、物理层(Physical)。
1.2 TCP/IP五层模型
TCP/IP五层模型:应用层(Application)、传输层(Transport)、网络层(Network)、数据链路层(Data Link)、物理层(Physical)。
1.3 Android与互联网交互的三种方式
1.4 TCP与UDP区别总结:
- 1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
- 2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保 证可靠交付
- 3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等) - 4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
- 5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
- 6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
二、网络协议
2.1 Http
什么是Http协议? hypertext transfer protocol(超文本传输协议),TCP/IP协议的一个应用层协议,用于 定义WEB浏览器与WEB服务器之间交换数据的过程。客户端连上web服务器后,若想获得web服务器 中的某个web资源,需遵守一定的通讯格式,HTTP协议用于定义客户端与web服务器通迅的格式。
主要特点
- 支持C/S模式
- 简单快速:只需传送请求方法和路径,请求常用的方法有:GET、POST、HEAD等
- 灵活:允许传输任意类型的数据对象,用Content-Type进行标记
- 无连接:限制每次连接只处理一个请求
- 无状态:对事务处理没有记忆功能
HTTP的URL格式
- http://host[:port] [/path]
- http表示要通过HTTP协议来定位网络资源;
- host表示合法的Internet主机域名或者IP地址;
- port指定一个端口号,为空则使用默认端口80;
- path指定请求资源的URI
Http协议的底层工作 两个名词:
- SYN(synchronous):TCP/IP建立连接时使用的握手信号
- ACK(Acknowledgement):确认字符,确认发来的数据已经接受无误
接着就到TCP/IP三次握手的概念:
- 客户端发送syn包(syn = j)到服务器,进入SYN_SEND状态,然后等待服务器确认
- 服务器收到syn包,确认客户的syn(ack = j + 1),同时在自己也发送一个SYN包(syn=k), 即SYN + ACK包,服务器进入SYN_RECV状态
- 客户端收到SYN + ACK包,向服务器发送确认包ACK(ack = k +1),发送完毕后,客户端与服务端 进入ESTABLISHED状态,完成三次握手,然后两者开始传送数据
Http的几种请求方式
- Get:请求获取Request-URI所标识的资源
- POST:在Request-URI所标识的资源后附加新的数据
- HEAD:请求获取由Request-URI所标识的资源的响应信息报头
- PUT:请求服务器存储一个资源,并用Request-URI作为其标识
- DELETE:请求服务器删除Request-URI所标识的资源
- TRACE:请求服务器回送收到的请求信息,主要用于测试或诊断
- CONNECT:保留将来使用
- OPTIONS:请求查询服务器的性能,或者查询与资源相关的选项
Get和Post的对比 http是应用层的协议,底层基于TCP/IP协议,所以本质上,get和post请求都是TCP请求。所以二者的区别都是体现在应用层上(HTTP的规定和浏览器/服务器的限制)
- 1.参数的传输方式:GET参数通过URL传递,POST放在Request body中。
- 2.GET请求在URL中传送的参数是有长度限制的,而POST没有。
- 3.对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。不过要注意,并不是所有浏览器都会在POST中发送两次包,比如火狐
- 4.对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
- 5.GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
- 6.GET请求只能进行url编码,而POST支持多种编码方式。
- 7.GET在浏览器回退时是无害的,而POST会再次提交请求。
- 8.GET产生的URL地址可以被Bookmark,而POST不可以。
- 9.GET请求会被浏览器主动cache,而POST不会,除非手动设置。
Http状态码合集
- 100~199 : 成功接受请求,客户端需提交下一次请求才能完成整个处理过程
- 200: OK,客户端请求成功
- 300~399:请求资源已移到新的地址(302,307,304)
- 401:请求未授权,改状态代码需与WWW-Authenticate报头域一起使用
- 403:Forbidden,服务器收到请求,但是拒绝提供服务
- 404:Not Found,请求资源不存在,这个就不用说啦
- 500:Internal Server Error,服务器发生不可预期的错误
- 503:Server Unavailable,服务器当前不能处理客户端请求,一段时间后可能恢复正常
2.2 HTTPS
HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。 HTTP是应用层协议,位于HTTP协议之下是传输协议TCP。TCP负责传输,HTTPS则定义了数据如何进行包装,在HTTPS跟TCP中间加多了一层加密层TLS/SSL,SSL是个加密套件,负责对HTTPS的数据进行加密。TLS是SSL的升级版。现在提到HTTPS,加密套件基本指的是TLS。 传输加密的流程:http是应用层将数据直接给到TCP进行传输,https是应用层将数据给到TLS/SSL,将数据加密后,再给到TCP进行传输。 HTTPS是如何加密数据的: 一般来说,加密分为对称加密、非对称加密
对称加密:
- 对称加密的意思就是,加密数据用的密钥,跟解密数据用的密钥是一样的。
对称加密的优点在于加密、解密效率通常比较高。缺点在于,数据发送方、数据接收方需要协商、共享同一把密钥,并确保密钥不泄露给其他人。传输过程中容易被截获。 - 网上一个很形象的例子:假如现在小客与小服要进行一次私密的对话,他们不希望这次对话内容被其他外人知道。可是,我们平时的数据传输过程中又是明文传输的,万一被某个黑客把他们的对话内容给窃取了,那就难受了。为了解决这个问题,小服这家伙想到了一个方法来加密数据,让黑客看不到具体的内容。该方法是这样子的:在每次数据传输之前,小服会先传输小客一把密钥,然后小服在之后给小客发消息的过程中,会用这把密钥对这些消息进行加密。小客在收到这些消息后,会用之前小服给的那把密钥对这些消息进行解密,这样,小客就能得到密文里面真正的数据了。如果小客要给小服发消息,也同样用这把密钥来对消息进行加密,小服收到后也用这把密钥进行解密。 这样,就保证了数据传输的安全性。
非对称加密
- 非对称加密的意思就是,加密数据用的密钥(公钥),跟解密数据用的密钥(私钥)是不一样的。
- 网上一个很形象的例子:小服还是挺聪明的,得意了一会之后,小服意识到了密钥会被截取这个问题。倔强的小服又想到了另外一种方法:用非对称加密的方法来加密数据。该方法是这样的:小服和小客都拥有两把钥匙,一把钥匙的公开的(全世界都知道也没关系),称之为公钥;而另一把钥匙是保密(也就是只有自己才知道),称之为私钥。并且,用公钥加密的数据,只有对应的私钥才能解密;用私钥加密的数据,只有对应的公钥才能解密。所以在传输数据的过程中,小服在给小客传输数据的过程中,会用小客给他的公钥进行加密,然后小客收到后,再用自己的私钥进行解密。小客给小服发消息的时候,也一样会用小服给他的公钥进行加密,然后小服再用自己的私钥进行解密。 这样,数据就能安全着到达双方。是什么原因导致非对称加密这种方法的不安全性呢?它和对称加密方法的不安全性不同。非对称加密之所以不安全,是因为小客收到了公钥之后,无法确定这把公钥是否真的是小服。
解决的办法就是数字证书:小服再给小客发公钥的过程中,会把公钥以及小服的个人信息通过Hash算法生成消息摘要,为了防止摘要被人调换,小服还会用CA提供的私钥对消息摘要进行加密来形成数字签名,当小客拿到这份数字证书之后,就会用CA提供的公钥来对数字证书里面的数字签名进行解密得到消息摘要,然后对数字证书里面小服的公钥和个人信息进行Hash得到另一份消息摘要,然后把两份消息摘要进行对比,如果一样,则证明这些东西确实是小服的,否则就不是。
2.3 加密算法
对称加密算法
- Data Encryption Standard(DES)
DES 是一种典型的块加密方法:将固定长度的明文通过一系列复杂的操作变成同样长度的密文,块的长度为64位。同时,DES 使用的密钥来自定义变换过程,因此算法认为只有持有加密所用的密钥的用户才能解密密文。 DES 的密钥表面上是64位的,实际有效密钥长度为56位,其余8位可以用于奇偶校验。 DES 现在已经不被视为一种安全的加密算法,主要原因是它使用的56位密钥过短。 为了提供实用所需的安全性,可以使用 DES 的派生算法 3DES 来进行加密 (虽然3DES 也存在理论上的攻击方法)。 - Advanced Encryption Standard(AES)
AES 在密码学中又称 Rijndael 加密法,用来替代原先的 DES,已经被多方分析且广泛使用。 DES与AES的比较 自DES 算法公诸于世以来,学术界围绕它的安全性等方面进行了研究并展开了激烈的争论。在技术上,对DES的批评主要集中在以下几个方面:
- 1、作为分组密码,DES 的加密单位仅有64 位二进制,这对于数据传输来说太小,因为每个分组仅含8 个字符,而且其中某些位还要用于奇偶校验或其他通讯开销。
- 2、DES 的密钥的位数太短,只有56 比特,而且各次迭代中使用的密钥是递推产生的,这种相关必然降低密码体制的安全性,在现有技术下用穷举法寻找密钥已趋于可行。
- 3、DES 不能对抗差分和线性密码分析。
- 4、DES 用户实际使用的密钥长度为56bit,理论上最大加密强度为256。DES 算法要提高加密强度(例如增加密钥长度),则系统开销呈指数增长。除采用提高硬件功能和增加并行处理功能外,从算法本身和软件技术方面都无法提高DES 算法的加密强度。
非对称加密算法
- RSA
1977年由 MIT 的 Ron Rivest、Adi Shamir 和 Leonard Adleman 一起提出,以他们三人姓氏开头字母命名,是一种获得广泛使用的非对称加密算法。 对极大整数做因数分解的难度 (The Factoring Problem) 决定了 RSA 算法的可靠性。换言之,对一个极大整数做因数分解愈困难,RSA 算法就愈可靠。假如有人找到一种快速因数分解的算法的话,那么用 RSA 加密的信息的可靠性就肯定会极度下降。目前看来找到这样的算法的可能性非常小。 - DES与RSA的比较
RSA算法的密钥很长,具有较好的安全性,但加密的计算量很大,加密速度较慢限制了其应用范围。为减少计算量,在传送信息时,常采用传统加密方法与公开密钥加密方法相结合的方式,即信息采用改进的DES对话密钥加密,然后使用RSA密钥加密对话密钥和信息摘要。对方收到信息后,用不同的密钥解密并可核对信息摘要。 采用DES与RSA相结合的应用,使它们的优缺点正好互补,即DES加密速度快,适合加密较长的报文,可用其加密明文;RSA加密速度慢,安全性好,应用于DES 密钥的加密,可解决DES 密钥分配的问题。 目前这种RSA和DES结合的方法已成为EMAIL保密通信标准。
三、常用的网络编程框架
3.1 Volley
- Volley的特点
Volley是谷歌大会上推出的网络通信框架(2.3之前使用HttpClient,之后使用HttpUrlConnection),它既可以访问网络获取数据,也可以加载图片,并且在性能方面进行了大幅度的调整,它的设计目的就是适合进行数据量不大但通信频繁的网络操作,而对于大数据量的操作,比如文件下载,表现很糟糕,因为volley处理http返回的默认实现是BasicNetwork,它会把返回的流全部导入内存中,下载大文件会发生内存溢出 - Volley执行的过程:
默认情况下,Volley中开启四个网络调度线程和一个缓存调度线程,首先请求会加入缓存队列,,缓存调度线程从缓存队列中取出线程,如果找到该请求的缓存就直接读取该缓存并解析,然后回调给主线程,如果没有找到缓存的响应,则将这个请求加入网络队列,然后网络调度线程会轮询取出网络队列中的请求,发起http请求,解析响应并将响应存入缓存,回调给主线程 - Volley为什么不适合下载上传大文件?为什么适合数据量小的频率高的请求?
1.volley基于请求队列,Volley的网络请求线程池默认大小为4。意味着可以并发进行4个请求,大于4个,会排在队列中。并发量小所以适合数据量下频率高的请求 2.因为Volley下载文件会将流存入内存中(是一个小于4k的缓存池),大文件会导致内存溢出,所以不能下载大文件,不能上传大文件的原因和1中差不多,设想你上传了四个大文件,同时占用了volley的四个线程,导致其他网络请求都阻塞在队列中,造成反应慢的现象
3.2 Retrofit
Retrofit底层是基于OkHttp实现的,与其他网络框架不同的是,它更多使用运行时注解的方式提供功能
- 原理
通过java接口以及注解来描述网络请求,并用动态代理的方式生成网络请求的request,然后通过client调用相应的网络框架(默认okhttp)去发起网络请求,并将返回的response通过converterFactorty转换成相应的数据model,最后通过calladapter转换成其他数据方式(如rxjava Observable) - Retrofit流程
- ①通过解析 网络请求接口的注解 配置 网络请求参数
- ②通过 动态代理 生成 网络请求对象
- ③通过 网络请求适配器 将 网络请求对象 进行平台适配
- ④通过 网络请求执行器 发送网络请求
- ⑤通过 数据转换器 解析服务器返回的数据
- ⑥通过 回调执行器 切换线程(子线程 ->>主线程)
- ⑦
用户在主线程处理返回结果
- Retrofit优点
- ①.可以配置不同HTTP client来实现网络请求,如okhttp、httpclient等;
- ②.请求的方法参数注解都可以定制;
- ③.支持同步、异步和RxJava;
- ④.超级解耦;
- ⑤.可以配置不同的反序列化工具来解析数据,如json、xml等
- ⑥.框架使用了很多设计模式
3.3 Okhttp
Okhttp:高性能的http库,支持同步、异步、而且支持http2、websocket协议,api简洁易用,实现了http缓存 Android网络访问的源码已用okHttp代替了HttpURLConnection
private void get(String url) {
// 1. 构造Request
final Request request = new Request.Builder().url( url )
.header( "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36 " )
.addHeader( "Accept", "application/json" )
.get()
.method( "GET", null )
.build();
// 2. 发送请求,并处理回调
OkHttpClient client = HttpsUtil.handleSSLHandshakeByOkHttp();
client.newCall( request ).enqueue( new Callback() {
//失败
@Override
public void onFailure(Call call, IOException e) {
Log.e( "OkHttpActivity", e.getMessage() );
}
//回应
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
// 1. 获取响应主体的json字符串
if (response.isSuccessful()) {
String json = response.body().string();
final Ip ip = JSON.parseObject( json, Ip.class );
// 2. 使用FastJson库解析json字符串
runOnUiThread( new Runnable() {
@Override
public void run() {
// 3. 根据返回的code判断获取是否成功
if (ip.getCode() != 0) {
tv_textView.setText( "未获得数据" );
} else {
// 4. 解析数据
IpData data = ip.getIpdata();
tv_textView.setText( data.getIp() + "," + data.getCity() );
}
}
} );
}
}
} );
//post请求
private void post(String url, Map <String, String> params) {
// 1. 构建RequestBody
RequestBody body = setRequestBody( params );
// 2. 创建Request对象
Request request = new Request.Builder().url( url ).post( body )
.header( "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36 " )
.addHeader( "Accept", "application/json" )
.build();
// 2. 发送请求,并处理回调
OkHttpClient client = HttpsUtil.handleSSLHandshakeByOkHttp();
client.newCall( request ).enqueue( new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e( "OkHttpActivity", e.getMessage() );
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
// 1. 获取响应主体的json字符串
String json = response.body().string();
// 2. 使用FastJson库解析json字符串
final Ip ip = JSON.parseObject( json, Ip.class );
runOnUiThread( new Runnable() {
@Override
public void run() {
// 3. 根据返回的code判断获取是否成功
if (ip.getCode() != 0) {
tv_textView.setText( "未获得数据" );
} else {
// 4. 解析数据
IpData data = ip.getIpdata();
tv_textView.setText( data.getIp() + "," + data.getCity() );
}
}
} );
}
}
} );
}
//将请求的参数组装成RequestBody
private RequestBody setRequestBody(Map <String, String> params) {
FormBody.Builder builder = new FormBody.Builder();
for (String key : params.keySet()) {
builder.add( key, params.get( key ) );
}
return builder.build();
}
//上传文件
private void uploadFile(String url, String fileName) {
Request request = new Request.Builder().url( url )
.post( RequestBody.create( MEDIA_TYPE_MARKDOWN, new File( fileName ) ) )
.build();
OkHttpClient client = HttpsUtil.handleSSLHandshakeByOkHttp();
client.newCall( request ).enqueue( new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
Log.e( TAG, e.getMessage() );
tv_textView.post( new Runnable() {
@Override
public void run() {
tv_textView.setText( "上传失败," + e.getMessage() );
}
} );
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
final String str = response.body().string();
runOnUiThread( new Runnable() {
@Override
public void run() {
tv_textView.setText( "上传成功," + str );
}
} );
} else {
Log.d( TAG, response.body().string() );
}
}
} );
}
//下载文件
public static void writeFile(InputStream is, String path, String fileName) throws IOException {
// 1. 根据path创建目录对象,并检查path是否存在,不存在则创建
File directory = new File( path );
if (!directory.exists()) {
directory.mkdirs();
}
// 2. 根据path和fileName创建文件对象,如果文件存在则删除
File file = new File( path, fileName );
if (file.exists()) {
file.delete();
}
// 3. 创建文件输出流对象,根据输入流创建缓冲输入流对象
FileOutputStream fos = new FileOutputStream( file );
BufferedInputStream bis = new BufferedInputStream( is );
// 4. 以每次1024个字节写入输出流对象
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read( buffer )) != -1) {
fos.write( buffer, 0, len );
}
fos.flush();
// 5. 关闭输入流、输出流对象
fos.close();
bis.close();
}
private void downFile(final String url, final String path) {
// 1. 创建Requet对象
Request request = new Request.Builder().url( url ).build();
// 2. 创建OkHttpClient对象,发送请求,并处理回调
OkHttpClient client = HttpsUtil.handleSSLHandshakeByOkHttp();
client.newCall( request ).enqueue( new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
// 1. 获取下载文件的后缀名
String ext = url.substring( url.lastIndexOf( "." ) + 1 );
// 2. 根据当前时间创建文件名,避免重名冲突
final String fileName = System.currentTimeMillis() + "." + ext;
// 3. 获取响应主体的字节流
InputStream is = response.body().byteStream();
// 4. 将文件写入path目录
writeFile( is, path, fileName );
// 5. 在界面给出提示信息
tv_textView.post( new Runnable() {
@Override
public void run() {
tv_textView.setText( fileName + "下载成功,存放在" + path );
}
} );
}
}
@Override
public void onFailure(Call call, IOException e) {
Log.e( TAG, e.getMessage() );
runOnUiThread( new Runnable() {
@Override
public void run() {
tv_textView.setText( "下载文件失败" );
}
} );
}
} );
}
|