okhttp之连接

okhttp之拦截器中,我们知道建立连接是在ConnectInterceptor中完成的,今天具体分析一下实现细节
在默认情况下,是没有现成的连接使用的,所以从新建开始看

一、建立连接
    val newConnection = RealConnection(connectionPool, route)
    newConnection.connect(
        connectTimeout,
        readTimeout,
        writeTimeout,
        pingIntervalMillis,
        connectionRetryEnabled,
        call,
        eventListener
    )

新建了RealConnection对象,调用connect方法

fun connect() {
    while (true) {
      try {
        // 1 HTTPS
        if (route.requiresTunnel()) {
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)
        } else {
          // 2 HTTP
          connectSocket(connectTimeout, readTimeout, call, eventListener)
        }
        establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)
        break
      }
    }
  }

两种情况分开分析

1、HTTPS连接建立

进入connectTunnel

private fun connectTunnel() {
    //构建不带敏感字段的请求,用来打通隧道
    var tunnelRequest: Request = createTunnelRequest()
    val url = tunnelRequest.url
    for (i in 0 until MAX_TUNNEL_ATTEMPTS) {
      connectSocket(connectTimeout, readTimeout, call, eventListener)
      tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url)
          ?: break 
    }
  }

创建建立隧道用的request,然后调用connectSocket:

private fun connectSocket() {
    //创建套接字
    val rawSocket = when (proxy.type()) {
      Proxy.Type.DIRECT, Proxy.Type.HTTP -> address.socketFactory.createSocket()!!
      else -> Socket(proxy)
    }
    //连接套接字
    try {
      Platform.get().connectSocket(rawSocket, route.socketAddress, connectTimeout)
    }
    //创建用来与服务器通信的通道,okio实现
    source = rawSocket.source().buffer()
    sink = rawSocket.sink().buffer()
  }

可以看到是新建了一个套接字,然后调用connect方法。这里开始创建了TCP的连接,接下来就是创建SSL隧道(因为SSL是应用层连接,我们也可以从这里看出来SSL是架设在TCP之上的,而这种分层的协议也很方便添加应用层协议上去。),开始进入createTunnel方法:

private fun createTunnel(): Request? {
    var nextRequest = tunnelRequest
    //建立SSL协议的字段
    val requestLine = "CONNECT ${url.toHostHeader(includeDefaultPort = true)} HTTP/1.1"
    while (true) {
      val source = this.source!!
      val sink = this.sink!!
      val tunnelCodec = Http1ExchangeCodec(null, this, source, sink)
      tunnelCodec.writeRequest(nextRequest.headers, requestLine)
      tunnelCodec.finishRequest()
      val response = tunnelCodec.readResponseHeaders(false)!!
          .request(nextRequest)
          .build()
      tunnelCodec.skipConnectBody(response)
      when (response.code) {
        //成功
        HTTP_OK -> {
          return null
        }
        //需要授权
        HTTP_PROXY_AUTH -> {
          if ("close".equals(response.header("Connection"), ignoreCase = true)) {
            return nextRequest
          }
        }
      }
    }
  }

如果需要认证身份的话,需要重复发请求。返回去看剩下的方法establishProtocol:

private fun establishProtocol() {
    //sslSocketFactory在HTTP协议的时候为空,HTTPS不为空
    if (route.address.sslSocketFactory == null) {
      //如果有h2_prior_knowledge字段,调用启动HTTP2
      if (Protocol.H2_PRIOR_KNOWLEDGE in route.address.protocols) {
        socket = rawSocket
        protocol = Protocol.H2_PRIOR_KNOWLEDGE
        startHttp2(pingIntervalMillis)
        return
      }
      //HTTP1.1
      protocol = Protocol.HTTP_1_1
      return
    }
    //TLS握手
    connectTls(connectionSpecSelector)
    if (protocol === Protocol.HTTP_2) {
      startHttp2(pingIntervalMillis)
    }
  }

假如支持HTTP2,启动HTTP2,否则是HTTP1.1,然后是TLS握手,HTTP2的部分我们不做展开,这里看一下TLS握手:

private fun connectTls(connectionSpecSelector: ConnectionSpecSelector) {
    val address = route.address
    val sslSocketFactory = address.sslSocketFactory
    var success = false
    var sslSocket: SSLSocket? = null
    try {
      //创建SSL套接字
      sslSocket = sslSocketFactory!!.createSocket(
          rawSocket, address.url.host, address.url.port, true /* autoClose */) as SSLSocket
      //密码对以及扩展等的配置
      val connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket)
      if (connectionSpec.supportsTlsExtensions) {
        Platform.get().configureTlsExtensions(sslSocket, address.url.host, address.protocols)
      }
      //开始握手
      sslSocket.startHandshake()
      // block for session establishment
      val sslSocketSession = sslSocket.session
      val unverifiedHandshake = sslSocketSession.handshake()
      //验证证书
      if (!address.hostnameVerifier!!.verify(address.url.host, sslSocketSession)) {
        val peerCertificates = unverifiedHandshake.peerCertificates
        if (peerCertificates.isNotEmpty()) {
          throw SSLPeerUnverifiedException()
        } else {
          throw SSLPeerUnverifiedException()
        }
      }
      //握手成功,保存协议
      val maybeProtocol = if (connectionSpec.supportsTlsExtensions) {
        Platform.get().getSelectedProtocol(sslSocket)
      } else {
        null
      }
      socket = sslSocket
      //更新数据通道
      source = sslSocket.source().buffer()
      sink = sslSocket.sink().buffer()
    }
  }

这里实现了TLS的证书验证过程,握手成功后输入和输出通道变成SSL套接字的输入以及输出,也代表我们交流的对象变成了SslSocket,完成了HTTPS的连接建立
然后返回去看一下HTTP的连接建立case

2、HTTP连接建立

调用到了connectSocket方法,这个方法我们已经分析过了,就是socket的连接,实现了TCP的三次握手,只是具体握手细节是系统实现的,我们只需要调用connect就行,最后是establishProtocol,如果没有HTTP2的话,只是对protocal的赋值。

二、总结

除了应用层本身的协议SSL之外,应用层对于连接的建立仅限于对系统接口的调用。
注:由于目前我对HTTP2不甚了解,贸然读源码没有什么实际的意义,所以这里没有做展开

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值