okhttp之Connection

2022-12-19 13:43:36 浏览数 (1)

转载请以链接形式标明出处: 本文出自:103style的博客

base on 3.12.0


目录

  • 简介
  • RealConnection 的成员变量
  • RealConnection 的构造函数
  • RealConnection 的相关方法
  • 小结

简介

Connection 是一个定义了四个方法的接口类。定义了 获取 路由,socket,连接协议,以及HTTPS的TLS握手记录

代码语言:javascript复制
public interface Connection {
  Route route();//路由
  Socket socket();//套接字
  @Nullable Handshake handshake();//https协议的TLS握手, 其他协议返回null.
  Protocol protocol();//连接的协议
}

Connection 的唯一实现类为 RealConnection。 当我们进行网络请求的时候, okhttp 会拿到 一个 RealConnection 来进行对应的网络连接操作。

下面我们来看下 RealConnection


RealConnection 的成员变量

静态常量:

代码语言:javascript复制
/**
 * 解决Android 7.0 的一个报错
 * https://github.com/square/okhttp/issues/3245
 */
private static final String NPE_THROW_WITH_NULL = "throw with null exception";
/**
 * 隧道连接 connectTunnel(...) 的最大尝试次数 
 */
private static final int MAX_TUNNEL_ATTEMPTS = 21;

构造方法传入的 连接池 和 路由:

代码语言:javascript复制
private final ConnectionPool connectionPool; //连接池
private final Route route; //路由

仅在首次调用 connect(...) 初始化的变量:

代码语言:javascript复制
private Socket rawSocket;//低级的 TCP socket
private Socket socket;// socket
private Handshake handshake;//HTTPS的TLS握手记录
private Protocol protocol;//连接协议
private Http2Connection http2Connection;// http2的连接
private BufferedSource source;//类似inputstream的输入流
private BufferedSink sink;//类似outputstream的输出流

由connectionPool管理 用来跟踪连接状态的变量:

代码语言:javascript复制
/**
 * 为true  则无法在此连接上创建新的流
 */
public boolean noNewStreams;
public int successCount; //连接成功次数
public int allocationLimit = 1; //此连接可以承载的并发流的最大数量。
/**
 * 此连接承载的当前流
 */
public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
/**
 * 达到零时的纳秒时间戳
 */
public long idleAtNanos = Long.MAX_VALUE;

RealConnection 的构造函数

构造函数只是StreamAllocation构建连接时调用,传入了当前的 连接池对应请求地址的路由

代码语言:javascript复制
public RealConnection(ConnectionPool connectionPool, Route route) {
    this.connectionPool = connectionPool;
    this.route = route;
}

RealConnection 的相关public方法

connect(...) 根据不同的协议建立不同的连接,首先区分 https 和 其他 连接通过connectTunnel(...)connectSocket(...) 做不同的准备 , 然后在establishProtocol(...)中通过判断是否是https 做不同的连接。

代码语言:javascript复制
public void connect(...) {
    //一个RealConnection只能掉一次此方法
    if (protocol != null) throw new IllegalStateException("already connected");
    ...
    while (true) {
        try {
            if (route.requiresTunnel()) {
                //https 协议 的准备工作
                connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
               ...
            } else {
                //其他通过socket 的准备工作
                connectSocket(connectTimeout, readTimeout, call, eventListener);
            }
            //确定协议开始连接
            establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
            eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
            break;
        } catch (IOException e) {
            //处理异常之后的释放以及回调操作
            //置空 protocol 等变量
        }
    }
    ...
}
private void establishProtocol(...) throws IOException {
    if (route.address().sslSocketFactory() == null) {
        //非https 连接
        if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
            ...
            //通过Http2Connection.Builder(true).build().start() 建立http2连接
            startHttp2(pingIntervalMillis);
            return;
        }
        socket = rawSocket;
        protocol = Protocol.HTTP_1_1;
        return;
    }
    //https 建立安全连接
    eventListener.secureConnectStart(call);
    connectTls(connectionSpecSelector);
    eventListener.secureConnectEnd(call, handshake);

    if (protocol == Protocol.HTTP_2) {
        //通过Http2Connection.Builder(true).build().start() 建立http2连接
        startHttp2(pingIntervalMillis);
    }
}

isEligible(...) 如果主机完全一致满足条件。 然后就只有满足对应条件的 HTTP/2连接 才合格。

代码语言:javascript复制
public boolean isEligible(Address address, @Nullable Route route) {
    // 如果此连接不接受新的流 则不合格
    if (allocations.size() >= allocationLimit || noNewStreams) return false;
    // 如果地址的非主机字段不重叠,则不合格
    if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
    // 如果主机完全匹配,此连接可以携带地址。
    if (address.url().host().equals(this.route().address().url().host())) {
        return true; // This connection is a perfect match.
    }
    // 1.必须是HTTP/2连接.
    if (http2Connection == null) return false;
    // 2.路由必须共享一个IP地址
    if (route == null) return false;
    if (route.proxy().type() != Proxy.Type.DIRECT) return false;
    if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
    if (!this.route.socketAddress().equals(route.socketAddress())) return false;
    // 3.此连接的服务器证书必须包含新主机。
    if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
    if (!supportsUrl(address.url())) return false;
    // 4.证书固定必须与主机匹配。
    try {
        address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
    } catch (SSLPeerUnverifiedException e) {
        return false;
    }
    return true;
}

supportsUrl(HttpUrl url) 需要与此连接的端口一致,主机名一致或者证书匹配。

代码语言:javascript复制
public boolean supportsUrl(HttpUrl url) {
    if (url.port() != route.address().url().port()) {
        return false;//端口不一致
    }
    if (!url.host().equals(route.address().url().host())) {
        // 主机不匹配  但是证书匹配也可以
        return handshake != null && OkHostnameVerifier.INSTANCE.verify(
                url.host(), (X509Certificate) handshake.peerCertificates().get(0));
    }
    return true;
}

newCodec(...) 根据是否是HTTP_2创建不同的 HttpCodec.

代码语言:javascript复制
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
                          StreamAllocation streamAllocation) throws SocketException {
    if (http2Connection != null) {
        return new Http2Codec(client, chain, streamAllocation, http2Connection);
    } else {
        socket.setSoTimeout(chain.readTimeoutMillis());
        source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
        sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
        return new Http1Codec(client, streamAllocation, source, sink);
    }
}

cancel() 关闭原始套接字,以免最终无法执行同步IO。

代码语言:javascript复制
public void cancel() {
    closeQuietly(rawSocket);
}
public static void closeQuietly(Socket socket) {
    if (socket != null) {
        try {
            socket.close();
        ...
        } catch (Exception ignored) {
        }
    }
}

isHealthy(boolean doExtensiveChecks) 此连接是否准备好承载新的流, 主要就是判断连接是否已经中断或关闭

代码语言:javascript复制
public boolean isHealthy(boolean doExtensiveChecks) {
    if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
        return false;
    }
    if (http2Connection != null) {
        return !http2Connection.isShutdown();
    }
    if (doExtensiveChecks) {
        try {
            int readTimeout = socket.getSoTimeout();
            try {
                socket.setSoTimeout(1);
                if (source.exhausted()) {
                    return false;
                }
                return true;
            } finally {
                socket.setSoTimeout(readTimeout);
            }
        } catch (SocketTimeoutException ignored) {
        } catch (IOException e) {
            return false;
        }
    }
    return true;
}

小结

Connection 是 用来 获取 路由,socket,连接协议,以及HTTPS的TLS握手记录, 以及根据 是否是 HTTPS 通过不同的方式建立连接。以及提供了复用流时的一些方法。


以上

0 人点赞