美文网首页
Android网络基础知识

Android网络基础知识

作者: Peakmain | 来源:发表于2021-11-01 11:06 被阅读0次

    网络体系

    • 主要关注TCP/IP模型,了解OSI模型即可


      image.png

    TCP/IP协议族

    TCP/IP概念层模型 功能 TCP/IP协议族
    文件传输,电子邮件,文件服务 FTP、HTTP、DNS
    应用层 数据格式化,代码格式化,数据加密
    解除或建立与别的接点的联系
    传输层 提供端对端的接口 TCP/UDP
    网络层 为数据包选择路由 IP、ICMP、RIP
    链路层 传输有地址的帧以及错误检测功能 PPP、ARP、RARP
    以二进制数据形式在物理媒体上传输数据 ISO2110、IEEE802
    image.png
    • TCP:面向连接的、可靠的流协议
    • UDP:面向无连接的通讯协议
    • IP:源地址和目的地址之间传送的数据包
    • ICMP:控制报文协议
    • IGMP:Internet组管理协议
    • ARP:地址解析协议
    • RARP:反向地址转化协议

    端口号

    端口号规定为16位,即允许一个IP主机有2的16次方65535个不同的端口。其中

    • 0~1023:分配给系统的端口号
    • 1024~49151:登记端口号,主要是让第三方应用使用
    • 49152~65535:短暂端口号,是留给客户进程选择暂时使用,一个进程使用完就可以供其他进程使用。

    Socket使用时,可以用1024~65535的端口号

    三次握手和四次挥手

    三次握手

    • 为什么是三次握手
      TCP是面向连接的,所以需要双方都确认连接的建立。三次握手目的是交换tcp通信的初始序号,如果是两次握手,只能交换一方的tcp通信序号,四次又属于浪费


      image.png
    • 第一次握手
      客户端请求建立连接,发送SYN包,并进入SYN_SENT状态,等待服务器确认
    • 第二次握手
      服务端应答客户端,并请求建立连接,服务器进入SYN_RECV状态
    • 第三次握手
      客户端进入ESTABLISHED,并针对服务端请求确认应答,发送完毕,服务器进入ESTABLISHED

    三次握手的漏洞
    syn洪泛攻击

    • 定义
      通过网络服务所在的端口发送大量伪造原地址的攻击报文,发送到服务端,造成服务端上的半开连接队列被占满,从而阻止其他用户进行访问
    • 原理
      攻击者客户端利用伪造的IP地址向服务端发出请求(第一次握手),而服务端的响应(第二次握手)的报文将永远发送不到真实的客户端,服务端在等待客户端的第三次握手(永远都不会有的),服务端在等待这种半开的连接过程中消耗了资源,如果有成千上万的这种连接,主机资源将被耗尽,从而达到攻击的目的
    • 解决方案
      1.无效连接监控释放
      2.延缓TCB分配方法
      3.防火墙

    四次挥手

    • 断开一个TCP连接时,需要客户端和服务端总共发送4个以确认连接的断开
    • 为什么需要四次挥手
      TCP是双全工(客户端和服务器端可以相互发送和接收请求),所以需要双方都确认关闭连接


      image.png
    • 第一次挥手:客户端发送关闭请求
    • 第二次挥手:服务器响应客户端关闭请求
    • 第三次挥手:服务端发送关闭请求
    • 第四次挥手:客户端发送关闭请求

    TCP/IP中的数据包

    image.png

    滑动窗口

    • 发送方和接收方都会维护一个数据帧的序列,这个序列被称为窗口
    • 发送方的窗口大小由接收方确认

    目的

    • 确保数据不丢失
      如果发送的数据丢失了可以重新发送
    • 控制发送速度
      防止接收方的缓存不够大导致溢出,同时控制流量也可以避免网络堵塞

    HTTP协议

    • HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议
    Http的工作方式

    1、浏览器
    用户输入地址后回车或者点击链接->浏览器拼装Http报文并发送请求给服务器->服务器处理请求后发送响应报文给浏览器->浏览器解析响应报文并使用渲染引擎显示到界面
    2、手机App
    用户点击或界面自动触发联网需求->android代码调用拼装HTTP报文并发送请求到服务器->服务器处理请求后发送响应报文给手机->Android代码处理响应报文并作出响应处理(如存储数据、加工数据、显示数据到界面)

    URL

    URL格式

    image.png
    • schema协议: http/https/ftp.
    • host: web服务器的ip地址或者域名
    • port: 服务端端口, http默认访问的端口是80
    • path: 资源访问路径
    • query-string: 查询参数
        public static void main(String[] args) throws MalformedURLException {
            URL url = new URL("https://www.baidu.com:443");
            URL relativeUrl = new URL(url, "/s?wd=peakmain&rsv_spt=1#name=peakmain1");
            System.out.println("协议:" + relativeUrl.getProtocol());
            System.out.println("主机:" + relativeUrl.getHost());
            System.out.println("端口:" + relativeUrl.getPort());
            System.out.println("文件路径:" + relativeUrl.getPath());
            System.out.println("相对路径:" + relativeUrl.getRef());
            System.out.println("查询的字符串:" + relativeUrl.getQuery());
        }
    
    HTTP请求的传输过程
    image.png
    一次完整http请求过程

    首先进行DNS域名解析

    • 三次握手建立TCP连接
    • 客户端向服务器发送请求命令:get/www.baidu.com/http/1.1
    • 客户端发送请求头信息
    • 服务器应答Http/1.1 200 ok
    • 返回响应头信息
    • 服务器向客户端发送数据
    • 服务器关闭TCP连接
    报文格式

    请求报文


    image.png

    响应报文


    image.png
    请求方式

    1、GET

    • 用于获取资源
    • 对服务器数据不进行修改
    • 不发送body,
    GET  /article/list/0/json HTTP/1.1
    Host:www.wanandroid.com
    

    对应的retortfit

        @GET("/article/list/{page}/json")
        fun getArticleList(@Path("page") page: Int): Observable<BaseEntity<Any>>
    

    2、POST

    • 用于增加或修改资源
    • 发送给服务器内容写在body
    POST  /user/login
    Host: www.wanandroid.com
    content-type: application/json;charset=UTF-8
    name=peakmain&password=123456
    

    对应的retorfit

        @POST("/user/login")
        @FormUrlEncoded
        fun login(
            @Field("username") username: String?,
            @Field("password") password: String?
        ): Observable<BaseEntity<Any>>
    

    3、PUT

    • 用于修改资源
    • 发送服务器的内容写在body内容
    PUT   /user/login
    Host: www.wanandroid.com
    content-type: application/json;charset=UTF-8
    name=peakmain&password=123456
    

    对应的retorfit

        @PUT("/user/login")
        @FormUrlEncoded
        fun login(
            @Field("username") username: String?,
            @Field("password") password: String?
        ): Observable<BaseEntity<Any>>
    

    4、DELETE

    • 用于删除资源
    • 不发送body

    5、HEAD

    • 和GET使用方法完全相同
    • 和GET唯一区别在于,返回的响应中没有body

    这里需要注意,只有post请求不是幂等,其他请求都是幂等

    Status Code状态码

    三位数字,⽤于对响应结果做出类型化描述(如「获取成功」「内容未找到」)

    • 1xx:临时性消息。如:100 (继续发送)、101(正在切换协议)
    • 2xx:成功。最典型的是 200(OK)、201(创建成功)
    • 3xx:重定向。如 301(永久移动)、302(暂时移动)、304(内容未改变)
    • 4xx:客户端错误。如 400(客户端请求错误)、401(认证失败)、403(被禁⽌)、404(找不到内容)
    • 5xx:服务器错误。如 500(服务器内部错误)
    Headers
    • Cache-Control:指定请求和响应遵循缓存机制

      • max-age:缓存的内容将在xx秒后失效
      • private:客户端可以缓存
      • public:客户端和代理服务器都可缓存
      • no-cache:指示请求不能缓存
    • Connection:表示是否需要持久连接,Http 1.1默认是持久连接

    • Accept-Encoding: 客户端接受的压缩编码类型。如 gzip

    • Range:可以请求实体的一个或者多个子范围

      • 表示头500个字节:bytes=0-499
      • 表示第二个500字节:bytes=500-999
      • 表示最后500个字节:bytes=-500
      • 表示500字节以后的范围:bytes=500-
      • 第一个字节和最后一个字节:bytes=0-0,-1
    • Accept-Charset: 客户端接受的字符集。如 utf-8

    • Content-Encoding:压缩类型。如 gzip

    • Content-Type:指定Body的类型。主要类型有四类

      • text/html
      • x-www-form-urlencoded
      • multitype/form-data
      • application/json,image/jpeg,application/zip...
    • Content-Length:指定body的长度字节

    http缓存机制和原理
    image.png
    • 对比缓存:缓存标识(Last-Modified/If-Modified-Since)发送到服务器进行比较,如果缓存一样,返回304,如果变了就返回数据

      • 服务器返回数据的时候,会返回Last-Modified,下次客户端请求,服务器通过If-Modified-Since进行时间对比(If-Modified-Since是否大于Last-Modified,大于等于则表示资源修改过,没有则返回304)
    • Etag 主要为了解决 Last-Modified 无法解决的一些问题。当前资源在服务器的唯一标识UUID(生成规则由服务器规定)

    • 客户端第一次请求的时候,服务器会返回Etag,下次请求客户端会发送一个if-None-Match,服务器会比较Etag和If-None-Match,如果两个相同,则返回304,否则返回200

    • 强制缓存:

      • Expires:服务器返回给客户端的到期时间,Http1.0
      • Cache-Control:Http1.1 弥补Expires的缺陷而设计的

    Http/Https

    Http Https
    数据都是未加密的,也就是明文 对Http协议传输的数据进行加密(SSL),保证会话过程中的安全性
    TCP端口默认为:80 TCP端口默认为443
    SSL协议加密方式

    SSL协议用到对称加密和非对称加密(公钥加密),在建立传输链路时,SSL首先对对称加密的秘钥使用公钥进行非对称加密,链路建立好之后,SSL对传输内容使用对称加密

    对称加密 非对称加密
    使⽤公钥对数据进⾏加密得到密⽂;使⽤私钥对数据进⾏解密得到原数据 通信双⽅使⽤同⼀个密钥,使⽤加密算法配合上密钥来加密,解密时使⽤加密过程的完全逆过程配合密钥来进⾏解
    速度快,可加密内容较大 加密速度慢,能提供更好的身份认证技术
    DES、AES RSA、背包算法
    窃听风险/篡改风险/冒充风险
    Https单向认证
    image.png
    Https双向认证
    image.png

    OKHttp

    优点

    • 支持HTTP/2并允许对同一主机的所有请求共享一个套接字
    • 通过连接池,减少了请求延迟
    • 默认通过GZip压缩数据
    • 响应缓存,避免了重复请求的网络
    • 请求失败自动重试主机的其他ip,自动重定向
    调用流程
    image.png
    • 分发器:内部维护队列与线程池,完成请求调配;
    • 拦截器:五大默认拦截器完成整个请求过程
    分发器

    异步请求工作流程

    image.png
    什么条件加入到runningAsyncCalls
      void enqueue(AsyncCall call) {
        synchronized (this) {
          readyAsyncCalls.add(call);
        }
        promoteAndExecute();
      }
      private boolean promoteAndExecute() {
        assert (!Thread.holdsLock(this));
    
        List<AsyncCall> executableCalls = new ArrayList<>();
        boolean isRunning;
        synchronized (this) {
          for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();
    
            if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
            if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
    
            i.remove();
            executableCalls.add(asyncCall);
            runningAsyncCalls.add(asyncCall);
          }
          isRunning = runningCallsCount() > 0;
        }
    
        for (int i = 0, size = executableCalls.size(); i < size; i++) {
          AsyncCall asyncCall = executableCalls.get(i);
          asyncCall.executeOn(executorService());
        }
    
        return isRunning;
      }
    
    • 首先会将AsyncCall加入到readyAsyncCalls中
    • 遍历所有readyAsyncCalls,当runingAsyncCalls大于64或者统一域名正在请求的个数大于5就直接返回
    • 当满足条件,将AsyncTask加入到runningAsyncCalls并加入executableCalls集合中
    • 将任务放到线程池里面进行执行

    Ready什么条件加入running

        @Override protected void execute() {
          boolean signalledCallback = false;
          timeout.enter();
          try {
            Response response = getResponseWithInterceptorChain();
          } finally {
            client.dispatcher().finished(this);
          }
        }
      }
      void finished(AsyncCall call) {
        finished(runningAsyncCalls, call);
      }
      private <T> void finished(Deque<T> calls, T call) {
        Runnable idleCallback;
        synchronized (this) {
          if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
          idleCallback = this.idleCallback;
        }
    
        boolean isRunning = promoteAndExecute();
    
        if (!isRunning && idleCallback != null) {
          idleCallback.run();
        }
      }
    
    
    • 首先从runningAsyncCalls中移除AsyncCall
    • 之后又走到promoteAndExecute方法,继续从readyAsyncCalls取出数据进行处理
    线程池的execute
    • 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务
    • 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务加入队列
    • 如果队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还要创建线程运行这个任务
    • 如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出异常
    OkHttp的线程池
      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;
      }
    
    • 核心线程数为0,最大核心线程数为Integer.MAX_VALUE,代表永不会缓存线程
    • 60, TimeUnit.SECONDS代表当一个线程无事可做,超过一定的时间(60s),线程会判断,如果当前运行的线程数大于corePoolSiz,那么这个线程就会被停掉,所以线程池所有的任务完成后,它最终会收缩到corePoolSize的大小
    • Okhttp的线程池工作行为:无等待,最大并发
    队列 特点
    ArrayBlockingQueue 基于数组的阻塞队列,初始化需要指定固定的大小
    LinkedBlockingQueue 基于链表实现的阻塞队列,初始化可以指定大小,也可以不指定
    SynchronousQueue 无容量队列
    五大拦截器
      Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        List<Interceptor> interceptors = new ArrayList<>();
        interceptors.addAll(client.interceptors());
        interceptors.add(retryAndFollowUpInterceptor);
        interceptors.add(new BridgeInterceptor(client.cookieJar()));
        interceptors.add(new CacheInterceptor(client.internalCache()));
        interceptors.add(new ConnectInterceptor(client));
        if (!forWebSocket) {
          interceptors.addAll(client.networkInterceptors());
        }
        interceptors.add(new CallServerInterceptor(forWebSocket));
    
        Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
            originalRequest, this, eventListener, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());
    
        return chain.proceed(originalRequest);
      }
    

    执行流程

    image.png
    • 重试拦截器判断用户是否取消了请求,在获得了结果之后,会根据响应码判断是否需要重定向,如果满足条件那么就会重启执行所有拦截器
    • 桥接拦截器负责将HTTP协议必备的请求头加入其中并添加一些默认的行为(如:GZIP压缩),在获得结果后,调用保存cookie接口并解析GZIP数据
    • 缓存拦截器,读取并判断是否使用缓存,获得结果后判断是否缓存
    • 连接拦截器负责找到或者新建一个连接,并获得对应的socket流,在获得结果后不进行额外的处理
    • 请求服务器拦截器进行真正的服务器的通信,向服务器发送数据,解析读取的响应数据

    一、重定向与重试拦截器
    1、重试的判定

      @Override public Response intercept(Chain chain) throws IOException {
              try {
              //请求出现了异常,那么releaseConnection 依然为true
            response = realChain.proceed(request, streamAllocation, null, null);
            releaseConnection = false;
          } catch (RouteException e) {
           //连接未成功,请求还没有发出去
            if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
              throw e.getFirstConnectException();
            }
            releaseConnection = false;
            continue;
          } catch (IOException e) {
            //请求发出去了,但是和服务器通信失败了(socket流正在读写数据的时候断开连接)
            boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
            if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
            releaseConnection = false;
            continue;
          } finally {
            // 
            if (releaseConnection) {
              streamAllocation.streamFailed(null);
              streamAllocation.release();
            }
          }
       }
      private boolean recover(IOException e, StreamAllocation streamAllocation,
          boolean requestSendStarted, Request userRequest) {
        streamAllocation.streamFailed(e);
    
        // 1、在配置OkHttpClient时设置了不允许重试(默认允许),则一旦发送请求失败不再重试
        if (!client.retryOnConnectionFailure()) return false;
    
        // requestSendStarted 只存在于HTTP2,暂不考虑
        if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
    
        // 2、判断是不是属于重试的异常
        if (!isRecoverable(e, requestSendStarted)) return false;
    
        // 3、是否存在更多的路由,比如设置了代理,DNS解析可能存在多个IP
        if (!streamAllocation.hasMoreRoutes()) return false;
        // For failure recovery, use the same route selector with a new connection.
        return true;
      }
      private boolean isRecoverable(IOException e, boolean requestSendStarted) {
        //1、是不是协议的异常
        if (e instanceof ProtocolException) {
          return false;
        }
    
        // 2、是不是Socket超时异常
        if (e instanceof InterruptedIOException) {
          return e instanceof SocketTimeoutException && !requestSendStarted;
        }
    
        // 3、是不是SSL格式异常
        if (e instanceof SSLHandshakeException) {
          if (e.getCause() instanceof CertificateException) {
            return false;
          }
        }
      //4、是不是SSL证书异常
        if (e instanceof SSLPeerUnverifiedException) {
          return false;
        }
        return true;
      }
    
    image.png

    2、重定向的判定

     @Override public Response intercept(Chain chain) throws IOException {
       try {
           followUp = followUpRequest(response, streamAllocation.route());
         } catch (IOException e) {
           streamAllocation.release();
           throw e;
         }
      }
    private Request followUpRequest(Response userResponse, Route route) throws IOException {
       switch (responseCode) {
         case HTTP_PROXY_AUTH:
           //407
           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");
           }
          //加入身份验证的Request
           return client.proxyAuthenticator().authenticate(route, userResponse);
         case HTTP_UNAUTHORIZED: 
            //401
           return client.authenticator().authenticate(route, userResponse);
         case HTTP_PERM_REDIRECT:
         case HTTP_TEMP_REDIRECT:
             //307或者308,不是GET而且不是HEAD直接返回null
           if (!method.equals("GET") && !method.equals("HEAD")) {
             return null;
           }
         case HTTP_MULT_CHOICE:
         case HTTP_MOVED_PERM:
         case HTTP_MOVED_TEMP:
         case HTTP_SEE_OTHER:
           // 300、301、302、303
           if (!client.followRedirects()) return null;
         //获取Location
           String location = userResponse.header("Location");
           if (location == null) return null;
           HttpUrl url = userResponse.request().url().resolve(location);
           if (url == null) return null;
           if (!sameScheme && !client.followSslRedirects()) return null;
           return requestBuilder.url(url).build();
    
         case HTTP_CLIENT_TIMEOUT:
           //408是否请求超时
           //设置允许自动重试
           if (!client.retryOnConnectionFailure()) {
             return null;
           }
        //是不是响应408的重试结果
           if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
             return null;
           }
        //未响应Retry-After
           if (userResponse.priorResponse() != null
               && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
             return null;
           }
             //Retry-After是否大于0
           if (retryAfter(userResponse, 0) > 0) {
             return null;
           }
           return userResponse.request();
    
         case HTTP_UNAVAILABLE:
          //503
            //是不是响应503的重试结果
           if (userResponse.priorResponse() != null
               && userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
             return null;
           }
           //retryAfter是否等于0
           if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
             return userResponse.request();
           }
           return null;
         default:
           return null;
       }
     }
    
    响应码 说明 重定向条件
    407 代理需要授权,如付费代理,需要验证身份 通过proxyAuthenticator获取到Request,例: 添加 Proxy-Authorization 请求头
    401 服务器需要授权(不安全)
    300、301、302、303、307、308 重定向响应 307和308必须要为GET/HEAD请求再继续判断
    1、用户允许自动重定向(默认允许)
    2、能够获得Location响应头,并且值为有效url
    3、如果重定向需要http和https间切换,需要允许(默认允许)
    408 请求超时 1、用户允许自动重试
    2、本次请求的结果不是响应408的重试结果
    3、服务器未响应Retry-After(稍后重试)或者响应Retry-After:0
    503 服务不可用 1、本次请求的结果不是响应503的重试结果
    2、服务器明确响应Retry-After:0,立即重试

    二、桥接拦截器
    补全请求头

    请求头 说明
    Content-Type 请求体类型,如:application/x-www-form-urlencoded
    Content-Length/Transfer-Encoding 请求体解析方式
    Host 请求的主机站点
    Connection:Keep-Alive 默认保持长连接
    Accept-Encoding:gzip 接收机响应体使用gzip压缩
    Cookie Cookie身体识别
    User-Agent 用户信息,如操作系统,浏览器等

    得到响应:

    • 读取Set-Cookie响应头并调用接口告知用户,在下次请求则会读取对应的数据设置进入请求头,默认CookieJar无法实现
    • 响应头Content-Encoding为gzip,使用GzipSource包装便于解析

    三、缓存拦截器
    拦截器通过CacheStrategy判断使用缓存或发起网络请求。此对象中的networkRequest与cacheResponse分别代表需要发起请求或者直接使用缓存

    networkRequest cacheResponse 说明
    NULL Not NULL 直接使用缓存
    Not NULL NULL 向服务器发送请求
    NULL NULL 返回504
    Not NULL Not NULL 发起请求,若得到304无修改,更新缓存响应并返回
      @Override public Response intercept(Chain chain) throws IOException {
         //1、通过url的md5数据,从文件缓存查找(get请求才有缓存)
        Response cacheCandidate = cache != null
            ? cache.get(chain.request())
            : null;
    
        long now = System.currentTimeMillis();
        //缓存策略:根据各种条件组成请求头
        CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
         //网络请求的Request
        Request networkRequest = strategy.networkRequest;
        //缓存的Response 
        Response cacheResponse = strategy.cacheResponse;
    
        if (cache != null) {
          cache.trackResponse(strategy);
        }
    
        if (cacheCandidate != null && cacheResponse == null) {
          closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
        }
          //直接返回504
        if (networkRequest == null && cacheResponse == null) {
          return new Response.Builder()
              .request(chain.request())
              .protocol(Protocol.HTTP_1_1)
              .code(504)
              .message("Unsatisfiable Request (only-if-cached)")
              .body(Util.EMPTY_RESPONSE)
              .sentRequestAtMillis(-1L)
              .receivedResponseAtMillis(System.currentTimeMillis())
              .build();
        }
    
        // 没有网络,直接使用缓存
        if (networkRequest == null) {
          return cacheResponse.newBuilder()
              .cacheResponse(stripBody(cacheResponse))
              .build();
        }
    
        Response networkResponse = null;
        try {
          networkResponse = chain.proceed(networkRequest);
        } finally {
          if (networkResponse == null && cacheCandidate != null) {
            closeQuietly(cacheCandidate.body());
          }
        }
        if (cacheResponse != null) {
        //networkRequest和cacheResponse 都不为空
            //如果networkResponse等于304则更新缓存并返回
          if (networkResponse.code() == HTTP_NOT_MODIFIED) {
            Response response = cacheResponse.newBuilder()
                .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                .cacheResponse(stripBody(cacheResponse))
                .networkResponse(stripBody(networkResponse))
                .build();
            networkResponse.body().close();
    
            cache.trackConditionalCacheHit();
            cache.update(cacheResponse, response);
            return response;
          } else {
            closeQuietly(cacheResponse.body());
          }
        }
    
        Response response = networkResponse.newBuilder()
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
    
        if (cache != null) {
          if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
            // Offer this request to the cache.
            CacheRequest cacheRequest = cache.put(response);
            return cacheWritingResponse(cacheRequest, response);
          }
    
        return response;
      }
    
       private CacheStrategy getCandidate() {
          // 1、没有缓存,进行网络请求
          if (cacheResponse == null) {
            return new CacheStrategy(request, null);
          }
    
          // 2、Https请求,但是没有握手信息,进行网络请求
          if (request.isHttps() && cacheResponse.handshake() == null) {
            return new CacheStrategy(request, null);
          }
    
          // 3、主要通过响应码以及头部缓存控制字段判断响应能不能缓存,不能缓存就进行网络请求
          if (!isCacheable(cacheResponse, request)) {
            return new CacheStrategy(request, null);
          }
    
          CacheControl requestCaching = request.cacheControl();
          //4、如果请求包含cacheControl:no-cache,需要与服务器验证缓存有效性
          if (requestCaching.noCache() || hasConditions(request)) {
            return new CacheStrategy(request, null);
          }
          //5、如果缓存响应中Cache-Control:immutable,响应内容一直不会改变,可以使用缓存
          CacheControl responseCaching = cacheResponse.cacheControl();
          //6、根据缓存响应的控制缓存的响应头,判断是否允许使用缓存
          //6.1、获得缓存响应从创建到现在的时间
          long ageMillis = cacheResponseAge();
          //6.2获取这个响应有效缓存的时长
          long freshMillis = computeFreshnessLifetime();
    
          if (requestCaching.maxAgeSeconds() != -1) {
              //如果请求指定了max-age表示指定了资源缓存的最大时间
            freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
          }
    
          long minFreshMillis = 0;
          //6.3请求包含Cache-Control:min-fresh表示用户认为这个缓存有效的时长,假设本身缓存的新鲜度为:100ms,而缓存的最小新鲜度为10ms,那么缓存真正有效时间为90ms
          if (requestCaching.minFreshSeconds() != -1) {
            minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
          }
    
          long maxStaleMillis = 0;
          //6.4判断缓存的响应包含Cache-Control:must-revalidate(不可用过期资源),获得用户请求头包含Cache-Control:max-stal=[秒]缓存过期后仍有效的时长
          if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
            maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
          }
          //6.5判断换粗是否有效
          if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
            Response.Builder builder = cacheResponse.newBuilder();
            if (ageMillis + minFreshMillis >= freshMillis) {
              builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
            }
            long oneDayMillis = 24 * 60 * 60 * 1000L;
            if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
              builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
            }
            return new CacheStrategy(null, builder.build());
          }
         //7、缓存过期处理
          String conditionName;
          String conditionValue;
          if (etag != null) {
            conditionName = "If-None-Match";
            conditionValue = etag;
          } else if (lastModified != null) {
            conditionName = "If-Modified-Since";
            conditionValue = lastModifiedString;
          } else if (servedDate != null) {
            conditionName = "If-Modified-Since";
            conditionValue = servedDateString;
          } else {
            return new CacheStrategy(request, null); // No condition! Make a regular request.
          }
    
          Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
          Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
    
          Request conditionalRequest = request.newBuilder()
              .headers(conditionalRequestHeaders.build())
              .build();
          return new CacheStrategy(conditionalRequest, cacheResponse);
        }
    

    networkReuqest存在则优先发起网络请求,否则使用cacheResponse缓存,若都为NULL则请求失败

    image.png

    四、连接拦截器

    image.png
      void put(RealConnection connection) {
        assert (Thread.holdsLock(this));
        if (!cleanupRunning) {
          cleanupRunning = true;
          executor.execute(cleanupRunnable);
        }
        connections.add(connection);
      }
      private final Runnable cleanupRunnable = new Runnable() {
        @Override public void run() {
          while (true) {
            long waitNanos = cleanup(System.nanoTime());
            if (waitNanos == -1) return;
            if (waitNanos > 0) {
              long waitMillis = waitNanos / 1000000L;
              waitNanos -= (waitMillis * 1000000L);
              synchronized (ConnectionPool.this) {
                try {
                  ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                } catch (InterruptedException ignored) {
                }
              }
            }
          }
        }
      };
      long cleanup(long now) {
        int inUseConnectionCount = 0;
        int idleConnectionCount = 0;
        RealConnection longestIdleConnection = null;
        long longestIdleDurationNs = Long.MIN_VALUE;
    
        // Find either a connection to evict, or the time that the next eviction is due.
        synchronized (this) {
          for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
            RealConnection connection = i.next();
    
            // If the connection is in use, keep searching.
            if (pruneAndGetAllocationCount(connection, now) > 0) {
              inUseConnectionCount++;
              continue;
            }
    
            idleConnectionCount++;
    
            // If the connection is ready to be evicted, we're done.
            long idleDurationNs = now - connection.idleAtNanos;
            if (idleDurationNs > longestIdleDurationNs) {
              longestIdleDurationNs = idleDurationNs;
              longestIdleConnection = connection;
            }
          }
    
          if (longestIdleDurationNs >= this.keepAliveDurationNs
              || idleConnectionCount > this.maxIdleConnections) {
            // We've found a connection to evict. Remove it from the list, then close it below (outside
            // of the synchronized block).
            connections.remove(longestIdleConnection);
          } else if (idleConnectionCount > 0) {
            // A connection will be ready to evict soon.
            return keepAliveDurationNs - longestIdleDurationNs;
          } else if (inUseConnectionCount > 0) {
            // All connections are in use. It'll be at least the keep alive duration 'til we run again.
            return keepAliveDurationNs;
          } else {
            // No connections, idle or in use.
            cleanupRunning = false;
            return -1;
          }
        }
    
        closeQuietly(longestIdleConnection.socket());
    
        // Cleanup again immediately.
        return 0;
      }
    
    • 新建连接,放入连接池
    • 启动清理任务,开始定时清理任务
    • 清除无用连接

    五、请求服务拦截器
    一般出现上传大容量请求体或者需要验证,代表需要先询问服务器是否接受发送请求体数据
    Okhttp的做法

    • 如果服务器允许则返回100,客户端继续发送请求体
    • 如果服务器不允许则直接返回给用户
    • 同时服务器也可能会忽略此请求头,一直无法读取应答,此时抛出超时异常

    相关文章

      网友评论

          本文标题:Android网络基础知识

          本文链接:https://www.haomeiwen.com/subject/iztvaltx.html