日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

OkHttp3的连接池及连接建立过程分析

發(fā)布時(shí)間:2024/4/11 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 OkHttp3的连接池及连接建立过程分析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

如我們前面在 OkHttp3 HTTP請求執(zhí)行流程分析 中的分析,OkHttp3通過Interceptor鏈來執(zhí)行HTTP請求,整體的執(zhí)行過程大體如下:


OkHttp Flow

這些Interceptor中每一個(gè)的職責(zé),這里不再贅述。

在OkHttp3中,StreamAllocation是用來建立執(zhí)行HTTP請求所需網(wǎng)絡(luò)設(shè)施的組件,如其名字所顯示的那樣,分配Stream。但它具體做的事情根據(jù)是否設(shè)置了代理,以及請求的類型,如HTTP、HTTPS或HTTP/2的不同而有所不同。代理相關(guān)的處理,包括TCP連接的建立,在 OkHttp3中的代理與路由 一文中有詳細(xì)的說明。

在整個(gè)HTTP請求的執(zhí)行過程中,StreamAllocation 對象分配的比較早,在RetryAndFollowUpInterceptor.intercept(Chain chain)中就完成了:

@Override public Response intercept(Chain chain) throws IOException {Request request = chain.request();streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), callStackTrace);

StreamAllocation的對象構(gòu)造過程沒有什么特別的:

public StreamAllocation(ConnectionPool connectionPool, Address address, Object callStackTrace) {this.connectionPool = connectionPool;this.address = address;this.routeSelector = new RouteSelector(address, routeDatabase());this.callStackTrace = callStackTrace;}

在OkHttp3中,okhttp3.internal.http.RealInterceptorChain將Interceptor連接成執(zhí)行鏈。RetryAndFollowUpInterceptor借助于RealInterceptorChain將創(chuàng)建的StreamAllocation對象傳遞給后面執(zhí)行的Interceptor。而在RealInterceptorChain中,StreamAllocation對象并沒有被真正用到。緊跟在RetryAndFollowUpInterceptor之后執(zhí)行的 okhttp3.internal.http.BridgeInterceptor 和 okhttp3.internal.cache.CacheInterceptor,它們的職責(zé)分別是補(bǔ)足用戶創(chuàng)建的請求中缺少的必須的請求頭和處理緩存,也沒有真正用到StreamAllocation對象。

在OkHttp3的HTTP請求執(zhí)行過程中,okhttp3.internal.connection.ConnectInterceptor和okhttp3.internal.http.CallServerInterceptor是與網(wǎng)絡(luò)交互的關(guān)鍵。

CallServerInterceptor負(fù)責(zé)將HTTP請求寫入網(wǎng)絡(luò)IO流,并從網(wǎng)絡(luò)IO流中讀取服務(wù)器返回的數(shù)據(jù)。而ConnectInterceptor則負(fù)責(zé)為CallServerInterceptor建立可用的連接。此處 可用的 含義主要為,可以直接寫入HTTP請求的數(shù)據(jù):

  • 設(shè)置了HTTP代理的HTTP請求,與代理建立好TCP連接;
  • 設(shè)置了HTTP代理的HTTPS請求,與HTTP服務(wù)器建立通過HTTP代理的隧道連接,并完成TLS握手;
  • 設(shè)置了HTTP代理的HTTP/2請求,與HTTP服務(wù)器建立通過HTTP代理的隧道連接,并完成與服務(wù)器的TLS握手及協(xié)議協(xié)商;
  • 設(shè)置了SOCKS代理的HTTP請求,通過代理與HTTP服務(wù)器建立好連接;
  • 設(shè)置了SOCKS代理的HTTPS請求,通過代理與HTTP服務(wù)器建立好連接,并完成TLS握手;
  • 設(shè)置了SOCKS代理的HTTP/2請求,通過代理與HTTP服務(wù)器建立好連接,并完成與服務(wù)器的TLS握手及協(xié)議協(xié)商;
  • 無代理的HTTP請求,與服務(wù)器建立好TCP連接;
  • 無代理的HTTPS請求,與服務(wù)器建立TCP連接,并完成TLS握手;
  • 無代理的HTTP/2請求,與服務(wù)器建立好TCP連接,完成TLS握手及協(xié)議協(xié)商。

后面我們更詳細(xì)地來看一下這個(gè)過程。

ConnectInterceptor的代碼看上去比較簡單:

public final class ConnectInterceptor implements Interceptor {public final OkHttpClient client;public ConnectInterceptor(OkHttpClient client) {this.client = client;}@Override public Response intercept(Chain chain) throws IOException {RealInterceptorChain realChain = (RealInterceptorChain) chain;Request request = realChain.request();StreamAllocation streamAllocation = realChain.streamAllocation();// We need the network to satisfy this request. Possibly for validating a conditional GET.boolean doExtensiveHealthChecks = !request.method().equals("GET");HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);RealConnection connection = streamAllocation.connection();return realChain.proceed(request, streamAllocation, httpCodec, connection);} }

ConnectInterceptor從RealInterceptorChain獲取前面的Interceptor傳過來的StreamAllocation對象,執(zhí)行 streamAllocation.newStream() 完成前述所有的連接建立工作,并將這個(gè)過程中創(chuàng)建的用于網(wǎng)絡(luò)IO的RealConnection對象,以及對于與服務(wù)器交互最為關(guān)鍵的HttpCodec等對象傳遞給后面的Interceptor,也就是CallServerInterceptor。

OkHttp3的連接池

在具體地分析 streamAllocation.newStream() 的執(zhí)行過程之前,我們先來看一下OkHttp3的連接池的設(shè)計(jì)實(shí)現(xiàn)。

OkHttp3將客戶端與服務(wù)器之間的連接抽象為Connection/RealConnection,為了管理這些連接的復(fù)用而設(shè)計(jì)了ConnectionPool。共享相同Address的請求可以復(fù)用連接,ConnectionPool實(shí)現(xiàn)了哪些連接保持打開狀態(tài)以備后用的策略。

ConnectionPool是什么?

借助于ConnectionPool的成員變量聲明來一窺ConnectionPool究竟是什么:

/*** Manages reuse of HTTP and HTTP/2 connections for reduced network latency. HTTP requests that* share the same {@link Address} may share a {@link Connection}. This class implements the policy* of which connections to keep open for future use.*/ public final class ConnectionPool {/*** Background threads are used to cleanup expired connections. There will be at most a single* thread running per connection pool. The thread pool executor permits the pool itself to be* garbage collected.*/private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));/** The maximum number of idle connections for each address. */private final int maxIdleConnections;private final long keepAliveDurationNs;private final Runnable cleanupRunnable = new Runnable() {@Override public void run() {while (true) {long waitNanos = cleanup(System.nanoTime());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) {}}}}}};private final Deque<RealConnection> connections = new ArrayDeque<>();final RouteDatabase routeDatabase = new RouteDatabase();boolean cleanupRunning;

ConnectionPool的核心是RealConnection的容器,且是順序容器,而不是關(guān)聯(lián)容器。ConnectionPool用雙端隊(duì)列Deque<RealConnection>來保存它所管理的所有RealConnection。

ConnectionPool還會對連接池中最大的空閑連接數(shù)及連接的保活時(shí)間進(jìn)行控制,maxIdleConnections和keepAliveDurationNs成員分別體現(xiàn)對最大空閑連接數(shù)及連接保活時(shí)間的控制。這種控制通過匿名的Runnable cleanupRunnable在線程池executor中執(zhí)行,并在向連接池中添加新的RealConnection觸發(fā)。

連接池ConnectionPool的創(chuàng)建

OkHttp3的用戶可以自行創(chuàng)建ConnectionPool,對最大空閑連接數(shù)及連接的保活時(shí)間進(jìn)行配置,并在OkHttpClient創(chuàng)建期間,將其傳給OkHttpClient.Builder,在OkHttpClient中啟用它。沒有定制連接池的情況下,則在OkHttpClient.Builder構(gòu)造過程中以默認(rèn)參數(shù)創(chuàng)建:

public Builder() {dispatcher = new Dispatcher();protocols = DEFAULT_PROTOCOLS;connectionSpecs = DEFAULT_CONNECTION_SPECS;proxySelector = ProxySelector.getDefault();cookieJar = CookieJar.NO_COOKIES;socketFactory = SocketFactory.getDefault();hostnameVerifier = OkHostnameVerifier.INSTANCE;certificatePinner = CertificatePinner.DEFAULT;proxyAuthenticator = Authenticator.NONE;authenticator = Authenticator.NONE;connectionPool = new ConnectionPool();

ConnectionPool的默認(rèn)構(gòu)造過程如下:

/*** Create a new connection pool with tuning parameters appropriate for a single-user application.* The tuning parameters in this pool are subject to change in future OkHttp releases. Currently* this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity.*/public ConnectionPool() {this(5, 5, TimeUnit.MINUTES);}public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {this.maxIdleConnections = maxIdleConnections;this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);// Put a floor on the keep alive duration, otherwise cleanup will spin loop.if (keepAliveDuration <= 0) {throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);}}

在默認(rèn)情況下,ConnectionPool 最多保存 5個(gè) 處于空閑狀態(tài)的連接,且連接的默認(rèn)保活時(shí)間為 5分鐘

RealConnection的存/取

OkHttp內(nèi)部的組件可以通過put()方法向ConnectionPool中添加RealConnection:

void put(RealConnection connection) {assert (Thread.holdsLock(this));if (!cleanupRunning) {cleanupRunning = true;executor.execute(cleanupRunnable);}connections.add(connection);}

在向ConnectionPool中添加RealConnection時(shí),若發(fā)現(xiàn)cleanupRunnable還沒有運(yùn)行會觸發(fā)它的運(yùn)行。

cleanupRunnable的職責(zé)本就是清理無效的RealConnection,只要ConnectionPool中存在RealConnection,則這種清理的需求總是存在的,因而這里會去啟動cleanupRunnable。

根據(jù)需要啟動了cleanupRunnable之后,將RealConnection添加進(jìn)雙端隊(duì)列connections。

這里先啟動 cleanupRunnable,后向 connections 中添加RealConnection。有沒有可能發(fā)生:

啟動cleanupRunnable之后,向connections中添加RealConnection之前,執(zhí)行 put() 的線程被搶占,cleanupRunnable的線程被執(zhí)行,它發(fā)現(xiàn)connections中沒有任何RealConnection,于是從容地退出而導(dǎo)致后面添加的RealConnection永遠(yuǎn)不會得得清理。

這樣的情況呢?答案是 不會。為什么呢?put()執(zhí)行之前總是會用ConnectionPool對象鎖來保護(hù),而在ConnectionPool.cleanup()中,遍歷connections也總是會先對ConnectionPool對象加鎖保護(hù)的。即使執(zhí)行 put() 的線程被搶占,cleanupRunnable的線程也會由于拿不到ConnectionPool對象鎖而等待 put() 執(zhí)行結(jié)束。

OkHttp內(nèi)部的組件可以通過 get() 方法從ConnectionPool中獲取RealConnection:

/** Returns a recycled connection to {@code address}, or null if no such connection exists. */RealConnection get(Address address, StreamAllocation streamAllocation) {assert (Thread.holdsLock(this));for (RealConnection connection : connections) {if (connection.allocations.size() < connection.allocationLimit&& address.equals(connection.route().address)&& !connection.noNewStreams) {streamAllocation.acquire(connection);return connection;}}return null;}

get() 方法遍歷 connections 中的所有 RealConnection 尋找同時(shí)滿足如下三個(gè)條件的RealConnection:

  • RealConnection的allocations的數(shù)量小于allocationLimit。每個(gè)allocation代表在該RealConnection上正在執(zhí)行的一個(gè)請求。這個(gè)條件用于控制相同連接上,同一時(shí)間執(zhí)行的并發(fā)請求的個(gè)數(shù)。對于HTTP/2連接而言,allocationLimit限制是在連接建立階段由雙方協(xié)商的。對于HTTP或HTTPS連接而言,這個(gè)值則總是1。從RealConnection.establishProtocol()可以清晰地看到這一點(diǎn):

    if (protocol == Protocol.HTTP_2) {socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.Http2Connection http2Connection = new Http2Connection.Builder(true).socket(socket, route.address().url().host(), source, sink).listener(this).build();http2Connection.start();// Only assign the framed connection once the preface has been sent successfully.this.allocationLimit = http2Connection.maxConcurrentStreams();this.http2Connection = http2Connection;} else {this.allocationLimit = 1;}
  • RealConnection 的 address 與傳入的 Address 參數(shù)相等。RealConnection 的 address 描述建立連接所需的配置信息,包括對端的信息等,不難理解只有所有相關(guān)配置相等時(shí) RealConnection 才是真正能復(fù)用的。具體看一下Address相等性比較的依據(jù):@Override public boolean equals(Object other) {if (other instanceof Address) {Address that = (Address) other;return this.url.equals(that.url)&& this.dns.equals(that.dns)&& this.proxyAuthenticator.equals(that.proxyAuthenticator)&& this.protocols.equals(that.protocols)&& this.connectionSpecs.equals(that.connectionSpecs)&& this.proxySelector.equals(that.proxySelector)&& equal(this.proxy, that.proxy)&& equal(this.sslSocketFactory, that.sslSocketFactory)&& equal(this.hostnameVerifier, that.hostnameVerifier)&& equal(this.certificatePinner, that.certificatePinner);}return false; } 這種相等性的條件給人感覺還是蠻苛刻的,特別是對url的對比。
    這難免會讓我們有些擔(dān)心,對 Address 如此苛刻的相等性比較,又有多大的機(jī)會能復(fù)用連接呢?
    我們的擔(dān)心其實(shí)是多余的。只有在 StreamAllocation.findConnection() 中,會通過Internal.instance 調(diào)用 ConnectionPool.get() 來獲取 RealConnection :
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,boolean connectionRetryEnabled) throws IOException {Route selectedRoute;synchronized (connectionPool) {if (released) throw new IllegalStateException("released");if (codec != null) throw new IllegalStateException("codec != null");if (canceled) throw new IOException("Canceled");RealConnection allocatedConnection = this.connection;if (allocatedConnection != null && !allocatedConnection.noNewStreams) {return allocatedConnection;}// Attempt to get a connection from the pool.RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);if (pooledConnection != null) {this.connection = pooledConnection;return pooledConnection;}selectedRoute = route;}if (selectedRoute == null) {selectedRoute = routeSelector.next();synchronized (connectionPool) {route = selectedRoute;refusedStreamCount = 0;}}RealConnection newConnection = new RealConnection(selectedRoute);synchronized (connectionPool) {acquire(newConnection);Internal.instance.put(connectionPool, newConnection);this.connection = newConnection;if (canceled) throw new IOException("Canceled");}

Internal.instance的實(shí)現(xiàn)在OkHttpClient 中:

static {Internal.instance = new Internal() {@Override public void addLenient(Headers.Builder builder, String line) {builder.addLenient(line);}@Override public void addLenient(Headers.Builder builder, String name, String value) {builder.addLenient(name, value);}@Override public void setCache(OkHttpClient.Builder builder, InternalCache internalCache) {builder.setInternalCache(internalCache);}@Override public boolean connectionBecameIdle(ConnectionPool pool, RealConnection connection) {return pool.connectionBecameIdle(connection);}@Override public RealConnection get(ConnectionPool pool, Address address, StreamAllocation streamAllocation) {return pool.get(address, streamAllocation);}@Override public void put(ConnectionPool pool, RealConnection connection) {pool.put(connection);}@Override public RouteDatabase routeDatabase(ConnectionPool connectionPool) {return connectionPool.routeDatabase;}@Override public StreamAllocation callEngineGetStreamAllocation(Call call) {return ((RealCall) call).streamAllocation();}

可見 ConnectionPool.get() 的 Address 參數(shù)來自于StreamAllocation。StreamAllocation的Address 在構(gòu)造時(shí)由外部傳入。構(gòu)造了StreamAllocation對象的RetryAndFollowUpInterceptor,其構(gòu)造Address的過程是這樣的:

private Address createAddress(HttpUrl url) {SSLSocketFactory sslSocketFactory = null;HostnameVerifier hostnameVerifier = null;CertificatePinner certificatePinner = null;if (url.isHttps()) {sslSocketFactory = client.sslSocketFactory();hostnameVerifier = client.hostnameVerifier();certificatePinner = client.certificatePinner();}return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());}

Address 除了 uriHost 和 uriPort 外的所有構(gòu)造參數(shù)均來自于OkHttpClient,而Address的url 字段正是根據(jù)這兩個(gè)參數(shù)構(gòu)造的:

public Address(String uriHost, int uriPort, Dns dns, SocketFactory socketFactory,SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier,CertificatePinner certificatePinner, Authenticator proxyAuthenticator, Proxy proxy,List<Protocol> protocols, List<ConnectionSpec> connectionSpecs, ProxySelector proxySelector) {this.url = new HttpUrl.Builder().scheme(sslSocketFactory != null ? "https" : "http").host(uriHost).port(uriPort).build();

可見 Address 的 url 字段僅包含HTTP請求url的 schema + host + port 這三部分的信息,而不包含 path 和 query 等信息。ConnectionPool主要是根據(jù)服務(wù)器的地址來決定復(fù)用的。

  • RealConnection還有可分配的Stream。對于HTTP或HTTPS而言,不能同時(shí)在相同的連接上執(zhí)行多個(gè)請求。即使對于HTTP/2而言,StreamID的空間也是有限的,同一個(gè)連接上的StreamID總有分配完的時(shí)候,而在StreamID被分配完了之后,該連接就不能再被使用了。

OkHttp內(nèi)部對ConnectionPool的訪問總是通過Internal.instance來進(jìn)行。整個(gè)OkHttp中也只有StreamAllocation 存取了 ConnectionPool,也就是我們前面列出的StreamAllocation.findConnection() 方法,相關(guān)的組件之間的關(guān)系大體如下圖:


OkHttp Connection Pool

RealConnection的清理

ConnectionPool 中對于 RealConnection 的清理在put()方法中觸發(fā),執(zhí)行 cleanupRunnable 來完成清理動作:

private final Runnable cleanupRunnable = new Runnable() {@Override public void run() {while (true) {long waitNanos = cleanup(System.nanoTime());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) {}}}}}};

cleanupRunnable每執(zhí)行一次清理動作,都會等待一段時(shí)間再次執(zhí)行,而具體等待的時(shí)長由cleanup()方法決定,直到cleanup()方法返回-1退出。cleanup()方法定義如下:

/*** Performs maintenance on this pool, evicting the connection that has been idle the longest if* either it has exceeded the keep alive limit or the idle connections limit.** <p>Returns the duration in nanos to sleep until the next scheduled call to this method. Returns* -1 if no further cleanups are required.*/long cleanup(long now) {int inUseConnectionCount = 0;int idleConnectionCount = 0;RealConnection longestIdleConnection = null;long longestIdleDurationNs = Long.MIN_VALUE;// Find either a connection to evict, or the time that the next eviction is due.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;}}if (longestIdleDurationNs >= this.keepAliveDurationNs|| idleConnectionCount > this.maxIdleConnections) {// We've found a connection to evict. Remove it from the list, then close it below (outside// of the synchronized block).connections.remove(longestIdleConnection);} else if (idleConnectionCount > 0) {// A connection will be ready to evict soon.return keepAliveDurationNs - longestIdleDurationNs;} else if (inUseConnectionCount > 0) {// All connections are in use. It'll be at least the keep alive duration 'til we run again.return keepAliveDurationNs;} else {// No connections, idle or in use.cleanupRunning = false;return -1;}}closeQuietly(longestIdleConnection.socket());// Cleanup again immediately.return 0;}/*** Prunes any leaked allocations and then returns the number of remaining live allocations on* {@code connection}. Allocations are leaked if the connection is tracking them but the* application code has abandoned them. Leak detection is imprecise and relies on garbage* collection.*/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;}// We've discovered a leaked allocation. This is an application bug.StreamAllocation.StreamAllocationReference streamAllocRef =(StreamAllocation.StreamAllocationReference) reference;String message = "A connection to " + connection.route().address().url()+ " was leaked. Did you forget to close a response body?";Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);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();}

cleanup()方法遍歷connections,并從中找到處于空閑狀態(tài)時(shí)間最長的一個(gè)RealConnection,然后根據(jù)查找結(jié)果的不同,分為以下幾種情況處理:

  • 找到一個(gè)處于空閑狀態(tài)的RealConnection,且該RealConnection處于空閑狀態(tài)的時(shí)間超出了設(shè)置的保活時(shí)間,或者當(dāng)前ConnectionPool中處于空閑狀態(tài)的連接數(shù)超出了設(shè)置的最大空閑連接數(shù),將該RealConnection從connections中移除,并關(guān)閉該RealConnection關(guān)聯(lián)的底層socket,然后返回0,以此請求cleanupRunnable立即再次檢查所有的連接。
  • 找到一個(gè)處于空閑狀態(tài)的RealConnection,但該RealConnection處于空閑狀態(tài)的時(shí)間尚未超出設(shè)置的保活時(shí)間,且當(dāng)前ConnectionPool中處于空閑狀態(tài)的連接數(shù)尚未超出設(shè)置的最大空閑連接數(shù),則返回保活時(shí)間與該RealConnection處于空閑狀態(tài)的時(shí)間之間的差值,請求cleanupRunnable等待這么長一段時(shí)間之后再次檢查所有的連接。
  • 沒有找到處于空閑狀態(tài)的連接,但找到了使用中的連接,則返回保活時(shí)間,請求cleanupRunnable等待這么長一段時(shí)間之后再次檢查所有的連接。
  • 沒有找到處于空閑狀態(tài)的連接,也沒有找到使用中的連接,也就意味著連接池中尚沒有任何連接,則將 cleanupRunning 置為false,并返回 -1,請求 cleanupRunnable 退出。

cleanup() 通過 pruneAndGetAllocationCount() 檢查正在使用一個(gè)特定連接的請求個(gè)數(shù),并以此來判斷一個(gè)連接是否處于空閑狀態(tài)。后者通遍歷 connection.allocations 并檢查每個(gè)元素的StreamAllocation 的狀態(tài),若StreamAllocation 為空,則認(rèn)為是發(fā)現(xiàn)了一個(gè)leak,它會更新連接的空閑時(shí)間為當(dāng)前時(shí)間減去保活時(shí)間并返回0,以此請求 cleanup() 立即關(guān)閉、清理掉該 leak 的連接。

ConnectionPool的用戶接口

OkHttp的用戶可以自己創(chuàng)建 ConnectionPool 對象,這個(gè)類也提供了一些用戶接口以方便用戶獲取空閑狀態(tài)的連接數(shù)、總的連接數(shù),以及手動清除空閑狀態(tài)的連接:

/** Returns the number of idle connections in the pool. */public synchronized int idleConnectionCount() {int total = 0;for (RealConnection connection : connections) {if (connection.allocations.isEmpty()) total++;}return total;}/*** Returns total number of connections in the pool. Note that prior to OkHttp 2.7 this included* only idle connections and HTTP/2 connections. Since OkHttp 2.7 this includes all connections,* both active and inactive. Use {@link #idleConnectionCount()} to count connections not currently* in use.*/public synchronized int connectionCount() {return connections.size();}....../** Close and remove all idle connections in the pool. */public void evictAll() {List<RealConnection> evictedConnections = new ArrayList<>();synchronized (this) {for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {RealConnection connection = i.next();if (connection.allocations.isEmpty()) {connection.noNewStreams = true;evictedConnections.add(connection);i.remove();}}}for (RealConnection connection : evictedConnections) {closeQuietly(connection.socket());}}

新建流

回到新建流的過程,連接建立的各種細(xì)節(jié)處理都在這里。 StreamAllocation.newStream() 完成新建流的動作:

public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {int connectTimeout = client.connectTimeoutMillis();int readTimeout = client.readTimeoutMillis();int writeTimeout = client.writeTimeoutMillis();boolean connectionRetryEnabled = client.retryOnConnectionFailure();try {RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);HttpCodec resultCodec;if (resultConnection.http2Connection != null) {resultCodec = new Http2Codec(client, this, resultConnection.http2Connection);} else {resultConnection.socket().setSoTimeout(readTimeout);resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);resultCodec = new Http1Codec(client, this, resultConnection.source, resultConnection.sink);}synchronized (connectionPool) {codec = resultCodec;return resultCodec;}} catch (IOException e) {throw new RouteException(e);}}

所謂的流,是封裝了底層的IO,可以直接用來收發(fā)數(shù)據(jù)的組件,它會將請求的數(shù)據(jù)序列化之后發(fā)送到網(wǎng)絡(luò),并將接收的數(shù)據(jù)反序列化為應(yīng)用程序方便操作的格式。在 OkHttp3 中,這樣的組件被抽象為HttpCodec。HttpCodec的定義如下 (okhttp/okhttp/src/main/java/okhttp3/internal/http/HttpCodec.java):

/** Encodes HTTP requests and decodes HTTP responses. */ public interface HttpCodec {/*** The timeout to use while discarding a stream of input data. Since this is used for connection* reuse, this timeout should be significantly less than the time it takes to establish a new* connection.*/int DISCARD_STREAM_TIMEOUT_MILLIS = 100;/** Returns an output stream where the request body can be streamed. */Sink createRequestBody(Request request, long contentLength);/** This should update the HTTP engine's sentRequestMillis field. */void writeRequestHeaders(Request request) throws IOException;/** Flush the request to the underlying socket. */void finishRequest() throws IOException;/** Read and return response headers. */Response.Builder readResponseHeaders() throws IOException;/** Returns a stream that reads the response body. */ResponseBody openResponseBody(Response response) throws IOException;/*** Cancel this stream. Resources held by this stream will be cleaned up, though not synchronously.* That may happen later by the connection pool thread.*/void cancel(); }

HttpCodec提供了這樣的一些操作:

  • 為發(fā)送請求而提供的,寫入請求頭部。
  • 為發(fā)送請求而提供的,創(chuàng)建請求體,以用于發(fā)送請求體數(shù)據(jù)。
  • 為發(fā)送請求而提供的,結(jié)束請求發(fā)送。
  • 為獲得響應(yīng)而提供的,讀取響應(yīng)頭部。
  • 為獲得響應(yīng)而提供的,打開請求體,以用于后續(xù)獲取請求體數(shù)據(jù)。
  • 取消請求執(zhí)行。

StreamAllocation.newStream() 主要做的事情正是創(chuàng)建HttpCodec。StreamAllocation.newStream() 根據(jù) OkHttpClient中的設(shè)置,連接超時(shí)、讀超時(shí)、寫超時(shí)及連接失敗是否重試,調(diào)用 findHealthyConnection() 完成 連接,即RealConnection 的創(chuàng)建。然后根據(jù)HTTP協(xié)議的版本創(chuàng)建Http1Codec或Http2Codec。

findHealthyConnection() 根據(jù)目標(biāo)服務(wù)器地址查找一個(gè)連接,如果它是可用的就直接返回,如果不可用則會重復(fù)查找直到找到一個(gè)可用的為止。在連接已被破壞而不可用時(shí),還會釋放連接:

/*** Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated* until a healthy connection is found.*/private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)throws IOException {while (true) {RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,connectionRetryEnabled);// If this is a brand new connection, we can skip the extensive health checks.synchronized (connectionPool) {if (candidate.successCount == 0) {return candidate;}}// Do a (potentially slow) check to confirm that the pooled connection is still good. If it// isn't, take it out of the pool and start again.if (!candidate.isHealthy(doExtensiveHealthChecks)) {noNewStreams();continue;}return candidate;}}

連接是否可用的標(biāo)準(zhǔn)如下 (okhttp/okhttp/src/main/java/okhttp3/internal/connection/RealConnection.java):

/** Returns true if this connection is ready to host new streams. */public boolean isHealthy(boolean doExtensiveChecks) {if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {return false;}if (http2Connection != null) {return true; // TODO: check framedConnection.shutdown.}if (doExtensiveChecks) {try {int readTimeout = socket.getSoTimeout();try {socket.setSoTimeout(1);if (source.exhausted()) {return false; // Stream is exhausted; socket is closed.}return true;} finally {socket.setSoTimeout(readTimeout);}} catch (SocketTimeoutException ignored) {// Read timed out; socket is good.} catch (IOException e) {return false; // Couldn't read; socket is closed.}}return true;}

首先要可以進(jìn)行IO,此外對于HTTP/2,只要http2Connection存在即可。如我們前面在ConnectInterceptor 中看到的,如果HTTP請求的method不是 "GET" ,doExtensiveChecks為true時(shí),需要做額外的檢查。

findHealthyConnection() 通過 findConnection()查找一個(gè)連接:

/*** Returns a connection to host a new stream. This prefers the existing connection if it exists,* then the pool, finally building a new connection.*/private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,boolean connectionRetryEnabled) throws IOException {Route selectedRoute;synchronized (connectionPool) {if (released) throw new IllegalStateException("released");if (codec != null) throw new IllegalStateException("codec != null");if (canceled) throw new IOException("Canceled");RealConnection allocatedConnection = this.connection;if (allocatedConnection != null && !allocatedConnection.noNewStreams) {return allocatedConnection;}// Attempt to get a connection from the pool.RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);if (pooledConnection != null) {this.connection = pooledConnection;return pooledConnection;}selectedRoute = route;}if (selectedRoute == null) {selectedRoute = routeSelector.next();synchronized (connectionPool) {route = selectedRoute;refusedStreamCount = 0;}}RealConnection newConnection = new RealConnection(selectedRoute);synchronized (connectionPool) {acquire(newConnection);Internal.instance.put(connectionPool, newConnection);this.connection = newConnection;if (canceled) throw new IOException("Canceled");}newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(),connectionRetryEnabled);routeDatabase().connected(newConnection.route());return newConnection;}

findConnection() 返回一個(gè)用于流執(zhí)行底層IO的連接。這個(gè)方法優(yōu)先復(fù)用已經(jīng)創(chuàng)建的連接;在沒有可復(fù)用連接的情況下新建一個(gè)。

在同一次 newStream() 的執(zhí)行過程中,有沒有可能兩次執(zhí)行 findConnection() ,第一次connection 字段為空,第二次不為空?這個(gè)地方對connection字段的檢查,看起來有點(diǎn)多余。執(zhí)行 findConnection() 時(shí),connection 不為空的話,意味著 codec 不為空,而在方法的開始處已經(jīng)有對codec字段的狀態(tài)做過檢查。真的是這樣的嗎?

答案當(dāng)然是否定的。同一次 newStream() 的執(zhí)行過程中,沒有可能兩次執(zhí)行findConnection(),第一次connection字段為空,第二次不為空,然而一個(gè)HTTP請求的執(zhí)行過程,又不是一定只調(diào)用一次newStream()。

newStream()的直接調(diào)用者是ConnectInterceptor,所有的Interceptor用RealInterceptorChain鏈起來,在Interceptor鏈中,ConnectInterceptor 和RetryAndFollowUpInterceptor 隔著 CacheInterceptor 和 BridgeInterceptor 。然而newStream() 如果出錯(cuò)的話,則是會通過拋出Exception返回到RetryAndFollowUpInterceptor 來處理錯(cuò)誤的。

RetryAndFollowUpInterceptor 中會嘗試基于相同的 StreamAllocation 對象來恢復(fù)對HTTP請求的處理。RetryAndFollowUpInterceptor 通過 hasMoreRoutes() 等方法,來檢查StreamAllocation 對象的狀態(tài),通過 streamFailed(IOException e)、release()、streamFinished(boolean noNewStreams, HttpCodec codec)等方法來reset StreamAllocation對象的一些狀態(tài)。

回到StreamAllocation的 findConnection()方法。沒有連接存在,且連接池中也沒有找到所需的連接時(shí),它會新建一個(gè)連接。通過如下的步驟新建連接:

  • 為連接選擇一個(gè)Route。
  • 新建一個(gè)RealConnection對象。public RealConnection(Route route) {this.route = route; }
  • 將當(dāng)前StreamAllocation對象的引用保存進(jìn)RealConnection的allocations。如我們前面在分析ConnectionPool時(shí)所見的那樣,這主要是為了追蹤RealConnection。/*** Use this allocation to hold {@code connection}. Each call to this must be paired with a call to* {@link #release} on the same connection.*/ public void acquire(RealConnection connection) {assert (Thread.holdsLock(connectionPool));connection.allocations.add(new StreamAllocationReference(this, callStackTrace)); }
  • 將RealConnection保存進(jìn)連接池。
  • 保存對RealConnection的引用。
  • 檢查請求是否被取消,若取消,則拋出異常。
  • 建立連接。
  • 更新RouteDatabase中Route的狀態(tài)。

ConnectionSpec

在OkHttp中,ConnectionSpec用于描述傳輸HTTP流量的socket連接的配置。對于https請求,這些配置主要包括協(xié)商安全連接時(shí)要使用的TLS版本號和密碼套件,是否支持TLS擴(kuò)展等;對于http請求則幾乎不包含什么信息。

OkHttp有預(yù)定義幾組ConnectionSpec (okhttp/okhttp/src/main/java/okhttp3/ConnectionSpec.java):

/** A modern TLS connection with extensions like SNI and ALPN available. */public static final ConnectionSpec MODERN_TLS = new Builder(true).cipherSuites(APPROVED_CIPHER_SUITES).tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0).supportsTlsExtensions(true).build();/** A backwards-compatible fallback connection for interop with obsolete servers. */public static final ConnectionSpec COMPATIBLE_TLS = new Builder(MODERN_TLS).tlsVersions(TlsVersion.TLS_1_0).supportsTlsExtensions(true).build();/** Unencrypted, unauthenticated connections for {@code http:} URLs. */public static final ConnectionSpec CLEARTEXT = new Builder(false).build();

預(yù)定義的這些ConnectionSpec被組織為默認(rèn)ConnectionSpec集合 (okhttp/okhttp/src/main/java/okhttp3/OkHttpClient.java):

public class OkHttpClient implements Cloneable, Call.Factory {private static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(Protocol.HTTP_2, Protocol.HTTP_1_1);private static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT);

OkHttp中由OkHttpClient管理ConnectionSpec集合 。OkHttp的用戶可以在構(gòu)造OkHttpClient的過程中提供自己的ConnectionSpec集合。默認(rèn)情況下OkHttpClient會使用前面看到的默認(rèn)ConnectionSpec集合。

在RetryAndFollowUpInterceptor中創(chuàng)建Address時(shí),ConnectionSpec集合被從OkHttpClient獲取,并由Address引用。

OkHttp還提供了ConnectionSpecSelector,用以從ConnectionSpec集合中選擇與SSLSocket匹配的ConnectionSpec,并對SSLSocket做配置的操作。

在StreamAllocation的findConnection()中,ConnectionSpec集合被從Address中取出來,以用于連接建立過程。

建立連接

回到連接建立的過程。RealConnection.connect()執(zhí)行連接建立的過程(okhttp/okhttp/src/main/java/okhttp3/internal/connection/RealConnection.java):

public void connect(int connectTimeout, int readTimeout, int writeTimeout,List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) {if (protocol != null) throw new IllegalStateException("already connected");RouteException routeException = null;ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);if (route.address().sslSocketFactory() == null) {if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {throw new RouteException(new UnknownServiceException("CLEARTEXT communication not enabled for client"));}String host = route.address().url().host();if (!Platform.get().isCleartextTrafficPermitted(host)) {throw new RouteException(new UnknownServiceException("CLEARTEXT communication to " + host + " not permitted by network security policy"));}}while (protocol == null) {try {if (route.requiresTunnel()) {buildTunneledConnection(connectTimeout, readTimeout, writeTimeout,connectionSpecSelector);} else {buildConnection(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);}} catch (IOException e) {closeQuietly(socket);closeQuietly(rawSocket);socket = null;rawSocket = null;source = null;sink = null;handshake = null;protocol = null;if (routeException == null) {routeException = new RouteException(e);} else {routeException.addConnectException(e);}if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {throw routeException;}}}}

這里的執(zhí)行過程大體如下:

  • 檢查連接是否已經(jīng)建立,若已經(jīng)建立,則拋出異常,否則繼續(xù)執(zhí)行。連接是否建立由protocol 標(biāo)識,它表示在整個(gè)連接建立,及可能的協(xié)議協(xié)商過程中選擇的所要使用的協(xié)議。
  • 根據(jù)ConnectionSpec集合connectionSpecs構(gòu)造ConnectionSpecSelector。
  • 若請求不是安全的請求,會對請求再執(zhí)行一些額外的限制。這些限制包括:
    • ConnectionSpec集合中必須要包含ConnectionSpec.CLEARTEXT。這也就是說,OkHttp的用戶可以通過為OkHttpClient設(shè)置不包含ConnectionSpec.CLEARTEXT的ConnectionSpec集合來禁用所有的明文請求。
    • 平臺本身的安全策略允許向相應(yīng)的主機(jī)發(fā)送明文請求。對于Android平臺而言,這種安全策略主要由系統(tǒng)的組件android.security.NetworkSecurityPolicy執(zhí)行 (okhttp/okhttp/src/main/java/okhttp3/internal/platform/AndroidPlatform.java):@Override public boolean isCleartextTrafficPermitted(String hostname) { try {Class<?> networkPolicyClass = Class.forName("android.security.NetworkSecurityPolicy");Method getInstanceMethod = networkPolicyClass.getMethod("getInstance");Object networkSecurityPolicy = getInstanceMethod.invoke(null);Method isCleartextTrafficPermittedMethod = networkPolicyClass.getMethod("isCleartextTrafficPermitted", String.class);return (boolean) isCleartextTrafficPermittedMethod.invoke(networkSecurityPolicy, hostname); } catch (ClassNotFoundException | NoSuchMethodException e) {return super.isCleartextTrafficPermitted(hostname); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {throw new AssertionError(); } } 平臺的這種安全策略并不是每個(gè)Android版本都有的。Android 6.0之后存在這種控制。
  • 根據(jù)請求是否需要建立隧道連接,而分別執(zhí)行buildTunneledConnection() 和 buildConnection()。是否需要建立隧道連接的依據(jù)為 (okhttp/okhttp/src/main/java/okhttp3/Route.java):/*** Returns true if this route tunnels HTTPS through an HTTP proxy. See <a* href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817, Section 5.2</a>.*/ public boolean requiresTunnel() {return address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP; } 即對于設(shè)置了HTTP代理,且安全的連接 (SSL) 需要請求代理服務(wù)器建立一個(gè)到目標(biāo)HTTP服務(wù)器的隧道連接,客戶端與HTTP代理建立TCP連接,以此請求HTTP代理服務(wù)在客戶端與HTTP服務(wù)器之間進(jìn)行數(shù)據(jù)的盲轉(zhuǎn)發(fā)。

建立隧道連接

建立隧道連接的過程如下:

  • 構(gòu)造一個(gè) 建立隧道連接 請求。
  • 與HTTP代理服務(wù)器建立TCP連接。
  • 創(chuàng)建隧道。這主要是將 建立隧道連接 請求發(fā)送給HTTP代理服務(wù)器,并處理它的響應(yīng)。
  • 重復(fù)上面的第2和第3步,知道建立好了隧道連接。至于為什么要重復(fù)多次,及關(guān)于代理認(rèn)證的內(nèi)容,可以參考代理協(xié)議相關(guān)的內(nèi)容。
  • 建立協(xié)議。
  • 關(guān)于建立隧道連接更詳細(xì)的過程可參考 OkHttp3中的代理與路由 的相關(guān)部分。

    建立普通連接

    建立普通連接的過程比較直接:

    /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */private void buildConnection(int connectTimeout, int readTimeout, int writeTimeout,ConnectionSpecSelector connectionSpecSelector) throws IOException {connectSocket(connectTimeout, readTimeout);establishProtocol(readTimeout, writeTimeout, connectionSpecSelector);}
  • 建立一個(gè)TCP連接。
  • 建立協(xié)議。
  • 更詳細(xì)的過程可參考 OkHttp3中的代理與路由 的相關(guān)部分。

    建立協(xié)議

    不管是建立隧道連接,還是建立普通連接,都少不了 建立協(xié)議 這一步。這一步是在建立好了TCP連接之后,而在該TCP能被拿來收發(fā)數(shù)據(jù)之前執(zhí)行的。它主要為數(shù)據(jù)的加密傳輸做一些初始化,比如TLS握手,HTTP/2的協(xié)議協(xié)商等。

    private void establishProtocol(int readTimeout, int writeTimeout,ConnectionSpecSelector connectionSpecSelector) throws IOException {if (route.address().sslSocketFactory() != null) {connectTls(readTimeout, writeTimeout, connectionSpecSelector);} else {protocol = Protocol.HTTP_1_1;socket = rawSocket;}if (protocol == Protocol.HTTP_2) {socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.Http2Connection http2Connection = new Http2Connection.Builder(true).socket(socket, route.address().url().host(), source, sink).listener(this).build();http2Connection.start();// Only assign the framed connection once the preface has been sent successfully.this.allocationLimit = http2Connection.maxConcurrentStreams();this.http2Connection = http2Connection;} else {this.allocationLimit = 1;}}
  • 對于加密的數(shù)據(jù)傳輸,創(chuàng)建TLS連接。對于明文傳輸,則設(shè)置protocol和socket。
    socket指向直接與應(yīng)用層,如HTTP或HTTP/2,交互的Socket:
    對于明文傳輸沒有設(shè)置HTTP代理的HTTP請求,它是與HTTP服務(wù)器之間的TCP socket;
    對于明文傳輸設(shè)置了HTTP代理或SOCKS代理的HTTP請求,它是與代理服務(wù)器之間的TCP socket;
    對于加密傳輸沒有設(shè)置HTTP代理服務(wù)器的HTTP或HTTP2請求,它是與HTTP服務(wù)器之間的SSLScoket;
    對于加密傳輸設(shè)置了HTTP代理服務(wù)器的HTTP或HTTP2請求,它是與HTTP服務(wù)器之間經(jīng)過了代理服務(wù)器的SSLSocket,一個(gè)隧道連接;
    對于加密傳輸設(shè)置了SOCKS代理的HTTP或HTTP2請求,它是一條經(jīng)過了代理服務(wù)器的SSLSocket連接。

  • 對于HTTP/2,會建立HTTP/2連接,并進(jìn)一步協(xié)商連接參數(shù),如連接上可同時(shí)執(zhí)行的并發(fā)請求數(shù)等。而對于非HTTP/2,則將連接上可同時(shí)執(zhí)行的并發(fā)請求數(shù)設(shè)置為1。

  • 建立TLS連接

    進(jìn)一步來看建立協(xié)議過程中,為安全請求所做的建立TLS連接的過程:

    private void connectTls(int readTimeout, int writeTimeout,ConnectionSpecSelector connectionSpecSelector) throws IOException {Address address = route.address();SSLSocketFactory sslSocketFactory = address.sslSocketFactory();boolean success = false;SSLSocket sslSocket = null;try {// Create the wrapper over the connected socket.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());// Verify that the socket's certificates are acceptable for the target host.if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"+ "\n certificate: " + CertificatePinner.pin(cert)+ "\n DN: " + cert.getSubjectDN().getName()+ "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));}// Check that the certificate pinner is satisfied by the certificates presented.address.certificatePinner().check(address.url().host(),unverifiedHandshake.peerCertificates());// Success! Save the handshake and the ALPN protocol.String maybeProtocol = connectionSpec.supportsTlsExtensions()? Platform.get().getSelectedProtocol(sslSocket): null;socket = sslSocket;source = Okio.buffer(Okio.source(socket));sink = Okio.buffer(Okio.sink(socket));handshake = unverifiedHandshake;protocol = maybeProtocol != null? Protocol.get(maybeProtocol): Protocol.HTTP_1_1;success = true;} catch (AssertionError e) {if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);throw e;} finally {if (sslSocket != null) {Platform.get().afterHandshake(sslSocket);}if (!success) {closeQuietly(sslSocket);}}}

    TLS連接是對原始的TCP連接的一個(gè)封裝,以提供TLS握手,及數(shù)據(jù)收發(fā)過程中的加密解密等功能。在Java中,用SSLSocket來描述。上面建立TLS連接的過程大體為:

  • 用SSLSocketFactory基于原始的TCP Socket,創(chuàng)建一個(gè)SSLSocket。
  • 配置SSLSocket。
  • 在前面選擇的ConnectionSpec支持TLS擴(kuò)展參數(shù)時(shí),配置TLS擴(kuò)展參數(shù)。
  • 啟動TLS握手。
  • TLS握手完成之后,獲取握手信息。
  • 對TLS握手過程中傳回來的證書進(jìn)行驗(yàn)證。
  • 檢查證書釘扎。
  • 在前面選擇的ConnectionSpec支持TLS擴(kuò)展參數(shù)時(shí),獲取TLS握手過程中順便完成的協(xié)議協(xié)商過程所選擇的協(xié)議。這個(gè)過程主要用于HTTP/2的ALPN擴(kuò)展。
  • OkHttp主要使用Okio來做IO操作,這里會基于前面獲取的SSLSocket創(chuàng)建用于執(zhí)行IO的BufferedSource和BufferedSink等,并保存握手信息及所選擇的協(xié)議。
  • 具體來看ConnectionSpecSelector中配置SSLSocket的過程:

    /*** Configures the supplied {@link SSLSocket} to connect to the specified host using an appropriate* {@link ConnectionSpec}. Returns the chosen {@link ConnectionSpec}, never {@code null}.** @throws IOException if the socket does not support any of the TLS modes available*/public ConnectionSpec configureSecureSocket(SSLSocket sslSocket) throws IOException {ConnectionSpec tlsConfiguration = null;for (int i = nextModeIndex, size = connectionSpecs.size(); i < size; i++) {ConnectionSpec connectionSpec = connectionSpecs.get(i);if (connectionSpec.isCompatible(sslSocket)) {tlsConfiguration = connectionSpec;nextModeIndex = i + 1;break;}}if (tlsConfiguration == null) {// This may be the first time a connection has been attempted and the socket does not support// any the required protocols, or it may be a retry (but this socket supports fewer// protocols than was suggested by a prior socket).throw new UnknownServiceException("Unable to find acceptable protocols. isFallback=" + isFallback+ ", modes=" + connectionSpecs+ ", supported protocols=" + Arrays.toString(sslSocket.getEnabledProtocols()));}isFallbackPossible = isFallbackPossible(sslSocket);Internal.instance.apply(tlsConfiguration, sslSocket, isFallback);return tlsConfiguration;}

    這個(gè)過程分為如下的兩個(gè)步驟:

  • 從為OkHttp配置的ConnectionSpec集合中選擇一個(gè)與SSLSocket兼容的一個(gè)。SSLSocket與ConnectionSpec兼容的標(biāo)準(zhǔn)如下:

    public boolean isCompatible(SSLSocket socket) { if (!tls) {return false; }if (tlsVersions != null&& !nonEmptyIntersection(tlsVersions, socket.getEnabledProtocols())) {return false; }if (cipherSuites != null&& !nonEmptyIntersection(cipherSuites, socket.getEnabledCipherSuites())) {return false; }return true; }/** * An N*M intersection that terminates if any intersection is found. The sizes of both arguments * are assumed to be so small, and the likelihood of an intersection so great, that it is not * worth the CPU cost of sorting or the memory cost of hashing. */ private static boolean nonEmptyIntersection(String[] a, String[] b) { if (a == null || b == null || a.length == 0 || b.length == 0) {return false; } for (String toFind : a) {if (indexOf(b, toFind) != -1) {return true;} } return false; }

    即ConnectionSpec啟用的TLS版本及密碼套件,與SSLSocket啟用的有交集。
    2 將選擇的ConnectionSpec應(yīng)用在SSLSocket上。OkHttpClient中ConnectionSpec的應(yīng)用:

    @Overridepublic void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket, boolean isFallback) {tlsConfiguration.apply(sslSocket, isFallback);}

    而在ConnectionSpec中:

    /** Applies this spec to {@code sslSocket}. */ void apply(SSLSocket sslSocket, boolean isFallback) { ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);if (specToApply.tlsVersions != null) {sslSocket.setEnabledProtocols(specToApply.tlsVersions); } if (specToApply.cipherSuites != null) {sslSocket.setEnabledCipherSuites(specToApply.cipherSuites); } }/** * Returns a copy of this that omits cipher suites and TLS versions not enabled by {@code * sslSocket}. */ private ConnectionSpec supportedSpec(SSLSocket sslSocket, boolean isFallback) { String[] cipherSuitesIntersection = cipherSuites != null? intersect(String.class, cipherSuites, sslSocket.getEnabledCipherSuites()): sslSocket.getEnabledCipherSuites(); String[] tlsVersionsIntersection = tlsVersions != null? intersect(String.class, tlsVersions, sslSocket.getEnabledProtocols()): sslSocket.getEnabledProtocols();// In accordance with https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 // the SCSV cipher is added to signal that a protocol fallback has taken place. if (isFallback && indexOf(sslSocket.getSupportedCipherSuites(), "TLS_FALLBACK_SCSV") != -1) {cipherSuitesIntersection = concat(cipherSuitesIntersection, "TLS_FALLBACK_SCSV"); }return new Builder(this).cipherSuites(cipherSuitesIntersection).tlsVersions(tlsVersionsIntersection).build(); }

    主要是:

    • 求得ConnectionSpec啟用的TLS版本及密碼套件與SSLSocket啟用的TLS版本及密碼套件之間的交集,構(gòu)造新的ConnectionSpec。
    • 重新為SSLSocket設(shè)置啟用的TLS版本及密碼套件為上一步求得的交集。
  • 我們知道HTTP/2的協(xié)議協(xié)商主要是利用了TLS的ALPN擴(kuò)展來完成的。這里再來詳細(xì)的看一下配置TLS擴(kuò)展的過程。對于Android平臺而言,這部分邏輯在AndroidPlatform:

    @Override public void configureTlsExtensions(SSLSocket sslSocket, String hostname, List<Protocol> protocols) {// Enable SNI and session tickets.if (hostname != null) {setUseSessionTickets.invokeOptionalWithoutCheckedException(sslSocket, true);setHostname.invokeOptionalWithoutCheckedException(sslSocket, hostname);}// Enable ALPN.if (setAlpnProtocols != null && setAlpnProtocols.isSupported(sslSocket)) {Object[] parameters = {concatLengthPrefixed(protocols)};setAlpnProtocols.invokeWithoutCheckedException(sslSocket, parameters);}}

    TLS擴(kuò)展相關(guān)的方法不是SSLSocket接口的標(biāo)準(zhǔn)方法,不同的SSL/TLS實(shí)現(xiàn)庫對這些接口的支持程度不一樣,因而這里通過反射機(jī)制調(diào)用TLS擴(kuò)展相關(guān)的方法。

    這里主要配置了3個(gè)TLS擴(kuò)展,分別是session tickets,SNI和ALPN。session tickets用于會話回復(fù),SNI用于支持單個(gè)主機(jī)配置了多個(gè)域名的情況,ALPN則用于HTTP/2的協(xié)議協(xié)商。可以看到為SNI設(shè)置的hostname最終來源于Url,也就意味著使用HttpDns時(shí),如果直接將IP地址替換原來Url中的域名來發(fā)起HTTPS請求的話,SNI將是IP地址,這有可能使服務(wù)器下發(fā)不恰當(dāng)?shù)淖C書。

    TLS擴(kuò)展相關(guān)方法的OptionalMethod創(chuàng)建過程也在AndroidPlatform中:

    public AndroidPlatform(Class<?> sslParametersClass, OptionalMethod<Socket> setUseSessionTickets,OptionalMethod<Socket> setHostname, OptionalMethod<Socket> getAlpnSelectedProtocol,OptionalMethod<Socket> setAlpnProtocols) {this.sslParametersClass = sslParametersClass;this.setUseSessionTickets = setUseSessionTickets;this.setHostname = setHostname;this.getAlpnSelectedProtocol = getAlpnSelectedProtocol;this.setAlpnProtocols = setAlpnProtocols;}......public static Platform buildIfSupported() {// Attempt to find Android 2.3+ APIs.try {Class<?> sslParametersClass;try {sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl");} catch (ClassNotFoundException e) {// Older platform before being unbundled.sslParametersClass = Class.forName("org.apache.harmony.xnet.provider.jsse.SSLParametersImpl");}OptionalMethod<Socket> setUseSessionTickets = new OptionalMethod<>(null, "setUseSessionTickets", boolean.class);OptionalMethod<Socket> setHostname = new OptionalMethod<>(null, "setHostname", String.class);OptionalMethod<Socket> getAlpnSelectedProtocol = null;OptionalMethod<Socket> setAlpnProtocols = null;// Attempt to find Android 5.0+ APIs.try {Class.forName("android.net.Network"); // Arbitrary class added in Android 5.0.getAlpnSelectedProtocol = new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");setAlpnProtocols = new OptionalMethod<>(null, "setAlpnProtocols", byte[].class);} catch (ClassNotFoundException ignored) {}return new AndroidPlatform(sslParametersClass, setUseSessionTickets, setHostname,getAlpnSelectedProtocol, setAlpnProtocols);} catch (ClassNotFoundException ignored) {// This isn't an Android runtime.}return null;}

    建立TLS連接的第7步,獲取協(xié)議的過程與配置TLS的過程類似,同樣利用反射調(diào)用SSLSocket的方法,在AndroidPlatform中:

    @Override public String getSelectedProtocol(SSLSocket socket) {if (getAlpnSelectedProtocol == null) return null;if (!getAlpnSelectedProtocol.isSupported(socket)) return null;byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket);return alpnResult != null ? new String(alpnResult, Util.UTF_8) : null;}

    至此我們分析了OkHttp3中,所有HTTP請求,包括設(shè)置了代理的明文HTTP請求,設(shè)置了代理的HTTPS請求,設(shè)置了代理的HTTP/2請求,無代理的明文HTTP請求,無代理的HTTPS請求,無代理的HTTP/2請求的連接建立過程,其中包括TLS的握手,HTTP/2的協(xié)議協(xié)商等。

    總結(jié)一下,OkHttp中,IO相關(guān)的組件的其關(guān)系大體如下圖所示:


    Connection Component

    Done。

    總結(jié)

    以上是生活随笔為你收集整理的OkHttp3的连接池及连接建立过程分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    日日夜夜网| 中文字幕最新精品 | 国产精品自产拍在线观看中文 | 色综合久久网 | 日韩精品中文字幕在线播放 | 国产亚洲精品久久久久秋 | 日韩大片在线看 | 青草草在线 | 在线成人欧美 | 午夜婷婷在线播放 | 日韩在线播放欧美字幕 | 免费看的黄色 | 日韩中文字幕免费视频 | 成人午夜精品久久久久久久3d | 欧美视频二区 | 最近高清中文在线字幕在线观看 | 永久免费毛片 | 一区二区三区免费在线观看视频 | 天天搞天天 | 国产亚洲婷婷免费 | 欧洲成人av| av丝袜在线 | 国产精品国产自产拍高清av | 在线观看国产一区二区 | 日韩系列 | 在线观看深夜福利 | 4p变态网欧美系列 | 亚洲欧美视屏 | 国产精品热 | 麻豆久久久久久久 | 国产精品久久久久免费 | 91精品国自产在线 | 国产视频美女 | 国产精品青草综合久久久久99 | 国产精品第一视频 | 久久久精品一区二区三区 | 亚洲国产视频直播 | av不卡中文字幕 | 一区二区三区动漫 | 亚洲精品乱码久久久久久蜜桃不爽 | 免费看黄色91| 一区二区免费不卡在线 | 美女视频黄,久久 | 日韩欧美极品 | 嫩模bbw搡bbbb搡bbbb | 久草视频在线播放 | 成人影片免费 | 91色综合| 中文字幕第 | 一区二区电影网 | 中文字幕av网站 | 18岁免费看片 | 蜜桃av人人夜夜澡人人爽 | 色婷婷视频在线 | 日韩一区二区三区视频在线 | 人交video另类hd | 日韩高清一区在线 | 色欲综合视频天天天 | 国产手机免费视频 | 在线视频 一区二区 | 97超碰人人网 | 亚洲成人黄色在线 | 午夜的福利 | 色资源网免费观看视频 | 久久兔费看a级 | 亚洲精品国偷拍自产在线观看蜜桃 | 久久综合毛片 | 精品一区二区三区香蕉蜜桃 | 最新成人av| 国产一二区视频 | 国产香蕉视频在线播放 | 欧美一级片免费在线观看 | 欧洲色综合 | 99九九99九九九视频精品 | 久久深夜福利免费观看 | 久久色在线观看 | 91免费在线播放 | 久久视频在线视频 | 欧美成人播放 | 国产精品手机视频 | 俺要去色综合狠狠 | 欧美国产高清 | 欧美国产精品久久久久久免费 | 91探花国产综合在线精品 | 中文av网 | 正在播放国产一区 | 91麻豆精品国产91久久久久久久久 | 日日干视频 | 精品国产一区二区在线 | 婷婷在线精品视频 | 国产精品成人在线观看 | 久久高清国产 | 久草a在线| 草久热| 国产精彩在线视频 | 成人免费在线视频观看 | 天堂av在线7| 国产又粗又猛又黄视频 | 日韩影视精品 | 国产高清视频在线播放一区 | 天天激情| 欧美精品乱码久久久久 | 91九色porny蝌蚪视频 | 视频在线观看91 | 色橹橹欧美在线观看视频高清 | 国产午夜精品一区 | 亚洲国产成人av网 | 视频 天天草 | 97视频在线看 | 色综合激情久久 | 日韩免费一区二区在线观看 | 人人狠狠综合久久亚洲 | 亚洲综合色激情五月 | 九九av| 91精品视频播放 | 久久99最新地址 | 日韩一区二区三区视频在线 | 色哟哟国产精品 | 黄色app网站在线观看 | 日韩精品一区二区三区高清免费 | 色射爱| 蜜桃av观看 | 国产精品欧美久久久久天天影视 | 伊人欧美 | 999色视频 | 国产一线二线三线在线观看 | 亚洲成年人免费网站 | 亚洲精品国偷拍自产在线观看 | www免费网站在线观看 | 国产精品成人久久 | 欧美久久久久久久久 | av福利在线免费观看 | 一级免费黄视频 | 亚洲综合视频在线 | 欧美 日韩 视频 | 玖玖玖国产精品 | 丁香九月激情 | 国产女人18毛片水真多18精品 | 日韩在线观看小视频 | 日韩视频免费播放 | 毛片一区二区 | 精品国产欧美一区二区三区不卡 | 午夜体验区 | 久久视频这里只有精品 | 91毛片视频 | 久久综合九色欧美综合狠狠 | 成人亚洲免费 | 精品欧美日韩 | 美女网站视频一区 | 日韩网站在线 | 国产 日韩 欧美 自拍 | 国产视频 久久久 | 人人干人人草 | 国产成人精品电影久久久 | 国产福利精品在线观看 | 欧美日韩免费一区 | 综合色在线观看 | 香蕉精品视频在线观看 | 韩国av免费在线 | 国内精品久久久久影院优 | 久久一级电影 | 国产伦理久久精品久久久久_ | 91精品国产99久久久久久红楼 | 亚洲国产美女久久久久 | 天天射日| 亚洲视频播放 | 欧美色婷| 蜜桃久久久 | 美女视频黄是免费的 | 韩国av一区二区三区 | 国产91大片| 中文字幕乱码电影 | 久久9视频 | 国产高清在线a视频大全 | 天天爽夜夜爽精品视频婷婷 | 99视频在线免费播放 | 丁香5月婷婷 | 久久a久久 | 日韩在线三级 | 色999视频 | 国产精品久久久久久久久久ktv | 国产一区在线视频播放 | 久久久久电影网站 | 国产亚洲成人精品 | 久久99国产视频 | 成人av电影免费 | 亚欧日韩av| 婷婷婷国产在线视频 | av丝袜天堂 | 欧美日韩免费在线视频 | av电影在线观看 | 人人爽久久久噜噜噜电影 | 精品国产福利在线 | 亚洲 欧洲av | 久久y| 国产一级二级在线播放 | av再线观看| 视频三区 | 欧美激情视频一二三区 | 国产亚洲精品久 | 美女视频黄频 | 亚洲国产成人av网 | 日韩视频在线观看视频 | 精品视频在线视频 | 国产一级片免费观看 | 97成人资源站 | 国产毛片aaa | 综合天天色| 在线看污网站 | av电影免费在线播放 | 成人午夜电影网站 | 99精品在线免费在线观看 | 91香蕉视频黄色 | 色婷婷激情五月 | 色综合天天视频在线观看 | 一区二区视频播放 | 免费视频区 | 91av视频观看 | 99久久99久久精品 | 狠狠色丁香婷婷 | 欧美精品v国产精品 | 97视频资源 | 久青草国产在线 | 九九九热精品免费视频观看 | 69精品在线观看 | 免费激情网 | av黄色在线观看 | 最新av网站在线观看 | 丰满少妇在线观看资源站 | 日本性久久 | 啪嗒啪嗒免费观看完整版 | 国产小视频91| 六月丁香婷婷久久 | 九九热久久免费视频 | 99视频在线免费看 | 亚洲,国产成人av | 成人小电影在线看 | 97综合网 | 欧美va天堂va视频va在线 | 欧美日韩观看 | 婷婷亚洲五月 | 国产第一福利 | 免费在线看成人av | 免费看黄20分钟 | 国产精品亚州 | 在线观看资源 | 特级西西www44高清大胆图片 | 一区二区 久久 | 98精品国产自产在线观看 | 97超碰在线资源 | av电影免费在线播放 | 亚洲不卡av一区二区三区 | 欧美精品999 | 在线视频 国产 日韩 | 高潮久久久久久 | 日韩免费电影一区二区 | 最近高清中文在线字幕在线观看 | 狠狠狠色丁香综合久久天下网 | 欧美精品日韩 | 日韩在线免费小视频 | 欧美激情第十页 | 99精品亚洲 | 国产美女精品视频 | 91在线区 | 国产r级在线观看 | 日韩1页| 成av在线| 日韩精品不卡在线观看 | 99精品免费视频 | 在线天堂中文www视软件 | 久久99国产精品久久99 | 亚洲自拍av在线 | 中文字幕91视频 | 亚洲激情综合 | 日韩精品无码一区二区三区 | 国产中文在线视频 | www.五月天激情 | 五月黄色 | 久久精品91久久久久久再现 | 欧美久久久久久久 | 一区二区精品视频 | 丁香导航| 欧美日韩精品在线视频 | 国产精品美女久久久网av | 日韩特黄一级欧美毛片特黄 | 国产美女免费观看 | 黄色大全免费网站 | 国产传媒中文字幕 | 超碰精品在线 | 日韩成人在线一区二区 | 免费一区在线 | 精品在线免费观看 | 亚洲激情小视频 | 国产老太婆免费交性大片 | 久久久久久高潮国产精品视 | 91人人干| 亚洲波多野结衣 | 一区视频在线 | 日日夜夜免费精品视频 | 成人毛片a | 色a网 | 国产精品99久久久久久久久 | 久久国产精品系列 | 亚洲欧美日韩在线看 | 色欲综合视频天天天 | 国产成人一区二区三区 | 69视频国产| 999成人免费视频 | 黄色片免费电影 | 中文字幕在线免费播放 | 色亚洲网 | 国模一区二区三区四区 | 欧美久久影院 | 日本最新一区二区三区 | 国产一区福利在线 | 91欧美日韩国产 | 丁香视频五月 | 四虎免费av| 在线免费高清一区二区三区 | 一级性av | 一区二区三区久久精品 | 视频高清 | 久久激情综合网 | 久久国产免| 激情av资源 | 日韩最新理论电影 | 精品视频 | 免费精品 | 国产成人a亚洲精品v | 九精品| 亚洲人人网 | 三级性生活视频 | 视频一区二区在线 | 中文字幕视频网站 | 999在线视频 | 91看片在线免费观看 | 久久69av | 天天干天天干天天色 | 中文字幕在线观看第一区 | 91精品啪在线观看国产线免费 | 久久乱码卡一卡2卡三卡四 五月婷婷久 | 国产精品视频内 | 国产小视频国产精品 | 色射色 | 成人av高清| 久久精品国产亚洲精品 | 国产日韩欧美中文 | 天天天天天操 | 亚洲激情电影在线 | 天天天干天天射天天天操 | 999超碰| 精品国产免费一区二区三区五区 | 国产成人免费在线 | 91精品啪在线观看国产 | 久久精品这里热有精品 | 一区二区三区视频网站 | 成年人免费电影 | 国产精品高清一区二区三区 | 最近中文字幕在线中文高清版 | 91视频久久久 | 国产精品福利视频 | 四虎在线免费观看 | 一区二区精品在线观看 | 911亚洲精品第一 | 人成免费网站 | 中文字幕在线观 | 国产免费午夜 | 美女视频黄在线观看 | 欧美成年人在线观看 | 在线观看黄网站 | 日韩黄色在线 | 在线观看资源 | 亚洲第一中文网 | 国产精品久久久久久久久久三级 | 久草免费色站 | 国产成人高清在线 | 国产麻豆电影在线观看 | 久久久免费 | 久久夜色网| 国产福利资源 | 久久精彩| 亚洲国产久 | 91探花在线视频 | 九九国产视频 | 精品女同一区二区三区在线观看 | 欧美 高跟鞋交 xxxxhd | 精品国产一区二区三区久久久久久 | 精品在线免费观看 | 天天操天天操天天操 | 97伊人网| 91在线区 | 国产精品一区二区三区99 | a在线观看免费视频 | 视频在线观看亚洲 | 91在线资源 | 日韩av在线看 | 精品免费久久久久久 | 日韩精品一区二区三区电影 | 国产亚洲成人网 | 国产不卡在线播放 | 精品国产乱码久久久久久天美 | 日韩成人欧美 | 五月激情五月激情 | 色综合久久综合中文综合网 | 天天操天天爽天天干 | 免费看黄在线看 | 九九久久影视 | 久国产在线播放 | 日韩在线高清免费视频 | 久久久久欧美精品999 | 国产精品系列在线观看 | 欧美色综合天天久久综合精品 | 天堂麻豆| 久久精品一区二区三区中文字幕 | av中文字幕在线观看网站 | 中文视频在线播放 | 亚洲精品美女视频 | 麻豆高清免费国产一区 | 亚洲视频久久久 | 久久五月天色综合 | 精品国产乱码久久久久 | 国产综合精品久久 | 午夜性生活 | 国产在线中文字幕 | 99中文字幕 | 97色国产| 性色视频在线 | 国产精品久久久久久一二三四五 | 一级成人免费 | 一级做a爱片性色毛片www | 免费视频久久久 | 日本天天操 | www.黄色在线 | 樱空桃av | 亚洲丁香日韩 | 免费网址你懂的 | 天天干天天干天天射 | 久久久久成人精品 | 久久这里只有精品9 | 91精品在线视频 | 日韩精品短视频 | 日韩精品视频免费在线观看 | 91久久精品一区二区二区 | 91麻豆精品国产91久久久更新时间 | 日本夜夜草视频网站 | 777久久久 | 久久免费国产精品 | 国产原厂视频在线观看 | 西西www4444大胆视频 | 九九视频网 | 久草在线在线精品观看 | 日操干 | 黄色综合 | 免费观看av | 九九九免费视频 | 麻豆一二三精选视频 | 亚洲一区二区三区四区精品 | 成人毛片a| 黄色一区二区在线观看 | 天天天操操操 | 国内外成人在线视频 | 日本黄色大片免费 | 精品自拍sae8—视频 | 久久婷亚洲五月一区天天躁 | 欧美精品xxx | 手机在线观看国产精品 | 久草精品电影 | 国产玖玖在线 | 亚洲日本va中文字幕 | 91在线网址 | 美女精品久久 | 欧美xxxxx在线视频 | 国产一区观看 | 亚洲国产中文在线 | 亚洲精品高清一区二区三区四区 | 一区二区三区 中文字幕 | 亚洲综合成人专区片 | 一区二区三区福利 | 国产精品久久久久免费 | 欧美亚洲成人xxx | 国产精品视频永久免费播放 | 日韩一区视频在线 | 91黄在线看 | 中文字幕资源在线 | 亚洲成av片人久久久 | 天天干天天天 | 激情五月视频 | 久久人人爽人人 | 日韩精品五月天 | 免费91在线 | 日韩 国产 | 中文字幕在线播放一区 | 日韩视频一区二区三区在线播放免费观看 | 啪啪免费视频网站 | 在线观看www. | 久久手机精品视频 | 国产精品 日韩 欧美 | 成人av电影免费观看 | 国产精品久久久久久久久久久久冷 | 国产精品久久久久一区二区国产 | 婷婷丁香社区 | 亚洲高清在线精品 | 国产精品综合久久久久久 | 久久再线视频 | av资源网在线播放 | 国产99久久久欧美黑人 | 亚洲美女久久 | 色婷婷成人网 | 免费精品人在线二线三线 | 福利视频一二区 | 国产涩图 | 国产精品24小时在线观看 | 国产精品入口久久 | 99在线观看视频网站 | 日韩精品久久中文字幕 | 在线电影91 | 国产区精品区 | 亚洲国产精品久久久 | 丁香婷婷综合五月 | 中文字幕韩在线第一页 | 精品一区二区三区久久久 | 日韩欧美一区二区三区视频 | 国产精品国产自产拍高清av | 国产精品一区二区三区免费视频 | 久久美女免费视频 | 香蕉在线视频观看 | 欧美日韩久久不卡 | 免费视频18 | 最新日韩视频在线观看 | 丁香五月亚洲综合在线 | 嫩模bbw搡bbbb搡bbbb | 2019天天干天天色 | 中文字幕日韩国产 | 成人免费精品 | 天天插综合网 | 日本久久精 | 国产高清在线免费视频 | 91丨九色丨勾搭 | 免费日韩视 | 欧美一二三区播放 | 久久久国产日韩 | 国产精品中文字幕在线观看 | 久久精品国产精品亚洲精品 | 亚洲毛片视频 | 久久99久久精品 | 欧美精品乱码久久久久久按摩 | 久久99久久99精品免费看小说 | 成人免费观看视频大全 | 青青河边草免费 | 亚洲人成人在线 | 久久国产精品一区二区三区四区 | 99中文字幕在线观看 | 久久毛片视频 | 成年人视频在线免费 | 免费看片色| 91视频免费国产 | 在线观看免费中文字幕 | 国产成人777777 | 日韩免费视频一区二区 | 一级α片免费看 | 深爱激情综合 | 丁香婷婷激情五月 | 亚洲精品2区 | 性色大片在线观看 | 日韩在线视频免费观看 | 中文字幕在线观看视频一区二区三区 | 综合av在线 | 成人影片在线免费观看 | 久草国产视频 | 久久99国产精品久久99 | 国产手机在线观看视频 | 五月开心激情 | 国产在线视频不卡 | 最新真实国产在线视频 | 国产成人黄色av | 国产成人免费精品 | 国产成人在线免费观看 | 亚洲aaa毛片| 9色在线视频 | 91免费在线看片 | 日精品| 精品999在线 | 美女福利视频一区二区 | 日韩欧美视频一区 | 中文字幕第一页在线vr | 国产韩国日本高清视频 | 成年人视频在线 | 人人射av| 日韩中文字幕第一页 | 伊人成人久久 | se视频网址 | 一区二区三区视频 | 国产精品一区久久久久 | 久热国产视频 | 97超碰国产精品 | 久久草在线免费 | 黄色毛片在线观看 | 亚洲有 在线 | av中文字幕在线播放 | 在线黄色av电影 | 日韩a级黄色片 | 久久综合综合久久综合 | 国产在线欧美在线 | 精品国产欧美一区二区三区不卡 | 国产麻豆视频在线观看 | 在线观看日韩免费视频 | 狠狠操欧美 | 黄色片亚洲 | 国产午夜在线观看视频 | 久久黄色小说 | 热久久免费视频 | 国产高清日韩欧美 | 成人小视频在线播放 | 欧美成人影音 | 国产精品 亚洲精品 | 亚洲一区二区三区四区精品 | 久久国产经典视频 | 精品女同一区二区三区在线观看 | 欧美日韩二三区 | 成人精品一区二区三区中文字幕 | 亚洲第五色综合网 | 少妇bbb好爽 | 在线 欧美 日韩 | 91视频xxxx| 天天草夜夜 | 亚洲视频免费在线 | 久久涩涩网站 | 成x99人av在线www | 青青草国产成人99久久 | 欧美日韩精品综合 | 亚洲日本三级 | 欧美日韩国产一区二区在线观看 | av在线看片| 91成人短视频在线观看 | 成人国产网址 | 亚洲综合欧美日韩狠狠色 | 国产精品久久免费看 | 91中文字幕在线播放 | 黄色的片子 | 亚洲精品自拍视频在线观看 | 亚洲 成人 欧美 | 亚洲精品视频在线观看免费视频 | 2019中文最近的2019中文在线 | 国产成人一区二区三区免费看 | 麻豆成人在线观看 | 亚洲精品午夜久久久 | 色综合久久中文综合久久牛 | 欧美精品被| 最近av在线 | 久久久综合九色合综国产精品 | 精品久久国产 | 国产又粗又猛又黄 | 国产精品久久久久久久久久免费 | 日韩在线观看视频网站 | 国产精品久久久av久久久 | 久久在线免费观看视频 | 亚洲成人免费在线观看 | 99热这里精品 | 99在线高清视频在线播放 | 亚洲一区视频免费观看 | 91在线精品秘密一区二区 | 97在线视频免费观看 | 五月激情丁香图片 | 天天色综合久久 | 九九热re| 1024手机在线看 | 亚洲少妇自拍 | 日本激情动作片免费看 | 国产精品久久久久av免费 | 国产中文字幕91 | 黄色一级大片免费看 | 激情综合网色播五月 | 91视频免费看网站 | 91av电影在线| 色综合久久久久久久久五月 | 国产午夜不卡 | 在线性视频日韩欧美 | 麻豆精品在线视频 | 免费色视频在线 | 久久久精品一区二区 | 国产精品99久久免费观看 | 欧美极品xxx | 天天看天天干 | 精品国产91亚洲一区二区三区www | 亚洲手机av| 蜜臀av性久久久久蜜臀aⅴ四虎 | 91九色综合 | 探花视频免费观看 | 亚洲高清91 | 天天干天天做天天爱 | 色网站中文字幕 | 91成人黄色 | 五月综合久久 | 日韩精品高清视频 | 狂野欧美激情性xxxx欧美 | 天天做日日爱夜夜爽 | 成人亚洲精品国产www | 亚洲精品乱码久久久一二三 | 久久久久久国产一区二区三区 | www成人av| 国产在线精品区 | 97超视频在线观看 | 亚洲第一中文网 | 中文视频在线播放 | 色播五月婷婷 | 国产精品亚州 | 在线一区观看 | 久久中文欧美 | 国产中文字幕视频 | 91亚洲精品久久久中文字幕 | www.com.日本一级| 综合色综合 | 可以免费观看的av片 | 91香蕉视频黄 | 亚洲国产一区av | 亚洲三级av| 日韩欧美一区二区不卡 | 91精品黄色 | 精品国产一区二区三区久久久蜜臀 | 97在线视频免费观看 | 久久国产精品电影 | 日本精品视频在线观看 | 久草久视频| 91成品视频 | 久久这里只精品 | 欧美日本在线观看视频 | www.888.av | 久久久在线 | 久久久久久久久综合 | 91插插插网站 | 色婷婷导航 | 久久 精品一区 | 一区二区三区久久精品 | 99色在线 | 91精品在线播放 | 精品96久久久久久中文字幕无 | 免费看高清毛片 | 91亚洲精品国偷拍 | 国产精品久久久久一区二区国产 | 日韩欧美国产激情在线播放 | 中文国产在线观看 | 国产精品久久久久久久久久新婚 | 欧美天天干| 天天躁天天狠天天透 | 国产成人性色生活片 | 天天干天天射天天操 | 久久综合久久综合久久综合 | 欧美一区日韩一区 | 国产高清在线a视频大全 | 伊人久操| 久久资源总站 | 操天天操 | 亚洲区精品 | 国产在线精品福利 | 在线观看精品国产 | 色视频一区| 日本公妇在线观看高清 | 69视频在线| 91av在线播放视频 | 天天干天天爽 | 久久热首页 | 中文字幕区 | 国产一卡久久电影永久 | 欧美色图p | 日韩在线色 | 国产在线精品区 | 国产系列精品av | 免费三级a| 午夜狠狠操 | 亚洲欧美日韩中文在线 | 久久黄色网 | 国产成人精品午夜在线播放 | 日韩一区在线播放 | 久久一级片 | 国产精品免费久久久久 | 亚洲精品视频在线 | 欧美精品乱码久久久久 | 久久国产精品免费一区二区三区 | 天堂av在线网址 | 中文一区二区三区在线观看 | 天天干天天干天天色 | 国产精品久久久久免费 | 在线观看岛国av | 成人在线免费看视频 | 91网在线看 | 欧美乱码精品一区二区 | 99精品在线播放 | 国产午夜影院 | 亚洲少妇影院 | 97色资源| 婷婷综合久久 | 亚洲韩国一区二区三区 | 亚洲精品国久久99热 | 日韩区欠美精品av视频 | 88av网站 | 国产中文在线观看 | 国产一区视频免费在线观看 | av免费网站在线观看 | 91成年视频 | 国产精品欧美日韩 | 中文字幕一区二区三区久久蜜桃 | 免费精品视频在线 | 久久黄色免费视频 | 久久刺激视频 | www欧美xxxx | 国产精品一区二区精品视频免费看 | 国产一级久久 | 免费成人在线视频网站 | 天天操夜夜曰 | 97超碰人人澡 | 91免费版成人 | 欧美精品少妇xxxxx喷水 | 在线免费黄 | 欧美日韩国产欧美 | 日本一区二区三区视频在线播放 | 韩国精品一区二区三区六区色诱 | 亚洲人片在线观看 | 夜色资源站国产www在线视频 | 欧美极品一区二区三区 | 日韩欧美视频 | 亚洲精品免费观看视频 | 欧美日韩国产综合一区二区 | 日韩专区在线观看 | 亚洲色视频 | 日韩精品免费一线在线观看 | 456成人精品影院 | 亚洲欧美999 | 国内精品久久久 | 视频直播国产精品 | 久色伊人| 久久视频精品 | 91av电影网 | 一级一片免费看 | 国产专区在线视频 | 精品色综合 | 亚洲综合射 | 国产一区二区三区在线免费观看 | 久久久受www免费人成 | 久久精品视频5 | 亚洲日本va午夜在线电影 | 国产高清在线永久 | 久久久影视 | 青青网视频 | 国产网红在线观看 | 久久国产免费 | 在线观看久草 | 国产99久久久久久免费看 | 中文字幕乱在线伦视频中文字幕乱码在线 | 天天干天天干天天 | 久久黄色免费视频 | 国产精品毛片久久久久久久 | 99热精品国产一区二区在线观看 | 国产一区私人高清影院 | 五月婷婷,六月丁香 | 国产精品永久久久久久久www | 免费在线观看国产黄 | 成人午夜久久 | 久久久久国| 日韩欧美精品在线观看 | 亚洲经典中文字幕 | 免费看的av片 | 九九视频在线播放 | 国产成人av一区二区三区在线观看 | av黄色大片 | 日韩精品一区二区三区水蜜桃 | 一区二区视频在线观看免费 | 九色视频网址 | 久久久久久亚洲精品 | 在线观看免费一级片 | 亚洲一区网 | 又黄又爽又湿又无遮挡的在线视频 | 九九免费精品 | 国产打女人屁股调教97 | 色综合天天做天天爱 | 色91在线视频 | 免费大片av | 欧美专区日韩专区 | 亚洲综合丁香 | 欧美 日韩 久久 | 国产精品麻豆三级一区视频 | 欧美久久久久 | 一区二区视 | 96精品高清视频在线观看软件特色 | 成人久久影院 | 久要激情网 | 久久福利 | 亚洲h视频在线 | 色综合国产 | 91香蕉嫩草 | 91视频啊啊啊 | 国产精品久久麻豆 | 免费观看特级毛片 | 久久精品免费看 | 有码中文字幕 | 狠狠躁日日躁狂躁夜夜躁av | 日本爱爱片 | 久久线视频 | 欧美精品乱码久久久久久 | 天天天综合 | 亚洲欧美少妇 | 西西www444| 国产九九九视频 | 片网站| 一区二区三区四区在线 | 久久成人资源 | 国产精品久久在线观看 | 超碰99人人| 91最新视频 | 久久99亚洲精品 | 亚洲一区二区三区精品在线观看 | 亚洲三级黄色 | 日韩手机在线观看 | 久久老司机精品视频 | 91麻豆精品国产91久久久久久久久 | 国产精品久久久久aaaa | 国产专区欧美专区 | 91一区啪爱嗯打偷拍欧美 | 免费亚洲成人 | 久久久午夜精品福利内容 | 国产99久久久久久免费看 | www色综合 | 亚洲人成人在线 | 欧美激情精品久久久久久 | 久久婷婷一区二区三区 | 成人丝袜 | 欧美成人亚洲 | 久草在线免费资源站 | 波多野结衣电影一区二区 | av中文字幕不卡 | 国产一区二区观看 | 欧美日韩免费视频 | 黄色小视频在线观看免费 | 亚洲国产成人在线观看 | 五月天激情开心 | 成人国产精品免费 | 在线免费观看视频一区 | 国产色视频一区二区三区qq号 | 免费观看性生交 | 黄色一区二区在线观看 | 久章操| 日韩v欧美v日本v亚洲v国产v | 国产成人精品在线观看 | 婷婷六月综合亚洲 | 九九久久国产精品 | 久草精品视频在线看网站免费 | 亚洲免费黄色 | 在线色资源 | 久久久久久久久久久影视 | 一本大道久久精品懂色aⅴ 五月婷社区 | 99久久婷婷国产一区二区三区 | 探花视频免费观看 | 欧美一级专区免费大片 | 久久99精品久久久久久三级 | 精品国产大片 | 亚洲精品国产精品乱码在线观看 | 一区二区三区四区久久 | 欧美日韩中文国产一区发布 | 丁香六月中文字幕 | 青青草国产精品视频 | 91尤物在线播放 | 99国产免费网址 | 在线91播放 | 91亚洲精品国偷拍自产在线观看 | 久草精品在线播放 | 中文成人字幕 | 一级一级一片免费 | 五月婷av| 亚洲一级特黄 | av日韩精品 | 一级黄色片毛片 | 国产午夜精品免费一区二区三区视频 | 五月天天在线 | 日韩免费二区 | 中文字幕观看av | 亚洲精品视频在 | 一区二区三区免费播放 | 黄色av播放 | 久久精品久久99 | 在线亚洲精品 | 日本免费久久高清视频 | 中文字幕中文字幕在线中文字幕三区 | 开心激情五月网 | 久久夜色精品国产欧美乱极品 | 四虎成人精品在永久免费 | 成人av资源 | 最近免费中文字幕 | 午夜av剧场 | 久久国产网 | 日韩av一区二区三区在线观看 | 综合久久精品 | 午夜黄色影院 | 狠狠做深爱婷婷综合一区 | 91人人澡人人爽 | 成人国产精品电影 | 国产色拍| 日韩,中文字幕 | 99免费在线观看视频 | 国产91在|