文章目录

  • 前言
  • 资料参考
  • 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岗位的时候,网络相关是必不可少的,上到常用的网络请求框架、原理,下到底层的协议具体内容到过程,本篇依旧是对常见网络框架的高度概括和整理以及一些文章的索引、常见面试题,对于三方框架详细内容还是需要结合源码查看

资料参考

TCP

  • TCP/IP 协议簇 分层

Android 常用网络协议 android 网络协议面试_面试

  • 握手和挥手流程见下
  • Android 常用网络协议 android 网络协议面试_Android 常用网络协议_02

  • 序列号:包序列号在TCP建立连接后,是累加的,从而包装通讯;三次握手中会确认双方的起始序列号
  • 洪泛攻击:TCP第一次发送握手请求时,传递一个虚假的IP地址,服务端收到后因为这个IP是假的收不到第三次确认的请求,导致一直处于等待状态
  • 特点
  1. 面向连接
  2. 可靠
  3. RTT(往返时延)、RTO(重传超时)
  4. 数据排列
  5. 流量控制
  6. 全双工
三次握手
  1. Client 发送SYN = 1 , seq = x ,进入SYN_SEND 状态
  2. Server 收到后,会像Client 发送 ACK = 1,ack = x + 1 , SYN = 1,seq = y,进入SYN_RECEIVE 状态
  3. Client收到并检查ACK = 1,ack 为 xxx +1 后,会进入 ESTABLISHED 状态,同时向Server发送 ACK = 1,ack = y + 1,Server收到后也进入 ESTABLISHED 状态,三次握手结束
四次挥手

以Client发起关闭请求为例

  1. Client 发送 FIN,seq = x,通知Server自己不再发送消息,进入 FIN_WAIT_1 状态
  2. Server 收到 请求,回复ACK = 1,seq = x+1,Client收到后进入 FIN_WAIT_2 状态
  3. Server 确认无消息发送了,发送 FIN,seq = y,进入CLOSE 状态 ,Client 收到后发送ACK = 1,seq = y+1,进入 TIME_WATING 状态
  4. Server 收到最后的ACK消息后进入 CLOSED,结束流程,Client 再经过 2MSL 的等待后,确认服务端已经关闭(如果服务端没收到会重发,来回2MSL),进入CLOSED 状态,流程结束

三方框架

OKHTTP
  • 概览
  1. OkHttpClient 、Request 对象都是使用构建者模式创建;核心请求是通过一个拦截器链获取(责任链陌生)
  2. 同步请求会直接阻塞执行,异步请求会根据队列加入线程池
  3. 请求执行完毕后会调用Dispatcher#finished 方法,此时会判断队列是否为空来调用idleCallback;如果是异步请求会调用Dispatcher#promoteCalls来调整队列,视情况把等待的请求加入
  • 核心
  1. Dispatcher(维护三个队列以及线程池等,管理请求的状态)
  2. 拦截器链(责任链模式)
  3. 五个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<>();

其中同步请求会直接加入队列,异步的会判断 最大请求数和相同主机最大请求数 ,如果超过了限制则加入等待队列,带有空闲时再进行调用

  • 线程池

用于执行异步请求,核心参数如下

  1. 核心线程数:0
  2. 最大线程数:Integer.MAX_VALUE(实际会受到maxRequests的限制)
  3. 空闲线程空闲时间:60秒
  4. 阻塞策略:这个队列不保存数据,当用任务添加时会直接运行,如果没有空闲线程则新建一个线程执行
拦截器链
  • 无论是同步还是异步最后都是调用 realCall#getResponseWithInterceptorChain ,在Interceptor#intercept 方法除了最后一个拦截器都会构建下一个RealInterceptorChain ,然后调用下一个拦截器的Interceptor#intercept
  • 拦截器链
  1. 自定义的拦截器
  2. RetryAndFollowUpInterceptor(重试和重定向拦截器)
  3. BridgeInterceptor(桥接拦截器)(补充请求头等信息、进行GZIP解压等)
  4. CacheInterceptor(缓存拦截器)(缓存策略;DiskLruCache;Key:url进行MD5加密hex转换;)
  5. ConnectInterceptor(连接拦截器)(获取sreamAllocation,通过 sreamAllocation#newSteam 获取用于写入/读取IO流的一个实现类HTTPCodec)
  6. CallServerInterceptor(发起具体链接)(将请求写入IO流,并读取结果)
连接池是怎么进行复用的?什么时候会判定失效被回收
  • 引用计数器判断连接是否闲置
  • 每次添加都会执行一个清理的runable,清理完会计算出下一次清理的时间(闲置链接离五分钟的时长/五分钟后)
  • 无引用且超时,会根据LRU清理空闲的连接(闲置连接超过了5分钟或者闲置数量超过了5个,会清理最长的限制链接)
  • 如果没有闲置的链接则结束循环;下次有添加时会重新开启这个清理任务(Runnable)
  • 源码跟踪参考
Retrofit
  • 概览
  1. 基于OKHTTP的 网络框架封装官方使用示例
  2. 核心是通过通过一系列设计模式(动态代理、适配器模式、工厂模式等)对OKHttp进行了使用封装
  3. 内部包括 数据转换请求转换 的集合,可以根据声明的方法去找到合适的适配器进行转换
  4. 大致流程:获取定义的接口方法信息(解析、缓存) -> 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 切换到主线程
  • 设计模式
  1. 构建者模式(Retrofit、ServiceMethod等关键类的创建)
  2. 动态代理:Retrofit#create 创建接口对应的操作类,这里会获取定义请求的信息并转换成ServiceMethod
  3. 适配器模式:对请求和结果进行转换的Adapter(通过工厂创建 )
  4. 外观模式:Retrofit这个类即使对多个转换器以及解析的ServiceMethod 信息做了一个统一的入口
  5. 策略模式:不同的转换器可以视为不同的策略,通过注解的返回类型查找到对应的策略
常见问题
  • 回调如何切换到主线程?

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
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(二进制流)