文章目录
- 前言
- 资料参考
- TCP
- 三次握手
- 四次挥手
- 三方框架
- OKHTTP
- Dispatcher
- 拦截器链
- 连接池是怎么进行复用的?什么时候会判定失效被回收
- Retrofit
- 常见问题
- 面试题
- Q:为什么要三次才能握手/四次才能挥手
- Q:四次挥手的第二步和第三步为什么要分开?
- Q:客户端最后为什么需要进行2MSL的等待?
- Q:了解Post和Get请求吗,说下他们的区别
- Q:Http请求报文包含哪几个部分?
- Q:Http请求头里面包含了什么信息?
- Q:常见的Http请求码
- Q:HTTP1.0 和 HTTP 1.1的区别
- Q:HTTP2.0 和 HTTP1.X 的区别
- Q:断点续传实现
- Q:HTTP 和 HTTPS
- Q:HTTPS 抓包
- Q:如何反抓包
- Q:HTTP请求的几种格式
前言
面试Android岗位的时候,网络相关是必不可少的,上到常用的网络请求框架、原理,下到底层的协议具体内容到过程,本篇依旧是对常见网络框架的高度概括和整理以及一些文章的索引、常见面试题,对于三方框架详细内容还是需要结合源码查看
资料参考
- 五层模型参考
- 三次握手和四次挥手
- 抓包工具: Fiddler、wireShark、TCPDump等等
- 小林coding的HTTP面试题
- https okhttp 应用自定义证书
- okhttp 实现https双向绑定
- 双向认证 SSL-pinning
TCP
- TCP/IP 协议簇 分层
- 握手和挥手流程见下
- 序列号:包序列号在TCP建立连接后,是累加的,从而包装通讯;三次握手中会确认双方的起始序列号
- 洪泛攻击:TCP第一次发送握手请求时,传递一个虚假的IP地址,服务端收到后因为这个IP是假的收不到第三次确认的请求,导致一直处于等待状态
- 特点
- 面向连接
- 可靠
- RTT(往返时延)、RTO(重传超时)
- 数据排列
- 流量控制
- 全双工
三次握手
- Client 发送
SYN = 1 , seq = x
,进入SYN_SEND 状态 - Server 收到后,会像Client 发送
ACK = 1,ack = x + 1 , SYN = 1,seq = y
,进入SYN_RECEIVE 状态 - Client收到并检查ACK = 1,ack 为 xxx +1 后,会进入 ESTABLISHED 状态,同时向Server发送
ACK = 1,ack = y + 1
,Server收到后也进入 ESTABLISHED 状态,三次握手结束
四次挥手
以Client发起关闭请求为例
- Client 发送
FIN,seq = x
,通知Server自己不再发送消息,进入 FIN_WAIT_1 状态 - Server 收到 请求,回复
ACK = 1,seq = x+1
,Client收到后进入 FIN_WAIT_2 状态 - Server 确认无消息发送了,发送
FIN,seq = y
,进入CLOSE 状态 ,Client 收到后发送ACK = 1,seq = y+1
,进入TIME_WATING
状态 - Server 收到最后的ACK消息后进入 CLOSED,结束流程,Client 再经过 2MSL 的等待后,确认服务端已经关闭(如果服务端没收到会重发,来回2MSL),进入CLOSED 状态,流程结束
三方框架
OKHTTP
- 概览
- OkHttpClient 、Request 对象都是使用构建者模式创建;核心请求是通过一个拦截器链获取(责任链陌生)
- 同步请求会直接阻塞执行,异步请求会根据队列加入线程池
- 请求执行完毕后会调用
Dispatcher#finished
方法,此时会判断队列是否为空来调用idleCallback
;如果是异步请求会调用Dispatcher#promoteCalls
来调整队列,视情况把等待的请求加入
- 核心
- Dispatcher(维护三个队列以及线程池等,管理请求的状态)
- 拦截器链(责任链模式)
- 五个okhttp的拦截器以及作用
Dispatcher
- 三个队列
private int maxRequests = 64;// 最大请求数
private int maxRequestsPerHost = 5; // 相同主机最大请求数
// 准备状态中的异步请求队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
// 执行中的异步请求队列(包含已经取消但是还没取消的请求)
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
// 同步请求队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
其中同步请求会直接加入队列,异步的会判断 最大请求数和相同主机最大请求数 ,如果超过了限制则加入等待队列,带有空闲时再进行调用
- 线程池
用于执行异步请求,核心参数如下
- 核心线程数:0
- 最大线程数:Integer.MAX_VALUE(实际会受到maxRequests的限制)
- 空闲线程空闲时间:60秒
- 阻塞策略:这个队列不保存数据,当用任务添加时会直接运行,如果没有空闲线程则新建一个线程执行
拦截器链
- 无论是同步还是异步最后都是调用
realCall#getResponseWithInterceptorChain
,在Interceptor#intercept
方法除了最后一个拦截器都会构建下一个RealInterceptorChain
,然后调用下一个拦截器的Interceptor#intercept
- 拦截器链
- 自定义的拦截器
- RetryAndFollowUpInterceptor(重试和重定向拦截器)
- BridgeInterceptor(桥接拦截器)(补充请求头等信息、进行GZIP解压等)
- CacheInterceptor(缓存拦截器)(缓存策略;DiskLruCache;Key:url进行MD5加密hex转换;)
- ConnectInterceptor(连接拦截器)(获取sreamAllocation,通过
sreamAllocation#newSteam
获取用于写入/读取IO流的一个实现类HTTPCodec)- CallServerInterceptor(发起具体链接)(将请求写入IO流,并读取结果)
连接池是怎么进行复用的?什么时候会判定失效被回收
- 引用计数器判断连接是否闲置
- 每次添加都会执行一个清理的runable,清理完会计算出下一次清理的时间(闲置链接离五分钟的时长/五分钟后)
- 无引用且超时,会根据LRU清理空闲的连接(闲置连接超过了5分钟或者闲置数量超过了5个,会清理最长的限制链接)
- 如果没有闲置的链接则结束循环;下次有添加时会重新开启这个清理任务(Runnable)
- 源码跟踪参考
Retrofit
- 概览
- 基于OKHTTP的 网络框架封装(官方使用示例)
- 核心是通过通过一系列设计模式(动态代理、适配器模式、工厂模式等)对OKHttp进行了使用封装
- 内部包括 数据转换 和 请求转换 的集合,可以根据声明的方法去找到合适的适配器进行转换
- 大致流程:获取定义的接口方法信息(解析、缓存) -> CallAdapter 转换请求对象 -> 发起请求 -> Converter 转换结果
- 主要成员变量
// ServiceMethod是对声明的方法解析后的类,这里的map是对该类的缓存
private final Map<Method, ServiceMethod<?, ?>> serviceMethodCache = new ConcurrentHashMap<>();
final okhttp3.Call.Factory callFactory;
final HttpUrl baseUrl;
final List<Converter.Factory> converterFactories; // 结果转换,如GSON等
final List<CallAdapter.Factory> callAdapterFactories; // 请求转换,将原始的retrofit的Call对象转换为各类对象如RxJava 的Observable
final @Nullable Executor callbackExecutor; // 回调执行器,安卓平台是用一个Handler 切换到主线程
- 设计模式
- 构建者模式(Retrofit、ServiceMethod等关键类的创建)
- 动态代理:
Retrofit#create
创建接口对应的操作类,这里会获取定义请求的信息并转换成ServiceMethod
- 适配器模式:对请求和结果进行转换的Adapter(通过工厂创建 )
- 外观模式:Retrofit这个类即使对多个转换器以及解析的
ServiceMethod
信息做了一个统一的入口- 策略模式:不同的转换器可以视为不同的策略,通过注解的返回类型查找到对应的策略
常见问题
- 回调如何切换到主线程?
retrofit 自带有一个 ExecutorCallAdapterFactory ,在这里会调用callbackExecutor 将回调切换到对应的线程,而默认的安卓平台里,其实现是一个主线程的Handler
面试题
Q:为什么要三次才能握手/四次才能挥手
- 需要三次握手的原因:双方都需要确立连接的建立
- 需要四次挥手的原因:因为是全双工通讯,双发都需要确认关闭
Q:四次挥手的第二步和第三步为什么要分开?
在第二步Server通知Client关闭后, 需要确认自己没有其他消息要发送了,之后才进行第三步,实际情况第三步可能会和第二步一起发送
Q:客户端最后为什么需要进行2MSL的等待?
最后发送给Server的报文可能会丢失,如果Server在第三步发送关闭报文时经过 1MSL的等待,没有收到第四步的 ACK = 1消息,则会重发。客户端只需等待2MSL,如果没有再次收到服务端的消息,则说明服务端已经收到确认了。此时双方都关闭链接,流程完毕
Q:了解Post和Get请求吗,说下他们的区别
- post相对get更安全,因为get请求参数直接在url上,post请求参数放在requestbody中
- 对参数的数据类型,GET只接受ASCII字符,而POST没有限制
- 业界不成文的规定是,(大多数)浏览器通常都会限制url长度在2K个字节,而(大多数)服务器最多处理64K大小的url“
- GET产生一个TCP数据包;POST产生两个TCP数据包
- 对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。- 请求缓存:GET 会被缓存,而post不会
- 二者是否可以混用:从功能上看其实是可以的,但是是不建议这样做的,因为不同的请求方法有不同的场景
Q:Http请求报文包含哪几个部分?
1.请求行,包含请求方法、URI、HTTP版本信息。
2.请求首部字段。
3.请求内容实体。
Q:Http请求头里面包含了什么信息?
包含一些基础的请求信息,包括可接受的响应内容类型、能显示的字符集如utf-8以及响应内容等客户端可识别的内容类型列表,还有From请求的地址以及Host服务器域名以及端口号
请求头 | 代表意思 | 示例 |
Accept | 可接受的响应内容类型 | Accept: text/plain |
Accept-Charset | 浏览器能够显示的字符集 | Accept-Charset: utf-8 |
Accept-Language | 可接受的响应内容语言列表 | Accept-Language: en-US |
From | 发起此请求的用户的邮件地址 | From: user@qq.com |
Host | 表示服务器的域名以及服务器所监听的端口号。如果所请求的端口是对应的服务的标准端口(80),则端口号可以省略 | Host: www.baidu.com:80 Host: www.baidu.com |
Connection | 是否保持持久连接 | Keep-Alive / close |
Cookie | 由之前服务器通过Set-Cookie(见下文)设置的一个HTTP协议Cookie | Cookie: $Version=1; Skin=new; |
- 在http/1.1 中Connection默认开启持久连接,除非设置为close才会关闭TCP连接
- 一个TCP连接可以发送多个HTTP请求
- 浏览器对同一个HOST建立的TCP连接有最大数限制,比如chrome最多允许6个
Q:常见的Http请求码
状态码 | 表达状态 |
100 | 表示服务器已接收了客户端请求,客户端可继续发送请求 |
2xx | 成功 |
200 | 请求成功 |
201 | 提示知道新文件的URL |
202 | 接受和处理、但处理未完成 |
203 | 返回信息不确定或不完整 |
204 | 请求收到,但返回信息为空 |
206 (Partial Content) | 返回部分内容(断点续传) |
3xx | 表示服务器要求客户端重定向 |
301 | 访问的资源已转移(永久性转移) |
302 | 访问的资源已转移(暂时性转移) |
304 | 客户端发送带条件的请求,找到资源但是不符合条件 |
4xx | 客户端错误 |
400 | 表示客户端请求有语法错误,不能被服务器所理解 |
401 | 发送的请求需要http认证 |
402 | 服务器已经理解请求,但是拒绝执行它 |
404 | 请求的内容不存在 |
5xx | 服务器错误 |
5xx | 表示服务器未能正常处理客户端的请求而出现意外错误 |
500 | 表示服务器发生不可预期的错误,导致无法完成客户端的请求 |
503 | 表示服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常 |
Q:HTTP1.0 和 HTTP 1.1的区别
- 缓存处理 引入更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓存策略
- 带宽优化及网络连接使用 请求头引入了range头域,允许只请求资源的某个部分,返回码206(Partial Content)(断点续传)
- 错误通知的管理,新增24个错误状态响应码
- Host头处理 请求消息和响应消息都支持Host头域,支持一台物理服务器上存在多个虚拟主机,共享同一个IP地址
- 默认开启长连接,减少建立和关闭连接的消耗和延迟
- 管道传输,但容易造成队头阻塞
Q:HTTP2.0 和 HTTP1.X 的区别
- 新的二进制格式(Binary Format),HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
- 多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。
- header压缩,如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
- 服务端推送(server push),同SPDY一样,HTTP2.0也具有server push功能。
Q:断点续传实现
- HTTP1.1 默认支持Range 和 Content Range 请求头,分别是客户端发送请求的范围和服务器响应的范围
- 客户端本地记录文件上传/下载的记录,在上传/下载时带上请求头
Q:HTTP 和 HTTPS
- 菜鸟教程参考
- HTTPS 四次握手可以参考小林Coding的面试文章
Q:HTTPS 抓包
- charles 抓包配置参考
- 安卓7.0 以下用户添加的证书可以直接信任,用Fidder等工具可以抓
- 安卓7.0 以上需要把证书放在系统目录下(需要root权限),或者运行在VX上面、对目标app进行反编译
- 如果是自己的app,可以添加信任证书
- Charles抓包HTTPS原理(中间人)
Q:如何反抓包
- 请求内容加密:把请求转换成byte数组,在C层进行加密(contentType: application/octet-stream)
- App内预置证书,对服务端证书进行校验,防抓包(SSL=PINNING)
- 代理判断,如果使用了代理请求失败
Q:HTTP请求的几种格式
- 参考
- application/x-www-form-urlencoded(键值对)
- multipart/form-data ()
- application/json (json格式)
- application/octet-stream(二进制流)