android网络开发Android开发服务器学习

OKHttp3源码解析

2016-10-25  本文已影响5535人  风骨依存

参考资源

鉴于一些关于OKHttp3源码的解析文档过于碎片化,本文系统的,由浅入深得,按照网络请求发起的流程顺序来讲解OkHttp3的源码。在自己学习的同时,给大家分享一些经验。

主要架构和流程

OKHttpClient、Call

OKHttp3在项目中发起网络请求的API如下:

okHttpClient.newCall(request).execute();

OKHttpClient类:

它的方法只有一个:newCall.返回一个Call对象(一个准备好了的可以执行和取消的请求)。

Call接口:

public interface Call {
  
  Request request();
 
  //同步的方法,直接返回Response
  Response execute() throws IOException;
  
  //异步的,传入回调CallBack即可(接口,提供onFailure和onResponse方法)
  void enqueue(Callback responseCallback);
  
  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  interface Factory {
    Call newCall(Request request);
  }
}

Call接口提供了内部接口Factory(用于将对象的创建延迟到该工厂类的子类中进行,从而实现动态的配置,工厂方法模式)。

实际的源码中,OKHttpClient实现了Call.Factory接口,返回了一个RealCall对象。

@Override 
public Call newCall(Request request) {
    return new RealCall(this, request);
}

RealCall里面的两个关键方法是:execute 和 enqueue。分别用于同步和异步得执行网络请求。后面会详细介绍。

请求Request、返回数据Response

Request:
    public final class Request {
      //url字符串和端口号信息,默认端口号:http为80,https为443.其他自定义信息
      private final HttpUrl url;
      
      //"get","post","head","delete","put"....
      private final String method;
      
      //包含了请求的头部信息,name和value对。最后的形势为:$name1+":"+$value1+"\n"+ $name2+":"+$value2+$name3+":"+$value3...
      private final Headers headers;
      
      //请求的数据内容
      private final RequestBody body;
      
      //请求的附加字段。对资源文件的一种摘要。保存在头部信息中:ETag: "5694c7ef-24dc"。客户端可以在二次请求的时候,在requst的头部添加缓存的tag信息(如If-None-Match:"5694c7ef-24dc"),服务端用改信息来判断数据是否发生变化。
      private final Object tag;
      
      //各种附值函数和Builder类
      ...
        
     }

其中内部类RequestBody: 请求的数据。抽象类:

    public abstract class RequestBody {
     
      ...
      
      //返回内容类型
      public abstract MediaType contentType();
    
      //返回内容长度
      public long contentLength() throws IOException {
        return -1;
      }
    
      //如何写入缓冲区。BufferedSink是第三方库okio对输入输出API的一个封装,不做详解。
      public abstract void writeTo(BufferedSink sink) throws IOException;

    }

OKHttp3中给出了两个requestBody的实现FormBody 和 MultipartBody,分别对应了两种不同的MIME类型:"application/x-www-form-urlencoded"和"multipart/"+xxx.作为的默认实现。

Response:
    public final class Response implements Closeable {
      //网络请求的信息
      private final Request request;
      
      //网路协议,OkHttp3支持"http/1.0","http/1.1","h2"和"spdy/3.1"
      private final Protocol protocol;
      
      //返回状态码,包括404(Not found),200(OK),504(Gateway timeout)...
      private final int code;
      
      //状态信息,与状态码对应
      private final String message;
      
      //TLS(传输层安全协议)的握手信息(包含协议版本,密码套件(https://en.wikipedia.org/wiki/Cipher_suite),证书列表
      private final Handshake handshake;
      
      //相应的头信息,格式与请求的头信息相同。
      private final Headers headers;
      
      //数据内容在ResponseBody中
      private final ResponseBody body;
      
      //网络返回的原声数据(如果未使用网络,则为null)
      private final Response networkResponse;
      
      //从cache中读取的网络原生数据
      private final Response cacheResponse;
      
      //网络重定向后的,存储的上一次网络请求返回的数据。
      private final Response priorResponse;
      
      //发起请求的时间轴
      private final long sentRequestAtMillis;
      
      //收到返回数据时的时间轴
      private final long receivedResponseAtMillis;
    
      //缓存控制指令,由服务端返回数据的中的Header信息指定,或者客户端发器请求的Header信息指定。key:"Cache-Control"
      //详见<a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9">RFC 2616,14.9</a>
      private volatile CacheControl cacheControl; // Lazily initialized.
        
      //各种附值函数和Builder类型          ...
    }

Note:所有网络请求的头部信息的key,不是随便写的。都是RFC协议规定的。request的header与response的header的标准都不同。具体的见 List of HTTP header fields。OKHttp的封装类Request和Response为了应用程序编程方便,会把一些常用的Header信息专门提取出来,作为局部变量。比如contentType,contentLength,code,message,cacheControl,tag...它们其实都是以name-value对的形势,存储在网络请求的头部信息中。

我们使用了retrofit2,它提供了接口converter将自定义的数据对象(各类自定义的request和response)和OKHttp3中网络请求的数据类型(ReqeustBody和ResponseBody)进行转换。
而converterFactory是converter的工厂模式,用来构建各种不同类型的converter。

故而可以添加converterFactory由retrofit完成requestBody和responseBody的构造。

这里对retrofit2不展开讨论,后续会出新的文章来详细讨论。仅仅介绍一下converterFacotry,以及它是如何构建OkHttp3中的RequestBody和ResponseBody的。

我们项目中的一个converterFacotry代码如下:

    public class RsaGsonConverterFactory extends Converter.Factory {
   
    //省略部分代码
    ...
    
    private final Gson gson;

    private RsaGsonConverterFactory(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    } 
    //将返回的response的Type,注释,和retrofit的传进来,返回response的转换器。Gson只需要type就可以将responseBody转换为需要的类型。
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new RsaGsonResponseBodyConverter<>(gson, adapter);
    }
    //将request的参数类型,参数注释,方法注释和retrofit传进来,返回request的转换器。Gson只需要type就可以将request对象转换为OKHttp3的reqeustBody类型。
    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new RsaGsonRequestBodyConverter<>(gson, adapter);
    }
    }

该Factory(工厂方法模式,用于动态的创建对象)主要是用来生产response的converter和request的converter。显然我们使用了Gson作为数据转换的桥梁。分别对应如下两个类:

直接将value中的值封装为JsonReader供Gson的TypeAdapter读取,获取转换后的对象。

上面的流操作使用的是第三方库okio。可以看到,retrofitokhttp,okio这三个库是完全相互兼容并互相提供了专有的API。

请求的分发和线程池技术

OKHttpClient类中有个成员变量dispatcher负责请求的分发。既在真正的请求RealCall的execute方法中,使用dispatcher来执行任务:

OKHttp3中分发器只有一个类 ——Dispathcer.

Dispathcer

(1) 其中包含了线程池executorService:

 public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

参数:

(2) 执行同步的Call:直接加入runningSyncCalls队列中,实际上并没有执行该Call,交给外部执行。

  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

(3) 将Call加入队列:如果当前正在执行的call数量大于maxRequests,64,或者该call的Host上的call超过maxRequestsPerHost,5,则加入readyAsyncCalls排队等待。否则加入runningAsyncCalls,并执行。

  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

(4) 从ready到running的轮转,在每个call 结束的时候调用finished,并:

    private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized (this) {
          if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
          //每次remove完后,执行promoteCalls来轮转。
          if (promoteCalls) promoteCalls();
          runningCallsCount = runningCallsCount();
          idleCallback = this.idleCallback;
        }
        //线程池为空时,执行回调
        if (runningCallsCount == 0 && idleCallback != null) {
          idleCallback.run();
        }
      }

(5) 线程轮转:遍历readyAsyncCalls,将其中的calls添加到runningAysncCalls,直到后者满。

    private void promoteCalls() {
        if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
        if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
    
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
          AsyncCall call = i.next();
            
          if (runningCallsForHost(call) < maxRequestsPerHost) {             i.remove();
            runningAsyncCalls.add(call);
            executorService().execute(call);
          }
    
          if (runningAsyncCalls.size() >= maxRequests) return; 
        }
      }  

执行请求

同步的请求RealCall 实现了Call接口:
可以execute,enqueue和cancle。
异步的请求AsyncCall(RealCall的内部类)实现了Runnable接口:
只能run(调用了自定义函数execute).

execute 对比:

    @Override protected void execute() {
          boolean signalledCallback = false;
          try {
            //实际执行。
            Response response = getResponseWithInterceptorChain();
            //执行回调
            if (retryAndFollowUpInterceptor.isCanceled()) {
              signalledCallback = true;
              responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
            } else {
              signalledCallback = true;
              responseCallback.onResponse(RealCall.this, response);
            }
          } catch (IOException e) {
            if (signalledCallback) {
              Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
            } else {
              responseCallback.onFailure(RealCall.this, e);
            }
          } finally {
            //执行完毕,finish
            client.dispatcher().finished(this);
          }
        }

实际上的执行函数都是getResponseWithInterceptorChain():

    private Response getResponseWithInterceptorChain() throws IOException {
        //创建一个拦截器列表
        List<Interceptor> interceptors = new ArrayList<>();
        //优先处理自定义拦截器
        interceptors.addAll(client.interceptors());
        //失败重连拦截器
        interceptors.add(retryAndFollowUpInterceptor);
        //接口桥接拦截器(同时处理cookie逻辑)
        interceptors.add(new BridgeInterceptor(client.cookieJar()));
        //缓存拦截器
        interceptors.add(new CacheInterceptor(client.internalCache()));
        //分配连接拦截器
        interceptors.add(new ConnectInterceptor(client));
        //web的socket连接的网络配置拦截器
        if (!retryAndFollowUpInterceptor.isForWebSocket()) {
          interceptors.addAll(client.networkInterceptors());
        }
        //最后是连接服务器发起真正的网络请求的拦截器
        interceptors.add(new CallServerInterceptor(
            retryAndFollowUpInterceptor.isForWebSocket())); 
        Interceptor.Chain chain = new RealInterceptorChain(
            interceptors, null, null, null, 0, originalRequest);
        //流式执行并返回response
        return chain.proceed(originalRequest);
      }

这里的拦截器的作用:将一个流式工作分解为可配置的分段流程,既实现了逻辑解耦,又增强了灵活性,使得该流程清晰,可配置。

各个拦截器(Interceptor)

这里的拦截器有点像安卓里面的触控反馈的Interceptor。既一个网络请求,按一定的顺序,经由多个拦截器进行处理,该拦截器可以决定自己处理并且返回我的结果,也可以选择向下继续传递,让后面的拦截器处理返回它的结果。这个设计模式叫做责任链模式

与Android中的触控反馈interceptor的设计略有不同的是,后者通过返回true 或者 false 来决定是否已经拦截。而OkHttp这里的拦截器通过函数调用的方式,讲参数传递给后面的拦截器的方式进行传递。这样做的好处是拦截器的逻辑比较灵活,可以在后面的拦截器处理完并返回结果后仍然执行自己的逻辑;缺点是逻辑没有前者清晰。

拦截器接口的源码:

public interface Interceptor {
 Response intercept(Chain chain) throws IOException;

 interface Chain {
   Request request();

   Response proceed(Request request) throws IOException;

   Connection connection();
 }
}

其中的Chain是用来传递的链。这里的传递逻辑伪代码如下:
代码的最外层逻辑

 Request request = new Request(){};
 
 
 Arrlist<Interceptor> incpts = new Arrlist();
 Interceptor icpt0 = new Interceptor(){ XXX };
 Interceptor icpt1 = new Interceptor(){ XXX };
 Interceptor icpt2 = new Interceptor(){ XXX };
 ...
 incpts.add(icpt0);
 incpts.add(icpt1);
 incpts.add(icpt2);
 
 Interceptor.Chain chain  = new MyChain(incpts);
 chain.proceed(request);

封装的Chain的内部逻辑

 public class MyChain implement Interceptor.Chain{
    Arrlist<Interceptor> incpts;
    int index = 0;
    
    public MyChain(Arrlist<Interceptor> incpts){
        this(incpts, 0);
    }
    
    public MyChain(Arrlist<Interceptor> incpts, int index){
        this.incpts = incpts;
        this.index =index;
    }
    
    public void setInterceptors(Arrlist<Interceptor> incpts ){
        this.incpts = incpts;
    }
 
    @override
    Response proceed(Request request) throws IOException{
            Response response = null;
            ...
            //取出第一个interceptor来处理
            Interceptor incpt = incpts.get(index);
            //生成下一个Chain,index标识当前Interceptor的位置。
            Interceptor.Chain nextChain = new MyChain(incpts,index+1);
            response =  incpt.intercept(nextChain);
            ...
            return response;
    }
  } 

各个Interceptor类中的实现:

public class MyInterceptor implement Intercetpor{
    @Override 
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        //前置拦截逻辑
        ...
        Response response = chain.proceed(request);//传递Interceptor
        //后置拦截逻辑
        ...
        return response;
    }
}

在这个链中,最后的一个Interceptor一般用作生成最后的Response操作,它不会再继续传递给下一个。

intercept方法源码:

    @Override public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
    
        ...
    
        int followUpCount = 0;
        Response priorResponse = null;
        
        //循环入口
        while (true) {
          ...
    
          Response response = null;
          try {
            //一次请求处理,获得结果
            response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
          } catch (RouteException e) {
            ...
            continue;
          } catch (IOException e) {
            ...
            continue;
          } finally {
            ...
          }
    
          // 如果前一次请求结果不为空,讲它添加到新的请求结果中。通常第一次请求一定是异常请求结果,一定没有body。
          if (priorResponse != null) {
            response = response.newBuilder()
                .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
                .build();
          }
          //获取后续的请求,比如验证,重定向,失败重连...
          Request followUp = followUpRequest(response);
    
          if (followUp == null) {
            ...
            //如果没有后续的请求了,直接返回请求结果
            return response;
          }
           ...      
          //
          request = followUp;
          priorResponse = response;
        }
      }

获取后续的的请求,比如验证,重定向,失败重连

private Request followUpRequest(Response userResponse) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    Connection connection = streamAllocation.connection();
    Route route = connection != null
        ? connection.route()
        : null;
    int responseCode = userResponse.code();

    final String method = userResponse.request().method();
    switch (responseCode) {
      case HTTP_PROXY_AUTH:
        Proxy selectedProxy = route != null
            ? route.proxy()
            : client.proxy();
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
        }
        return client.proxyAuthenticator().authenticate(route, userResponse);

      case HTTP_UNAUTHORIZED:
        return client.authenticator().authenticate(route, userResponse);

      case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        // "If the 307 or 308 status code is received in response to a request other than GET
        // or HEAD, the user agent MUST NOT automatically redirect the request"
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
        // Does the client allow redirects?
        if (!client.followRedirects()) return null;

        String location = userResponse.header("Location");
        if (location == null) return null;
        HttpUrl url = userResponse.request().url().resolve(location);

        // Don't follow redirects to unsupported protocols.
        if (url == null) return null;

        // If configured, don't follow redirects between SSL and non-SSL.
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if (!sameScheme && !client.followSslRedirects()) return null;

        // Redirects don't include a request body.
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        if (HttpMethod.permitsRequestBody(method)) {
          if (HttpMethod.redirectsToGet(method)) {
            requestBuilder.method("GET", null);
          } else {
            requestBuilder.method(method, null);
          }
          requestBuilder.removeHeader("Transfer-Encoding");
          requestBuilder.removeHeader("Content-Length");
          requestBuilder.removeHeader("Content-Type");
        }

        // When redirecting across hosts, drop all authentication headers. This
        // is potentially annoying to the application layer since they have no
        // way to retain them.
        if (!sameConnection(userResponse, url)) {
          requestBuilder.removeHeader("Authorization");
        }

        return requestBuilder.url(url).build();

      case HTTP_CLIENT_TIMEOUT:
        // 408's are rare in practice, but some servers like HAProxy use this response code. The
        // spec says that we may repeat the request without modifications. Modern browsers also
    // repeat the request (even non-idempotent ones.)
    if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
      return null;
    }

    return userResponse.request();

  default:
    return null;
}

}

桥接拦截器的主要作用是将:

  1. 请求从应用层数据类型类型转化为网络调用层的数据类型。


  2. 将网络层返回的数据类型 转化为 应用层数据类型。

         1. 保存最新的cookie(默认没有cookie,需要应用程序自己创建,详见
                     [Cookie的API]
                     (https://square.github.io/okhttp/3.x/okhttp/okhttp3/CookieJar.html)
                     和
                     [Cookie的持久化]
                     (https://segmentfault.com/a/1190000004345545));
         2. 如果request中使用了"gzip"压缩,则进行Gzip解压。解压完毕后移除Header中的"Content-Encoding"和"Content-Length"(因为Header中的长度对应的是压缩前数据的长度,解压后长度变了,所以Header中长度信息实效了);
         3. 返回response。
    

补充:Keep-Alive 连接:
HTTP中的keepalive连接在网络性能优化中,对于延迟降低与速度提升的有非常重要的作用。
通常我们进行http连接时,首先进行tcp握手,然后传输数据,最后释放

http连接

这种方法的确简单,但是在复杂的网络内容中就不够用了,创建socket需要进行3次握手,而释放socket需要2次握手(或者是4次)。重复的连接与释放tcp连接就像每次仅仅挤1mm的牙膏就合上牙膏盖子接着再打开接着挤一样。而每次连接大概是TTL一次的时间(也就是ping一次),在TLS环境下消耗的时间就更多了。很明显,当访问复杂网络时,延时(而不是带宽)将成为非常重要的因素。
当然,上面的问题早已经解决了,在http中有一种叫做keepalive connections的机制,它可以在传输数据后仍然保持连接,当客户端需要再次获取数据时,直接使用刚刚空闲下来的连接而不需要再次握手

Keep-Alive连接

在现代浏览器中,一般同时开启6~8个keepalive connections的socket连接,并保持一定的链路生命,当不需要时再关闭;而在服务器中,一般是由软件根据负载情况决定是否主动关闭。

缓存拦截器的主要作用是将请求 和 返回 关连得保存到缓存中。客户端与服务端根据一定的机制,在需要的时候使用缓存的数据作为网络请求的响应,节省了时间和带宽。

客户端与服务端之间的缓存机制:
客户端缓存的实现:

OKHttp3的缓存类为Cache类,它实际上是一层缓存逻辑的包装类。内部有个专门负责缓存文件读写的类:DiskLruCache。于此同时,OKHttp3还定义了一个缓存接口:InternalCache。这个缓存接口类作为Cache的成员变量其所有的实现,都是调用了Cahce类的函数实现的。它们间具体的关系如下:

缓存的工作机制

InternalCache接口:

    public interface InternalCache {
      Response get(Request request) throws IOException;
    
      CacheRequest put(Response response) throws IOException;
    
      /**
       * 移除request相关的缓存数据
       */
      void remove(Request request) throws IOException;
    
      /**
       * 用网络返回的数据,更新缓存中数据
       */
      void update(Response cached, Response network);
    
      /** 统计网络请求的数据 */
      void trackConditionalCacheHit();
    
      /** 统计网络返回的数据 */
      void trackResponse(CacheStrategy cacheStrategy);
    }

Note:setInternalCache这个方法是不对应用程序开放的,应用程序只能使用cache(Cache cache)这个方法来设置缓存。并且,Cache内部的internalCache是final的,不能被修改。总结:internalCache这个接口虽然是public的,但实际上,应用程序是无法创建它,并附值到OkHttpClient中去的。

那么问题来了,为什么不用Cahce实现InternalCache这个接口,而是以组合的方式,在它的内部实现都调用了Cache的方法呢?
因为:

Cache类:

通过okio的读写API,实现它们之间灵活的切换。

DiskLruCache类:

简介:一个有限空间的文件缓存。

缓存使用了日志文件(文件名为journal)来存储缓存的数据目录和操作记录。一个典型的日志文件的文本文档如下:

     //第一行为缓存的名字
     libcore.io.DiskLruCache                            
     1                                                    //缓存的版本
     100                                                 //应用版本
     2                                                   //值的数量          
     //缓存记录:操作 key 第一个数据的长度 第二个数据的长度    
     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054    
     DIRTY 335c4c6028171cfddfbaae1a9c313c52
     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
     REMOVE 335c4c6028171cfddfbaae1a9c313c52
     DIRTY 1ab96a171faeeee38496d8b330771a7a
     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
     READ 335c4c6028171cfddfbaae1a9c313c52
     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6

操作记录:状态+key+额外擦数

一些冗余的操作记录,比如DIRTY,REMOVE...比较多的时候(大于2000个,或者超过总数量),会发器线程对该日志文件进行压缩(删除这些冗余的日志记录)。此时,会创建一个journal.tmp文件作为临时的文件,供缓存继续使用。同时还有个journal.bkp文件,用作journal文件的临时备份。
换文文件结构如下:

缓存的文件mu lu

工作原理:

  • 缓存内容Entry的这种工作机制(单个editor,带有序列号的内容快照)以最小的代价,实现了单线程修改,多线程读写的数据对象(否则则需要使用复杂的锁机制)既添降低了逻辑的复杂性,又提高了性能(缺点就是高并发情况下,导致数据频繁失效,导致缓存的命中率降低)。
  • 变化的序列号计数在很多涉及并发读取的机制都有使用。比如:SQlite的连接。
DiskLruCache缓存工作流

其返回的SnapShot数据快照,提供了Source接口(okio),供外部内类Cache直接转化为内存对象Cache.Entry。外部类进一步Canche.Entry转化外OKHttpClient使用的Response。

OkHttpClient从缓存中获取一个url对应的缓存数据的数据格式变化过程如下:

缓存数据格式变化过程

LRU的算法体现在:DiskLreCache的日志操作过程中,每一次读取缓存都产生一个READ的记录。由于缓存的初始化是按照日志文件的操作记录顺序来读取的,所以这相当于把这条缓存数据放置到了缓存队列的顶端,也就完成了LRU算法:last recent used,最近使用到的数据最新。

连接拦截器 和 最后的请求服务器的拦截器

这两个连接器基本上完成了最后发起网络请求的工作。追所以划分为两个拦截器,除了解耦之外,更重要的是在这两个流程之间还可以插入一个专门为WebSocket服务的拦截器( WebSocket一种在单个 TCP 连接上进行全双工通讯的协议,本文不做详解)。

关于OKHttp如何真正发起网络请求的,下面专门详细讲解。

发起网络请求

  • 新建的连接connection会存放到一个缓存池connectionpool中。网络连接完成后不会立即释放,而是存活一段时间。网络连接存活状态下,如果有相同的目标连接,则复用该连接,用它来进行写入写出流操作。
  • 统计每个connection上发起网络请求的次数,若次数为0,则一段时间后释放该连接。
  • 每个网络请求对应一个stream,connection,connectionpool等数据,将它封装为StreamAllocation对象。

具体的流程见下图:

网络请求API流程

类之间的关系如下:

类之间的关系

几个主要的概念:

Connection:连接。

真正的底层实现网络连接的接口。它包含了连接的路线,物理层socket,连接协议,和握手信息。

public interface Connection {
  /** 返回连接线路信息(包涵url,dns,proxy) */
  Route route();

  /**
   * 返回连接使用的Socket(网络层到应用层之间的一层封装)
   */
  Socket socket();

  /**
   * 返回传输层安全协议的握手信息
   */
  Handshake handshake();

  /**
   * 返回网络协议:Http1.0,Http1.1,Http2.0...
   */
  Protocol protocol();
}

在OKHttp3中的实现:RealConnection.

  • 包含了连接的信息,包括socket,它是与网络层交互的接口,真正实现网络连接;
  • 内部维护了一个列表List<StreamAllocation>,相当于发起网络请求的引用计数容器。下面详细讨论。
  • 包涵输入输出流,source和sink。但是Connection只负责将socket的操作,与source和sink建立起连接,针对source和sink的写入和读取操作,交给响应的Stream完成(解耦)。将socket封装为okio的代码:
    source = Okio.buffer(Okio.source(socket));
    sink = Okio.buffer(Okio.sink(socket));

这就将socket的读写操作标准华为okio的API。

  • 还有个内部类framedConnection,专门用来处理Http2和SPDY(goole推出的网络协议)的(不详细讨论)。
Stream: 完成网络请求的读写流程功能。

接口,源码如下:

public interface HttpStream {


  /** 创建请求的输出流*/
  Sink createRequestBody(Request request, long contentLength);

  /** 写请求的头部信息(Header) */
  void writeRequestHeaders(Request request) throws IOException;

  /** 将请求写入sokcet */
  void finishRequest() throws IOException;

  /** 读取网络返回的头部信息(header)*/
  Response.Builder readResponseHeaders() throws IOException;

  /** 返回网络返回的数据 */
  ResponseBody openResponseBody(Response response) throws IOException;

  /**
   * 异步的取消,并释放相关的资源。(connect pool的线程会自动完成)
   */
  void cancel();
}
  • 这个功能实际上是从connection中剥离出来:connection负责底层连接的实现,其写入request和读取response的功能交给stream来完成。
  • 之所以需要将该功能解耦出来,一个重要的原因:根据不同的网络请求协议,request的写入和response的读出,具有不同的实现。比如http1,http2等,响应的类为Http1xStream,Http2xStream。
ConnectionPool:内存连接池

负责管理HTTP连接,可以复用相同Address(包括url,dns,port,是否加密,proxy等信息)的连接,以减少网络延迟。

  • 维护了一个双端队列,默认的实现是:最多5个空闲的连接,这5个连接最多存活5分钟。
  • 直到连接被清除,底层的socket连接才会释放。
StreamAllocation: 网络流的分配计数器(下文简称SA)

计数功能:
每发起一个网络请求,都会新建SA对象。由于连接connection可以被复用,所以一个connection可以对应多次网络请求,即多个SA。所以RealConnection中维护了一个SA的列表。每次创建新的SA的时候,会在对应的connection的列表+1。以下情形下,会把列表-1,并释放SA对应的资源:

该引用计数的列表的作用:统计一个connection对应的网络请求的数量,如果为空,则connection进入空闲,开始倒计时回收。未回收之前的connection都可以复用,降低网络延迟(因为创建和销毁连接的开销比较长,如果时HTTP,则需要三次握手)。

SA中的方法:

最后的发起网络请求的代码
    @Override 
    public Response intercept(Chain chain) throws IOException {
    
        //获取httpstream对象,在之前的ConnectInterceptor中创建
        HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();
        StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
        Request request = chain.request();
    
        long sentRequestMillis = System.currentTimeMillis();
        
        //写请求的header
        httpStream.writeRequestHeaders(request);
        
        //写请求的body
        if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
          Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
          BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
          request.body().writeTo(bufferedRequestBody);
          bufferedRequestBody.close();
        }
        
        //完成网络请求
        httpStream.finishRequest();
    
        //读取网络请求返回的header
        Response response = httpStream.readResponseHeaders()
            .request(request)
            .handshake(streamAllocation.connection().handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();
    
        //读取网络请求返回的body
        if (!forWebSocket || response.code() != 101) {
          response = response.newBuilder()
              .body(httpStream.openResponseBody(response))
              .build();
        }
                    
        ...
        
        //返回结果
        return response;
        }  

深入网络请求Socket

未完待续...

上一篇 下一篇

猜你喜欢

热点阅读