在《 Okhttp之RealConnection建立链接简单分析》一文中简单的分析了RealConnection的connect方法的作用:打开一个TCP链接或者打开一个隧道链接,在打开tcp链接之后就调用establishProtocol,本篇主要是对此方法来进行分析:

   */
  private void establishProtocol(ConnectionSpecSelector connectionSpecSelector) throws IOException {
    //不是https请求即普通的http请求,那么协议定义为http/1.1
    if (route.address().sslSocketFactory() == null) {
      protocol = Protocol.HTTP_1_1;
      socket = rawSocket;
      return;
    }
    //https请求
    //使用SSLSocket,同时会赋值protocol和socket,主要用来判断服务器是否支持http2.0
    connectTls(connectionSpecSelector);

    //对http2的支持
    if (protocol == Protocol.HTTP_2) {
      socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
      http2Connection = new Http2Connection.Builder(true)
          .socket(socket, route.address().url().host(), source, sink)
          .listener(this)
          .build();
      http2Connection.start();
    }
  }

大体上看该方法主要做了如下工作:

1、如果是http请求的话,就将协议protocol定义为http/1.1

2、如果是https的请求的话,就调用connectTls创建一个SSLSocket,然后通过一些列的方法调用(比如三次握手啦之类的),初始化protocol:关键代码如下:

   sslSocket = (SSLSocket) sslSocketFactory.createSocket(
          rawSocket, address.url().host(), address.url().port(), true /* autoClose */);

      // Configure the socket's ciphers, TLS versions, and extensions.
      ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
      if (connectionSpec.supportsTlsExtensions()) {
        Platform.get().configureTlsExtensions(
            sslSocket, address.url().host(), address.protocols());
      }

      // Force handshake. This can throw!
      sslSocket.startHandshake();
      Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());
      //////////////分割线//////////////////////
 String maybeProtocol = connectionSpec.supportsTlsExtensions()
      ? Platform.get().getSelectedProtocol(sslSocket)
      : null;
  //socket为sslSocket
  socket = sslSocket;
  //将输入输出流交给Okio
  source = Okio.buffer(Okio.source(socket));
  sink = Okio.buffer(Okio.sink(socket));

  protocol = maybeProtocol != null
      ? Protocol.get(maybeProtocol)
      : Protocol.HTTP_1_1; 

为什么是https的话就开始判断是否是http2呢?其实是因为http2就目前来说在实际使用中,只用于https协议场景下,通过握手阶段extension字段协商而来,当然HTTP2.0其实可以支持非HTTPS的(参考资料),但是现在主流的浏览器像chrome,firefox表示还是只支持基于 TLS 部署的HTTP2.0协议,所以要想升级成HTTP2.0还是先升级HTTPS为好(参考资料)。而上面的代码分割线以上的部门就是做了这这些协商工作。

分割线一下的部分就是对协议的初始化了。其实,如果打印Okhttp的response对象的toString(),会发现其协议版本也在里面体现出来:Response{protocol=http/1.1, code=200, message=OK, url=http://www.xxx.com/}

3、最后就进入了对http2的处理。

总的来说establishProtocol方法的作用查询服务器对http版本的支持情况,如果是http2协议的服务器,就交给http2Connection 来处理请求。

当协商完成后客户端和服务端就可以发送所谓的链接序言了,且该序列后跟着一个设置帧(SETTING),其可为空帧,什么是链接序言呢?用字符串表示的话就是:字符串PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n)

那么okhttp是怎么发送序言的呢?追踪下Http2Connection的start方法最终进入:

  void start(boolean sendConnectionPreface) throws IOException {
    if (sendConnectionPreface) {
      //发送链接序言
      writer.connectionPreface();
      //发送setting帧
      writer.settings(okHttpSettings);
      //省略部分代码
    }
    //开启线程执行readerRunnable
    new Thread(readerRunnable).start(); // Not a daemon thread.
  }

上面的代码connectionPreface其实就是发送链接序言的:

 static final ByteString CONNECTION_PREFACE
      = ByteString.encodeUtf8("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");

  public synchronized void connectionPreface() throws IOException {
    //发送链接序言
    sink.write(CONNECTION_PREFACE.toByteArray());
    sink.flush();
  }

HTTP/2中基本的协议单位是帧。每个帧都有不同的类型和用途。 例如,报头(HEADERS)和数据(DATA)帧组成了基本的HTTP 请求和响应;其他帧例如 设置(SETTINGS)、窗口更新(WINDOW_UPDATE)和推送承诺(PUSH_PROMISE) 是用来实现HTTP/2的其他功能(摘自《http/2.0中文翻译》。

而Okhttp2对也是严格按照这些帧的种类来进行数据的读取和解析:具体在readerRunnable的execute方法:

     protected void execute() {
        //省略了部分代码,比如try cath
        reader.readConnectionPreface(this);
        //不断读取http2.0的数据帧
        while (reader.nextFrame(false, this)) {
        }

      }
    }

其核心逻辑也就是在Http2Reader的nextFrame中:

 public boolean nextFrame(boolean requireSettings, Handler handler) throws IOException {
    try {
      source.require(9); // Frame header size
    } catch (IOException e) {
      return false; // This might be a normal socket close.
    }
  //省略了部分代码

    switch (type) {
      case TYPE_DATA:
        readData(handler, length, flags, streamId);
        break;

      case TYPE_HEADERS:
        readHeaders(handler, length, flags, streamId);
        break;

      case TYPE_PRIORITY:
        readPriority(handler, length, flags, streamId);
        break;

      case TYPE_RST_STREAM:
        readRstStream(handler, length, flags, streamId);
        break;

      case TYPE_SETTINGS:
        readSettings(handler, length, flags, streamId);
        break;

      case TYPE_PUSH_PROMISE:
        readPushPromise(handler, length, flags, streamId);
        break;

      case TYPE_PING:
        readPing(handler, length, flags, streamId);
        break;

      case TYPE_GOAWAY:
        readGoAway(handler, length, flags, streamId);
        break;

      case TYPE_WINDOW_UPDATE:
        readWindowUpdate(handler, length, flags, streamId);
        break;

      default:
        // Implementations MUST discard frames that have unknown or unsupported types.
        source.skip(length);
    }
    return true;
  }

到此为止,本篇博文简单解析完毕,至于对每一帧怎么处理以及每个帧的含义可以参考《Http2.0中文解析》。在这里就不做大篇幅的说明了(因为某些具体的处理细节我还没高透彻)

Okhttp对http2的支持简单分析的更多相关文章

  1. Okhttp之连接池ConnectionPool简单分析(一)

    开篇声明:由于本篇博文用到的一些观点或者结论在之前的博文中都已经分析过,所以本篇博文直接拿来用,建议读此博文的Monkey们按照下面的顺序读一下博主以下博文,以便于对此篇博文的理解: <Okht ...

  2. Okhttp之RealConnection建立链接简单分析

    在之前的博客中我们知道Okhttp在发起链接请求先从链接池中获取连接,如果链接池中没有链接则创建新的链接RealConnection对象,然后执行其connet方法打开SOCKET链接(详见< ...

  3. Okhttp之CallServerInterceptor简单分析

    在Okhttp源码分析专栏的几篇博客分析了Okhttp几个拦截器的主要功能,还剩下最后一个拦截器CallServerInterceptor没有分析,本篇博客就简单分析下该拦截器的功能. 在Okhttp ...

  4. OkHttp之ConnectInterceptor简单分析

    在< Okhttp之CacheInterceptor简单分析 >这篇博客中简单的分析了下缓存拦截器的工作原理,通过此博客我们知道在执行完CacheInterceptor之后会执行下一个浏览 ...

  5. Okhttp之CacheInterceptor简单分析

    <OkHttp之BridgeInterceptor简单分析 >简单分析了BridgeInterceptor的工作原理,在Okhttp的拦截器链上BridgeInterceptor的下一个拦 ...

  6. OkHttp之BridgeInterceptor简单分析

    在< Okhttp源码简单解析(一) >这篇博客简单分析了Okhttp请求的执行流程,通过该篇博客我们知道OkHttp的核心网络请求中内置"拦截器"发挥了重大作用:本篇 ...

  7. http2 技术整理 nginx 搭建 http2 wireshark 抓包分析 server push 服务端推送

    使用 nginx 搭建一个 http2 的站点,准备所需: 1,域名 .com .net 均可(国内域名需要 icp 备案) 2,云主机一个,可以自由的安装配置软件的服务器 3,https 证书 ht ...

  8. CSipSimple 简单分析

    简介 CSipSimple是一款可以在android手机上使用的支持sip的网络电话软件,可以在上面设置使用callda网络电话.连接使用方式最好是使用wifi,或者3g这样上网速度快,打起电话来效果 ...

  9. 透过byte数组简单分析Java序列化、Kryo、ProtoBuf序列化

    序列化在高性能网络编程.分布式系统开发中是举足轻重的之前有用过Java序列化.ProtocolBuffer等,在这篇文章这里中简单分析序列化后的byte数组观察各种序列化的差异与性能,这里主要分析Ja ...

随机推荐

  1. DataSet 动态添加列

    public DataSet GetNewId(List<string> IdArr) { DataSet ds = new DataSet(); DataTable newtb = ne ...

  2. LINUX设备驱动模型之class

    转自 https://blog.csdn.net/qq_20678703/article/details/52754661 1.LINUX设备驱动模型中的bus.device.driver,.其中bu ...

  3. python ros topic demo

    发布者: #!/usr/bin/env python #coding=utf- import rospy from std_msgs.msg import String def talker():   ...

  4. python ros 回充调用demo

    #!/usr/bin/env python #coding=utf- import rospy from std_msgs.msg import String def talker(): pub = ...

  5. Codeforces 680D - Bear and Tower of Cubes

    680D - Bear and Tower of Cubes 思路:dfs+贪心,设剩余的体积为res,存在a,使得a3 ≤ res,每次取边长为a的立方体或者边长为a-1的立方体(这时体积上限变成a ...

  6. R—读取数据(导入csv,txt,excel文件)

    导入CSV.TXT文件 read.table函数:read.table函数以数据框的格式读入数据,所以适合读取混合模式的数据,但是要求每列的数据数据类型相同. read.table读取数据非常方便,通 ...

  7. English trip -- VC(情景课)10 B Around the house 在家里

    Vocablulary focus  核心词汇 cook play the guitar listen to music watch TV read magazines work in the gar ...

  8. Destroy the Colony CodeForces - 1111D (可逆背包,计数)

    大意:给定字符串$s$, 保证长度为偶数, 给定q个询问, 每次询问给定两个位置$x$,$y$, 可以任意交换字符, 要求所有字符$s[x],s[y]$在同一半边, 剩余所有同种字符在同一半边的方案数 ...

  9. 『OpenCV3』处理视频&摄像头

    在opencv中,摄像头和视频文件并没有很大不同,都是一个可以read的数据源,使用cv2.VideoCapture(path).read()可以获取(flag,当前帧),对于每一帧,使用图片处理函数 ...

  10. C++ vector 实现二维数组

    在STL中Vector这一容器,无论是在封装程度还是内存管理等方面都由于传统C++中的数组.本文主要是关于使用Vector初始化.遍历方面的内容.其他二维的思想也是类似的. 这里简单叙述一下C++ 构 ...