欧酷网

您的位置:主页>移动开发>

OkHttp使用踩坑记录总结(一):OkHttpClient单例和长连接Connection Keep-Alive

说明

在项目中对第三方服务的调用,使用了OkHttp进行http请求,这当中踩了许多坑。本篇博文将对OkHttp使用过程遇到的问题进行总结记录。

正文

OkHttpClient 单例

在刚开始使用时,没有将OkHttpClient单例化,造成的后果就是服务器OOM异常,can’t create native thread…

为什么会出现OOM,提示无法创建本地线程?
通过查看资料文档发现,每个client对象都有自己的线程池和连接池,如果为每个请求都创建一个client对象,自然会出现内存溢出。所以官方建议OkHttpClient应该单例化,重用连接和线程能降低延迟和减少内存消耗

OkHttpClients should be shared
OkHttp performs best when you create a single OkHttpClient instance and reuse it for all of your HTTP calls. This is because each client holds its own connection pool and thread pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a client for each request wastes resources on idle pools.

官方介绍了三种创建client的方式:

  1. new OkHttpClient()
    该方式将创建一个使用默认设置的client单例对象。
  2. new OkHttpClient.Builder()
    该方式允许自定义配置自己的单例client对象。配置connectionTimeout, readTimeout, writeTimeout等参数。
 okHttpClient = new OkHttpClient.Builder()
                        .connectTimeout(50L, TimeUnit.SECONDS)
                        .readTimeout(60L, TimeUnit.SECONDS)
                        .build();
 
  1. okHttpclient.newBuilder()
    该方式通过已经存在的client对象,创建特殊需要的client对象。如 我们通过上个方法创建了自定义配置的单例client对象,但是针对某些场景需要调整某些参数,那么就需要使用该方法创建定制的client。新client对象与旧client对象共享连接池,线程池和其他配置

You can customize a shared OkHttpClient instance with newBuilder(). This builds a client that shares the same connection pool, thread pools, and configuration. Use the builder methods to configure the derived client for a specific purpose.

OkHttpClient myClient = okHttpClient.newBuilder()
                        .readTimeout(80L, TimeUnit.SECONDS).build();
 

通过该方式解决了OkHttpClient针对不同请求设置不同参数的问题。在这篇文章中

HTTP客户端连接,选择HttpClient还是OkHttp?

作者对OkHttpClient和HttpClient作了性能比较,可以看出OkHttpClient的性能要好于HttpClient。 其中在非单例比较时,就单纯的建立连接耗时,OkHttpClient也优于HttpClient。

长连接 Connection: keep-alive

之所以使用OkHttp是因为它能高效率,高吞吐量的进行http请求,优于HttpClient。它的高效率,官方给出了四个原因:

  1. 支持HTTP/2,允许相同地址请求可以共享一个socket连接。
  2. 在HTTP/2不可用时,连接池可以减少请求延迟。
  3. 支持透明的GZIP压缩响应
  4. 支持响应缓存,避免重复请求。

以上可以看出,OkHttp支持HTTP/1.1和HTTP/2协议。通过这篇文章

一文读懂 HTTP/1HTTP/2HTTP/3

,我们可以了解下HTTP协议不同版本的特点。

通过了解可以知道,HTTP/1.1和HTTP/2的http请求都是建立在TCP连接基础之上的,所以他们的性能极大的依赖于TCP连接的配置。通过我之前的文章

计算机网络

,可以对TCP进行了解。

每个TCP连接都会进行三次握手,并且因为TCP的拥塞控制使用的滑动窗口和慢开始算法导致网络带宽利用率不高。所以,在HTTP/2不可用时,OkHttp使用了连接池,避免为每个请求都创建连接

OkHttp在创建连接时,默认创建长连接,这是因为在HTTP1.1中Connection: keep-alive是默认设置的,而HTTP1.0是Connection: close, 这表示每次请求完都会关闭连接,并且OkHttp不支持HTTP1.0。所以,OkHttp使用连接池复用这些长连接。

复用长连接,避免了创建TCP连接的三次握手和慢开始,我们简单了解下HTTP请求头中Connection和Keep-Alive。

Connection作为请求头的通用参数,其控制了请求连接在当前事务完成后是否继续保持。如果其值被设置为keep-alive, 则表示该连接是长连接,允许之后的相同地址的请求复用。还可以设置为close,每次请求结束后关闭连接。但是该参数在HTTP/2中不再使用。

Keep-Alive同样作为请求头通用参数,其允许发送方设置连接的使用规则。参数timeout设置了空闲连接的最小存活时间,单位为秒(s);参数max设置了一个连接最多可以进行多少个请求。

然而在实际使用中,服务器会时不时抛出java.net.SocketException: Connection reset异常。通过这篇文章

Connection reset原因分析和解决方案

,可以知道在使用OkHttp时,客户端和服务端连接应该保持一致,要么都是长连接,要么都是短连接。对此,服务端我们无法设置,只能是设置Connection为close。

并且在查资料时,碰见了两个问题:

  1. OkHttp默认是使用长连接进行请求,那么如果要发送非长连接的请求如何实现?
    我们可以在请求时设置请求头参数Connection参数为close。
  2. OkHttp复用连接,但是当重新请求时,之前获得的session丢失,如何处理?

    问题详情


    这个问题,在服务端获取session后会在影响头的cookie中设置sesisonID,所以当复用连接进行请求时,需要从之前的请求头中获取sessionId,并设置到新请求的Cookie参数中。
Headers headers = firstResponse.headers();
requestBuilder = requestBuilder.addHeader("Cookie", headers.get("Set-Cookie"));
 

通过文档,我们可以了解OkHttp是如何建立连接的:

  1. 当进行请求时,OkHttp使用请求的URL和配置创建的OkHttpClient对象去创建一个地址(Address),该地址说明了如何连接服务端。
  2. 根据创建的地址尝试从连接池复用连接。
  3. 如果连接池中没有连接,则选择一个路由(Rote)创建连接。这通常意味着要发送一个DNS请求获取服务端的IP地址,如果需要还可以选择TLS版本和代理服务器。
  4. 如果是一个新的路由,则会通过socket连接,TLS管道或者是TLS连接与与服务端进行连接。
  5. 最后发送HTTP请求和读取响应。

如果服务器地址不可达,OkHttp则会重新选择路由进行重试恢复连接。并且当响应被接收后,连接会被放回连接池等待复用,当连接空闲时长超过配置值时,则会被关闭。

通过源码,可以看到在创建OkHttpClient对象时,其通过内部静态类Builder构造器进行创建配置。在Builder类中新建了ConnectionPool对象,但该连接池没有配置连接池的大小,而是设置了最大空闲连接数maxidleConnection和存活时长keepAliveDuration

public ConnectionPool() {
        this(5, 5L, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    ....
}
 

那OkHttp是如何控制连接池的大小呢?在此之前,我们先了解下OkHttp的同步请求和异步请求。

请看下篇文章

《OkHttp使用踩坑记录总结(二):OkHttp同步异步请求和连接池线程池》

  • 点赞

  • 收藏

  • 分享

  •    
    • 文章举报

布碗

 
发布了113 篇原创文章 ·    获赞 33 ·    访问量 20万+  

私信

关注

相关文章推荐