第四章:Okhttp 连接管理与复用

136 阅读4分钟

4.1 连接管理核心组件

4.1.1 连接管理类关系图

deepseek_mermaid_20250717_e1fd6c.png

4.2 TCP连接建立过程

4.2.1 连接建立流程图

deepseek_mermaid_20250717_3bd565.png

4.2.2 连接建立源码分析

// 源码路径: okhttp3/internal/connection/RealConnection.java
public void connect(int connectTimeout, int readTimeout, ...) {
    // 1. 创建原始Socket
    rawSocket = proxy.type() == Proxy.Type.DIRECT 
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    // 2. 设置超时
    rawSocket.setSoTimeout(readTimeout);
    rawSocket.connect(new InetSocketAddress(route.socketAddress(), connectTimeout));

    // 3. 建立TLS隧道(HTTPS场景)
    if (route.address().sslSocketFactory() != null) {
        upgradeToTls();
    }

    // 4. 协议协商(HTTP/1.1或HTTP/2)
    if (protocol == Protocol.HTTP_2) {
        startHttp2Connection();
    }
}

private void upgradeToTls() {
    // 1. 创建SSLSocket
    sslSocket = (SSLSocket) address.sslSocketFactory()
        .createSocket(rawSocket, address.url().host(), address.url().port(), true);
    
    // 2. 配置TLS参数
    ConnectionSpec connectionSpec = route.connectionSpec();
    connectionSpec.apply(sslSocket, isTlsFallback);
    
    // 3. 开始TLS握手
    sslSocket.startHandshake();
    
    // 4. 验证主机名
    if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {
        throw new SSLPeerUnverifiedException("Hostname verification failed");
    }
    
    // 5. 获取握手信息
    handshake = Handshake.get(sslSocket.getSession());
}

4.3 连接复用机制

4.3.1 连接复用判断逻辑

// 源码路径: okhttp3/internal/connection/RealConnection.java
public boolean isEligible(Address address, @Nullable Route route) {
    // 1. 连接已达并发流限制(HTTP/2)
    if (allocations.size() >= allocationLimit || noNewStreams) {
        return false;
    }
    
    // 2. 非HTTP/2需完全匹配Address
    if (!this.route.address().equals(address)) {
        return false;
    }
    
    // 3. 路由必须匹配
    if (route != null && !this.route.equals(route)) {
        return false;
    }
    
    // 4. HTTP/2需验证主机名证书
    if (http2Connection != null) {
        return address.hostnameVerifier().verify(address.url().host(), handshake().peerPrincipal());
    }
    
    return true; // 满足复用条件
}

4.3.2 连接获取流程

deepseek_mermaid_20250717_0b6e6f.png

4.4 连接池管理

4.4.1 连接池数据结构

// 源码路径: okhttp3/internal/connection/ConnectionPool.java
public final class ConnectionPool {
    // 空闲连接队列(双向链表)
    private final Deque<RealConnection> connections = new ArrayDeque<>();
    
    // 清理任务
    private final Runnable cleanupRunnable = () -> {
        while (true) {
            // 执行清理
        }
    };
}

4.4.2 连接回收策略

// 源码路径: okhttp3/internal/connection/ConnectionPool.java
void put(RealConnection connection) {
    // 1. 检查是否正在清理
    if (cleanupRunning) {
        // 启动清理线程
    }
    
    // 2. 加入连接池
    connections.add(connection);
}

synchronized void recycle(RealConnection connection) {
    // 1. 从活跃连接移除
    connection.allocations.remove(connection);
    
    // 2. 加入空闲队列
    connections.add(connection);
}

4.5 连接池清理机制

4.5.1 清理算法流程图

deepseek_mermaid_20250717_f208e8.png

4.5.2 清理源码实现

// 源码路径: okhttp3/internal/connection/ConnectionPool.java
long cleanup(long now) {
    int idleCount = 0;
    RealConnection longestIdle = null;
    long longestIdleDuration = Long.MIN_VALUE;

    // 1. 统计空闲连接
    for (RealConnection connection : connections) {
        if (connection.isIdle()) {
            idleCount++;
            long idleDuration = now - connection.idleAtNanos;
            if (idleDuration > longestIdleDuration) {
                longestIdle = connection;
                longestIdleDuration = idleDuration;
            }
        }
    }

    // 2. 执行清理决策
    if (longestIdleDuration >= keepAliveDurationNs || idleCount > maxIdleConnections) {
        connections.remove(longestIdle);
        closeQuietly(longestIdle.socket()); // 关闭连接
        return 0; // 立即再次检查
    }

    // 3. 计算下次清理时间
    return keepAliveDurationNs - longestIdleDuration;
}

4.6 HTTP/2多路复用

4.6.1 HTTP/2连接建立

deepseek_mermaid_20250717_9a5956.png

4.6.2 HTTP/2连接复用

// 源码路径: okhttp3/internal/http2/Http2Connection.java
public Http2Stream newStream(
    List<Header> requestHeaders, 
    boolean out, 
    boolean in) throws IOException {
    
    // 1. 创建新流
    Http2Stream stream;
    int associatedStreamId;
    
    synchronized (writer) {
        synchronized (this) {
            // 2. 分配流ID
            int nextStreamId = nextStreamId;
            if (nextStreamId > Integer.MAX_VALUE / 2) {
                shutdown(REFUSED_STREAM);
            }
            this.nextStreamId = nextStreamId + 2;
            stream = new Http2Stream(nextStreamId, this, out, in, requestHeaders);
        }
        
        // 3. 发送HEADERS帧
        writer.headers(stream.getId(), requestHeaders);
    }
    
    return stream;
}

4.7 连接生命周期监控

4.7.1 事件监听接口

// 源码路径: okhttp3/EventListener.java
public abstract class EventListener {
    // 连接开始
    public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {}
    
    // TLS握手开始
    public void secureConnectStart(Call call) {}
    
    // TLS握手结束
    public void secureConnectEnd(Call call, @Nullable Handshake handshake) {}
    
    // 连接建立完成
    public void connectEnd(Call call, InetSocketAddress inetSocketAddress, 
                          Proxy proxy, @Nullable Protocol protocol) {}
    
    // 连接被复用
    public void connectionAcquired(Call call, Connection connection) {}
    
    // 连接释放
    public void connectionReleased(Call call, Connection connection) {}
}

4.7.2 连接监控实现

OkHttpClient client = new OkHttpClient.Builder()
    .eventListener(new EventListener() {
        @Override
        public void connectionAcquired(Call call, Connection connection) {
            log("Connection acquired: " + connection.hashCode());
        }
        
        @Override
        public void connectionReleased(Call call, Connection connection) {
            log("Connection released: " + connection.hashCode());
        }
        
        @Override
        public void connectEnd(Call call, InetSocketAddress inetSocketAddress, 
                              Proxy proxy, Protocol protocol) {
            log("Connected via: " + protocol);
        }
    })
    .build();

4.8 连接优化策略

4.8.1 DNS优化

// 自定义DNS解析器
public class CustomDns implements Dns {
    private static final long CACHE_DURATION = 5 * 60 * 1000; // 5分钟缓存
    private final Map<String, DnsCacheEntry> cache = new ConcurrentHashMap<>();
    
    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
        // 1. 检查缓存
        DnsCacheEntry cached = cache.get(hostname);
        if (cached != null && !cached.isExpired()) {
            return cached.addresses;
        }
        
        // 2. 系统DNS解析
        List<InetAddress> addresses = Dns.SYSTEM.lookup(hostname);
        
        // 3. 缓存结果
        cache.put(hostname, new DnsCacheEntry(addresses));
        
        return addresses;
    }
    
    private static class DnsCacheEntry {
        final List<InetAddress> addresses;
        final long expireTime;
        
        DnsCacheEntry(List<InetAddress> addresses) {
            this.addresses = addresses;
            this.expireTime = System.currentTimeMillis() + CACHE_DURATION;
        }
        
        boolean isExpired() {
            return System.currentTimeMillis() > expireTime;
        }
    }
}

4.8.2 HTTP/2参数优化

OkHttpClient client = new OkHttpClient.Builder()
    .connectionSpecs(Arrays.asList(
        new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
            .http2Settings(new Settings.Builder()
                .set(Settings.INITIAL_WINDOW_SIZE, 16777216) // 16MB窗口
                .set(Settings.MAX_CONCURRENT_STREAMS, 100)   // 最大并发流
                .build())
            .build()))
    .build();

4.9 连接问题排查

4.9.1 常见连接问题

  1. 连接泄漏

    • 现象:应用运行一段时间后无法发起新连接
    • 原因:未关闭ResponseBody
    • 解决:确保使用try-with-resources或手动关闭
  2. TLS握手失败

    • 现象:SSLHandshakeException
    • 原因:协议/加密套件不匹配
    • 解决:调整ConnectionSpec配置
  3. 连接超时

    • 现象:ConnectTimeoutException
    • 原因:网络问题或服务器不可达
    • 解决:增加连接超时时间,检查网络

4.9.2 连接诊断工具

// 启用详细日志
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.HEADERS);

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(logging)
    .build();

// 输出连接池状态
ConnectionPool pool = client.connectionPool();
System.out.println("空闲连接数: " + pool.idleConnectionCount());
System.out.println("总连接数: " + pool.connectionCount());

本章小结

  1. 连接建立过程

    • DNS解析获取IP地址
    • TCP三次握手建立连接
    • TLS握手加密通信(HTTPS)
    • 协议协商(HTTP/1.1或HTTP/2)
  2. 连接复用机制

    • 相同Address可复用连接
    • HTTP/2支持多路复用
    • 空闲连接最大5个,存活5分钟
  3. 连接池管理

    • 使用双端队列存储连接
    • 定时清理空闲连接
    • 自动回收释放的连接
  4. HTTP/2特性

    • 二进制分帧传输
    • 多路复用减少连接数
    • 头部压缩减少开销
    • 服务器推送提升性能
  5. 性能优化策略

    • DNS缓存减少解析时间
    • 调整HTTP/2窗口大小
    • 监控连接生命周期
    • 合理配置连接池参数

在下一章中,我们将深入分析HTTP/2多路复用实现,包括二进制分帧、流控制、优先级调度等核心机制。