本文字数:11171字
预计阅读时间:28分钟
前言
在android开发中,无论是去解决网络请求中遇到的问题,还是优化网络请求的效率,都离不开最根本的网络传输协议(http和tcp等),所以本文的第一部分是对网络协议相关的基础知识进行讲解。掌握了必备的基础知识之后,接下来的第二部分,会对android中使用最广泛的网络请求框架okhttp进行分析,这款框架不仅对基础的网络请求逻辑进行了封装,还尽善尽美的替我们做好了几乎每一件可以去优化的事情,如果能很好的理解并使用它,我相信大部分网络请求相关的问题都可以很好的得到解决。本文的最后部分,想说一说我是如何使用kotlin反射对离线数据上传进行封装的。
网络传输协议
网络传输协议大概分为4层(图片来自于极客教程):

(图1)
即链路层、网络层、传输层、应用层。其中,链路层协议是用来在各个物理节点(主机、交换机、路由器)之间传输的协议,会把网络层的ip数据包和mac地址封装成帧,通过mac地址来定位传输以帧为单位的数据。网络层协议是通过ip地址进行路由选择。这两层的协议基本上不用我们操心,我们主要来看一下传输层和应用层协议。
TCP和UDP协议
TCP和UDP都是传输层协议,不关心数据的格式,只负责传输,把上层的数据分割成一段一段的去传输。TCP协议是可靠的,有连接的,顺序发送数据,顺序接收(收到数据之后会确认,得到确认之后才会继续发送)。UPD是不可靠的,无连接的,顺序发送数据,乱序接收(不需要确认,不关心有没有收到)。
TCP如何实现可靠传输
TCP传输的每一段数据都会被装在一个TCP头里,头的内容包括接收方端口号、序列号、标志位、确认码。
三次握手:TCP需要经过三次握手创建连接: 第一次握手:客户端首先向服务端发送一个标志位为SYN的数据段,它的序列号为X,表示要创建连接 第二次握手:服务端收到客户端的SYN数据段,会向客户端回应一个标志位为ACK的确认数据段,它的序列号为Y,还会附带一个确认号X+1,X+1表示是对序列号为X数据段的确认 第三次握手:客户端再向服务端回应一个标志位为ACK的确认数据段,它的序列号为X+1,确认号为Y+1,服务端收到后就完成了TCP连接的创建
为什么要使用三次握手?一个原因是可以协商双方的初始序列号,后续的传输都可以通过初始序列号偏移和确认。另外一个原因是可以防止服务端资源的浪费,比如一次握手就建立了连接,服务端开始向客户端发送数据,这段数据可能因为某种原因没有到达客户端,客户端误以为连接失败便不再接收数据,此时服务端依然在向客户端发送数据,造成资源的浪费。
有了三次握手创建的可靠连接,交换了初始序列号,后续通过确认号确认收到数据,如果没有收到就重新发送,由此便实现了可靠的数据传输
四次挥手:四次挥手用于安全的关闭连接 第一次挥手:客户端向服务端发送一个标志位为FIN的数据段,表示要断开连接 第二次挥手:服务端收到FIN数据段,向客户端发送一个标志位为ACK的确认数据段,然后开始发送最后的数据 第三次挥手:服务端发送完所有数据后,向客户端发送一个标志位为FIN的数据段 第四次挥手:客户端向服务端再发送一个标志位为ACK的确认数据段,连接关闭
HTTP协议
http即超文本传输协议,是应用层协议,它把应用层的各种类型数据封装为http报文,使用tcp协议分割为报文段进行传输。http报文分为两种,请求报文与响应报文。
请求报文的组成包括:请求行、头部字段、实体数据请求行的组成为:请求方式、url地址、http协议版本号
响应报文的组成包括:状态行、响应头、响应体状态行的组成为:状态码和http协议版本
常用请求方式有五种:GET:从服务端查询数据,请求报文里不包含实体数据 POST:向服务端提交数据,请求报文中包含实体数据 HEAD:获取响应报文的头部字段 PUT:直接向服务器写入或更新资源 DELETE:从服务器删除资源
http协议版本:http1.0:每次请求都需要重新建立tcp连接,连接无法复用,有性能问题
http1.1:引入了长连接keep-alive,各个请求是串行处理的,一个请求出现超时,其他请求就会被阻塞
http2.0:
使用二进制格式传输,1.x版本都是基于文本的
多路复用,一个tcp连接可以同时支持多个http请求,提升了效率
header优化:header进行了压缩且会进行缓存
支持服务端向客户端push消息
OkHttp原理
OKHttp已经替我们做好了几乎所有事情,包括创建http请求报文、建立tcp连接、设置缓存等,我们只需要根据自己的需要发号施令就可以发起网络请求了。它巧妙地使用了责任链模式,使各个功能模块独立出来,各司其职,就像流水线一样,产品每经过一个加工站,装上几个零件就发往下一个站点。请求报文和响应报文就是这样的产品,经过每一个处理单元,得到最终的请求数据或响应数据。
OKHttp中的 拦截器(interceptor) 就是http报文的加工站,我们只需要理解每一个interceptor的职责就可以搞清楚它的原理了。
在使用拦截器之前,第一件事是要创建请求数据,和我们前面说的http请求报文的内容类似,包括url地址、请求方式、头部字段、实体数据:
其中实体数据包括媒体类型和数据体:
接着就是把Request依次向每一个拦截器传递,注意这个拦截顺序是不能改变的,后面会详细说。请求完成,得到响应数据之后,再依次向上传递。
具体实现: