OKHttp 3 소스 코드 분석 - Connect Interceptor

주요 유형 은 ConnectInterceptor, StreamAllocation, RealConnection, ConnectionPool 를 포함한다.
우 리 는 먼저 분석 ConnectionPool 한 후에 분석 ConnectionInterceptor 했다.
연결 탱크ConnectionPool 역할 은 주로 재 활용 Http 연결 로 네트워크 연결 의 지연 을 피하 고 TCP 튜 닝 으로 인 한 대역 폭 이 너무 작은 문 제 를 피 하 는 것 이다.ConnectionPool 주로 두 가지 방법 이 있다.
  • get 방법 은 연결 을 얻 는 데 사용 된다.
  • put 방법 은 연결 을 추가 하 는 데 사용 된다.

  • get 방법
    public final class ConnectionPool {
        private final Deque<RealConnection> connections = new ArrayDeque<>();
    	@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
        	assert (Thread.holdsLock(this));
        	for (RealConnection connection : connections) {
          		if (connection.isEligible(address, route)) {
            		streamAllocation.acquire(connection, true);
            		return connection;
          		}
        	}
      		return null;
      }	    
    }
    

    연결 탱크 의 연결 을 옮 겨 다 니 며 addressroute 를 매개 변수 호출 RealConnection 대상 으로 하 는 방법 isEligible 으로 연결 이 적합 한 지 판단 합 니 다.
    적절 하 다 면 StreamAllocationacquire 방법 을 호출 한 후 RealConnection 대상 으로 돌아간다.모든 연결 이 맞지 않 을 때 되 돌아 오기 null.StreamAllocationacquire 방법 을 살 펴 보 자.
    public final class StreamAllocation {
        public void acquire(RealConnection connection, boolean reportedAcquired) {
            assert (Thread.holdsLock(connectionPool));
            if (this.connection != null) throw new IllegalStateException();
            this.connection = connection;
            this.reportedAcquired = reportedAcquired;
            connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
     	}
    }
    
    StreamAllocation 의 변수 connectionnull 인지 아 닌 지 를 먼저 판단 한다.null 가 아 닌 연결 이 포함 되 어 있 음 을 설명 하 며 상태 이상 을 직접 던 집 니 다.
    가장 중요 한 창설 대상 StreamAllocationReferenceStreamAllocationReference 의 약 한 인용 이다.그리고 StreamAllocation 대상 RealConnectionconnection 변수 에 추가 합 니 다.이렇게 하면 allocationsRealConnection 변 수 를 옮 겨 다 니 며 allocations 사용 중인 지 확인 할 수 있다.
    put 방법
    public final class ConnectionPool {
        private final Deque<RealConnection> connections = new ArrayDeque<>();
    	void put(RealConnection connection) {
            assert (Thread.holdsLock(this));
            if (!cleanupRunning) {
              cleanupRunning = true;
              executor.execute(cleanupRunnable);
            }
            connections.add(connection);
      	}
    }
    
    StreamAllocation 방법 은 비교적 간단 하 다. 먼저 판단 put, cleanupRunning 은 연결 을 정리 하 는 작업 이 실행 중이 라 고 밝 혔 다.실행 되 지 않 으 면 스 레 드 탱크 를 호출 하여 실행 합 니 다 cleanupRunning.그 다음 에 cleanupRunnable 대상 RealConnection 을 변수 connection 에 추가 했다.
    연결 청소
    public final class ConnectionPool {
        private final Runnable cleanupRunnable = new Runnable() {
        @Override public void run() {
          while (true) {
            //    cleanup         。
            long waitNanos = cleanup(System.nanoTime());
            //     -1    
            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) {
                }
              }
            }
          }
        }
      };
    }
    
    connections 방법 은 세 개의 값 을 되 돌려 줍 니 다. 하 나 는 - 1 로 직접 돌아 갑 니 다. 하 나 는 0 이 순환 을 실행 하고 계속 청 소 를 하 는 것 과 같 습 니 다. 0 이상 이면 청 소 를 기다 리 는 시간 을 설정 합 니 다.
    방법 을 분석 해 보 겠 습 니 다.
    반환 값
  • cleanUp 현재 연결 되 지 않 았 음
  • cleanUp 계속 청소 작업 이 필요 하 다 고 밝 혔 다
  • 대기 시간 -1 보다 크다
  • public final class ConnectionPool {
        long cleanup(long now) {
        	int inUseConnectionCount = 0;
        	int idleConnectionCount = 0;
        	RealConnection longestIdleConnection = null;
        	long longestIdleDurationNs = Long.MIN_VALUE;
            ......
        }
    }
    

    먼저 변 수 를 정의 합 니 다.
  • 0 는 사용 중인 연결 수량 을 나타 낸다.
  • 0 남 은 연결 수량 을 나타 낸다.
  • inUseConnectionCount 은 여가 시간 이 가장 긴 연결 을 나타 낸다.
  • idleConnectionCount 는 가장 긴 여가 시간 을 나타 낸다.
  • public final class ConnectionPool {
        long cleanup(long now) {
            ......
            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;
            		}
          		}
                 ......
        	}
        }
    }
    
    

    순환 longestIdleConnection, 호출 longestIdleDurationNs 가 져 오기 connections 대상 의 pruneAndGetAllocationCount 수량 을 반복 합 니 다. RealConnection 보다 많 으 면 존재 StreamAllocation 가 사용 중 이 며 증가 0 합 니 다.RealConnection 로 설명 inUseConnectionCount 대상 이 이미 비어 있다 면 증가 0.그리고 연결 의 남 은 시간 을 계산 합 니 다.남 은 시간 이 현재 의 최대 치 RealConnection 보다 많 을 때, 우 리 는 값 idleConnectionCountlongestIdleDurationNs 을 부여 합 니 다.
    public final class ConnectionPool {    
        long cleanup(long now) {  
            ......
            synchronized (this) {
                ......
                //                                    ,
            	if (longestIdleDurationNs >= this.keepAliveDurationNs
              		|| idleConnectionCount > this.maxIdleConnections) {
                    //    ,     。
            		connections.remove(longestIdleConnection);
          		} else if (idleConnectionCount > 0) {
                    //       。
            		return keepAliveDurationNs - longestIdleDurationNs;
          		} else if (inUseConnectionCount > 0) {
                    //       ,         
            		return keepAliveDurationNs;
          		} else {
                    //      ,    -1。
            		cleanupRunning = false;
            		return -1;
          		}
        	}
            //           ,   0,    clean。
        	closeQuietly(longestIdleConnection.socket());
        	return 0;
      	}
    }
    

    상술 한 코드 는 이미 주석 을 달 았 으 며, 서로 다른 조건 에 따라 응답 처 리 를 한다.
    우 리 는 이어서 longestIdleDurationNs 방법의 논 리 를 분석 해 보 자.
    public final class ConnectionPool {    
    	private int pruneAndGetAllocationCount(RealConnection connection, long now) {
            List<Reference<StreamAllocation>> references = connection.allocations;
            for (int i = 0; i < references.size(); ) {
              	Reference<StreamAllocation> reference = references.get(i);
              	if (reference.get() != null) {
                	i++;
                	continue;
              	}
              	references.remove(i);
              	connection.noNewStreams = true;
              // If this was the last allocation, the connection is eligible for immediate eviction.
              	if (references.isEmpty()) {
                	connection.idleAtNanos = now - keepAliveDurationNs;
                	return 0;
              	}
            }
            return references.size();
      	}
    }
    
    longestIdleConnection 약 인용 pruneAndGetAllocationCount 대상.RealConnection 약 한 인용 이 존재 하 는 지 여 부 를 옮 겨 다 니 는 것 이다.존재 한다 면 StreamAllocation 사용 하고 있다 는 뜻 이 고, 말 이 없 으 면 이미 사용 하지 않 는 다 는 뜻 이다.
    if (references.isEmpty()) {
    	connection.idleAtNanos = now - keepAliveDurationNs;
    	return 0;
    }
    

    이 코드 는 연결 의 생존 시간 을 단축 시 키 고 현재 시간 에서 생존 시간 을 빼 면 현재 시간 에 회수 할 수 있다 는 것 을 의미한다.생존 전략 과 충돌 합 니 다.
    ConnectInterceptor
    public final class ConnectInterceptor implements Interceptor {
    	@Override public Response intercept(Chain chain) throws IOException {
            RealInterceptorChain realChain = (RealInterceptorChain) chain;
            Request request = realChain.request();
            StreamAllocation streamAllocation = realChain.streamAllocation();
    
            boolean doExtensiveHealthChecks = !request.method().equals("GET");
            //  StreamAllocation newStream  
            HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
            //  StreamAllocation RealConnection  
            RealConnection connection = streamAllocation.connection();
    	    //        
            return realChain.proceed(request, streamAllocation, httpCodec, connection);
      }    
    }
    
    pruneAndGetAllocationCount 먼저 RealConnection 가 만 든 ConnectInterceptor 을 얻 었 습 니 다.
    이어서
  • 호출 RetryAndFollowUpInterceptor 대상 StreamAllocation 방법 으로 StreamAllocation 대상 을 얻 을 수 있 습 니 다.
  • 호출 newStreamHttpCodec 방법.
  • 마지막 으로 호출 StreamAllocationconnection 방법 으로 처리 했다.다음 차단기 에 전달 하 는 것 이다.

  • 다음은 일일이 분석 하 겠 습 니 다.
    Stream Allocation 의 Chain 방법proceednewStream 방법 이 비교적 많 고 단계별 로 읽는다.
    public final class StreamAllocation {
    
    	 public HttpCodec newStream(
          	OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
        	int connectTimeout = chain.connectTimeoutMillis();
        	int readTimeout = chain.readTimeoutMillis();
        	int writeTimeout = chain.writeTimeoutMillis();
        	int pingIntervalMillis = client.pingIntervalMillis();
        	boolean connectionRetryEnabled = client.retryOnConnectionFailure();
        	......
    }
    

    시간 초과 등 정 보 를 읽 습 니 다.
    public final class StreamAllocation {
    	 public HttpCodec newStream(
          	OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
            ......
            try {
              RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
                  writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
              HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
              synchronized (connectionPool) {
                codec = resultCodec;
                return resultCodec;
              }
            } catch (IOException e) {
              throw new RouteException(e);
            }
         }
    }
    

    주로 두 가지 기능 입 니 다.
  • StreamAllocation 방법 으로 연결 획득,
  • 대상 을 newStreamfindHealthyConnection 방법 으로 얻 은 후 되 돌려 준다.
  • RealConnectionnewCodec 방법 을 분석 하여 StreamAllocation 대상 을 얻 자.
    public final class StreamAllocation {
        private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
          	int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
          	boolean doExtensiveHealthChecks) throws IOException {
            while (true) {
              RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
                  pingIntervalMillis, connectionRetryEnabled);
    
              //successCount 0       ,           successCount
              synchronized (connectionPool) {
                if (candidate.successCount == 0) {
                  return candidate;
                }
              }
    
              //           
              if (!candidate.isHealthy(doExtensiveHealthChecks)) {
                noNewStreams();
                continue;
              }
    
              return candidate;
            }
          }
    }
    

    순환 호출 findHealthyConnection 방법 으로 대상 획득 RealConnection.그리고 findConnection 대상 을 판단 하 는 RealConnectionRealConnection 이 고 successCount 설명 0 이 사용 되 지 않 으 면 바로 돌아간다.0 를 위해 서 는 현재 RealConnection 의 건강 여 부 를 0 방법 으로 판단 해 야 하지 않 으 며, 주로 isHealthy 의 폐쇄 여부 와 흐름 의 폐쇄 여 부 를 판단 해 야 한다.RealConnectionSocket 방법 이 비교적 많 습 니 다. 우 리 는 관건 적 인 코드 만 봅 니 다.
    public final class StreamAllocation {
        private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
                                              int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
           Connection releasedConnection;
           synchronized (connectionPool) {
               //    
           	   if (released) throw new IllegalStateException("released");
          	   if (codec != null) throw new IllegalStateException("codec != null");
          	   if (canceled) throw new IOException("Canceled");
               //    ,  
               if (result == null) {
                   //             
                   Internal.instance.get(connectionPool, address, this, null);
                   if (connection != null) {
                     foundPooledConnection = true;
                     result = connection;
                   } else {
                     selectedRoute = route;
                   }
              }
           }
           if (result != null) {
               return result;
           }
    	   ......
        }
    }
    

    먼저 잘못 사용 한 다음 에 StreamAllocationfindConnection 방법 으로 연결 풀 Internal 에서 연결 을 얻 습 니 다.get 가 져 오지 않 으 면 connectionPool 바로 돌아 갑 니 다.
    public final class StreamAllocation {
        private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
                                              int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    	synchronized (connectionPool) {
          if (canceled) throw new IOException("Canceled");
    	  //  Route           
          if (newRouteSelection) {
            List<Route> routes = routeSelection.getAll();
            for (int i = 0, size = routes.size(); i < size; i++) {
              Route route = routes.get(i);
              Internal.instance.get(connectionPool, address, this, route);
              if (connection != null) {
                foundPooledConnection = true;
                result = connection;
                this.route = route;
                break;
              }
            }
          }
         //            
         if (!foundPooledConnection) {
            if (selectedRoute == null) {
              selectedRoute = routeSelection.next();
            }
            route = selectedRoute;
            refusedStreamCount = 0;
            //  RealConnection  
            result = new RealConnection(connectionPool, selectedRoute);
            acquire(result, false);
          }
      	}
    }
    
    result 에 따라 연결 풀 null 에서 대상 을 다시 가 져 옵 니 다.가 져 오지 못 하면 대상 을 만 듭 니 다 Route.
    public final class StreamAllocation {
        private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
                                              int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
        if (foundPooledConnection) {
          return result;
        }
        .......
    }
    

    연결 풀 에서 연결 대상 을 가 져 오 면 connectionPoolRealConnection 이 고 바로 돌아간다.
    public final class StreamAllocation {
        private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
                                              int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
        if (foundPooledConnection) {
          return result;
        }
        result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
            connectionRetryEnabled, call, eventListener);
        routeDatabase().connected(result.route());
    
        Socket socket = null;
        synchronized (connectionPool) {
          reportedAcquired = true;
    
          // Pool the connection.
          Internal.instance.put(connectionPool, result);
    
          // If another multiplexed connection to the same address was created concurrently, then
          // release this connection and acquire that one.
          if (result.isMultiplexed()) {
            socket = Internal.instance.deduplicate(connectionPool, address, this);
            result = connection;
          }
        }
        closeQuietly(socket);
    
        eventListener.connectionAcquired(call, result);
        return result;
    }
    
    foundPooledConnectiontrue 연결 풀 에서 연결 대상 을 얻 지 못 했다 는 설명 이다. 그것 이 바로 새로 만 든 연결 이다.우 리 는 foundPooledConnectionfalse 를 호출 하여 연결 해 야 한다.그 다음 에 RealConnection 대상 을 호출 하 는 connect 방법 으로 연결 을 연결 풀 에 넣 고 마지막 으로 돌아 갑 니 다.
    이 어 분석 Internal 방법 put 이 뒤 를 이 었 다.
    public final class RealConnection extends Http2Connection.Listener implements Connection {
        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);
            }
      }
    }
    

    주로 창설 RealConnection.newCodec 인 코딩 요청, 디 코딩 응답 에 사 용 됩 니 다.
    StreamAllocation 의 connection 방법
    public final class StreamAllocation{
        public synchronized RealConnection connection() {
        	return connection;
    	}
    }
    
    
    HttpCodecHttpCodec 변 수 를 직접 되 돌려 줍 니 다.

    좋은 웹페이지 즐겨찾기