OkHttp解析系列
OkHttp解析(一)從用法看清原理
OkHttp解析(二)網(wǎng)絡連接
OkHttp解析(三)關于Okio
第一篇文章中從OkHttp的使用出發(fā)講解了大部分源碼思路,而OkHttp關于網(wǎng)絡請求的訪問則是在Interceptor攔截器中進行的
Socket管理(StreamAllocation)
對于StreamAllocation,第一篇文章中已經(jīng)介紹過,也講解了一點,但未詳細。我們知道它是在 RetryAndFollowUpInterceptor
這個攔截器中創(chuàng)建的,并以此通過責任鏈傳遞給下一個攔截器
而它的使用則是在ConnectionInterceptor
攔截器中去與服務器建立連接。
更詳細的說:建立TCP連接,處理SSL/TLS握手,完成HTTP2協(xié)商等過程在 ConnectInterceptor
中完成,具體是在StreamAllocation.newStream()
。
而向網(wǎng)絡寫數(shù)據(jù),是在CallServerInterceptor
中。
要知道OkHttp底層并未使用HttpURLConnection進行網(wǎng)絡請求,而是使用Socket。
public final class StreamAllocation {
public final Address address;
private Route route;
private final ConnectionPool connectionPool;
// State guarded by connectionPool.
private final RouteSelector routeSelector;
private int refusedStreamCount;
private RealConnection connection;
private boolean released;
private boolean canceled;
private HttpStream stream;
...
}
StreamAllocation相當于流分配器,用于協(xié)調連接、流和請求三者之間的關系。
對于它的成員變量
Address:描述某一個特定的服務器地址。
Route:表示連接的線路
ConnectionPool:連接池,所有連接的請求都保存在這里,內部由線程池維護
RouteSelector:線路選擇器,用來選擇線路和自動重連
RealConnection:用來連接到Socket鏈路
HttpStream:則是Http流,它是一個接口,實現(xiàn)類是Http1xStream、Http2xStream。分別對應HTTP/1.1、HTTP/2和SPDY協(xié)議
前面說了,StreamAllocation
是在RetryAndFollowUpInterceptor
創(chuàng)建,看下構造方法
public StreamAllocation(ConnectionPool connectionPool, Address address) {
this.connectionPool = connectionPool;
this.address = address;
this.routeSelector = new RouteSelector(address, routeDatabase());
}
這個連接池則是OkHttpClient
在Builder里面默認創(chuàng)建的,而Address
則是在RetryAndFollowUpInterceptor
中根據(jù)對應的URL創(chuàng)建出來
此外,這里還創(chuàng)建了RouteSelector
public final class RouteSelector {
/*最近使用的路線,一條線路包括代理和Socket地址 */
private Proxy lastProxy;
private InetSocketAddress lastInetSocketAddress;
/*負責下一個代理的使用 */
private List<Proxy> proxies = Collections.emptyList();
private int nextProxyIndex;
/*負責下一個Socket地址的使用*/
private List<InetSocketAddress> inetSocketAddresses = Collections.emptyList();
private int nextInetSocketAddressIndex;
/* 失敗的線路集合 */
private final List<Route> postponedRoutes = new ArrayList<>();
...
public RouteSelector(Address address, RouteDatabase routeDatabase) {
this.address = address;
this.routeDatabase = routeDatabase;
resetNextProxy(address.url(), address.proxy());
}
}
RouteDatabase則是默認的路線數(shù)據(jù)庫
private void resetNextProxy(HttpUrl url, Proxy proxy) {
if (proxy != null) {
// If the user specifies a proxy, try that and only that.
proxies = Collections.singletonList(proxy);
} else {
// Try each of the ProxySelector choices until one connection succeeds. If none succeed
// then we'll try a direct connection below.
proxies = new ArrayList<>();
List<Proxy> selectedProxies = address.proxySelector().select(url.uri());
if (selectedProxies != null) proxies.addAll(selectedProxies);
// Finally try a direct connection. We only try it once!
proxies.removeAll(Collections.singleton(Proxy.NO_PROXY));
proxies.add(Proxy.NO_PROXY);
}
nextProxyIndex = 0;
}
如果用戶指定了代理,也就是proxy != null
,則賦值到proxies
中,表示確定了下一個代理的位置
如果沒有指定代理,proxy == null
則會創(chuàng)建一些默認的代理,默認值為Proxy.NO_PROXY
,接著復位nextProxyIndex = 0
準備進行代理服務器的連接。
在創(chuàng)建StreamAllocation
中,主要做了
根據(jù)對應了URL創(chuàng)建了指定的服務器地址
Address
設置了對應的代理,選定下一個代理服務器
接著到了StreamAllocation
的使用,對應在ConnectionInterceptor
@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");
HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpStream, connection);
}
這里首先會進行網(wǎng)絡的請求判斷,然后調用到streamAllocation.newStream
接著是streamAllocation.connection
public HttpStream 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);
HttpStream resultStream;
if (resultConnection.framedConnection != null) {
resultStream = new Http2xStream(client, this, resultConnection.framedConnection);
} else {
resultConnection.socket().setSoTimeout(readTimeout);
resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
resultStream = new Http1xStream(
client, this, resultConnection.source, resultConnection.sink);
}
synchronized (connectionPool) {
stream = resultStream;
return resultStream;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
在streamAllocation.newStream
方法中,則根據(jù)設置的超時時間來創(chuàng)建相應了連接RealConnection
,接著創(chuàng)建一個與RealConnection
綁定的HttpStream
,可能是Http2xStream
也可能是Http1xStream
。
可以說真正連接訪問的是RealConnection
,前面在RouteSelector
中,我們已經(jīng)確定好了對應的端口,地址,接下來就可以進行TCP連接了。我們看下RealConnection
的創(chuàng)建獲取情況
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
...
return candidate;
}
}
該方法用來找尋一個合適的連接
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
...
//默認連接
RealConnection allocatedConnection = this.connection;
...
//從連接池中獲取RealConnection
RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
if (pooledConnection != null) {
this.connection = pooledConnection;
return pooledConnection;
}
//如果連接池中獲取到,則獲取上次的路線
selectedRoute = route;
}
//如果連接池中沒有,則調用RouteSelector.next獲取對應的線路,該方法會遞歸調用next選擇合適的路線出來
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
synchronized (connectionPool) {
route = selectedRoute;
refusedStreamCount = 0;
}
}
//根據(jù)指定路線創(chuàng)建RealConnection
RealConnection newConnection = new RealConnection(selectedRoute);
acquire(newConnection);
...
//連接
newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(),
connectionRetryEnabled);
routeDatabase().connected(newConnection.route());
return newConnection;
}
可以看到,在streamAllocation.newStream
調用
RealConnection resultConnection = findHealthyConnection(...)
;做了幾件事
換取連接池中已有的路線
如果連接池中沒有,則調用RouteSelector.next獲取對應的線路,該方法會遞歸調用next選擇合適的路線出來
選擇出對應合適的路線后Route會創(chuàng)建RealConnection對象
創(chuàng)建出RealConnection對象后調用了
realConnection.connected
選擇線路后如何連接
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) {
if (protocol != null) throw new IllegalStateException("already connected");
...
while (protocol == null) {
try {
if (route.requiresTunnel()) {
buildTunneledConnection(connectTimeout, readTimeout, writeTimeout,
connectionSpecSelector);
} else {
buildConnection(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
}
}
...
}
}
在這里protocol
則是協(xié)議,如果是一開始連接,則protocol == null
,直到連接成功獲取到對應的協(xié)議。
先判斷當前線路是否有設置代理隧道,如有則調用buildTunneledConnection
沒有則調用`buildConnection
private void buildTunneledConnection(int connectTimeout, int readTimeout, int writeTimeout,
ConnectionSpecSelector connectionSpecSelector) throws IOException {
//創(chuàng)建默認的隧道代理請求
Request tunnelRequest = createTunnelRequest();
HttpUrl url = tunnelRequest.url();
int attemptedConnections = 0;
int maxAttempts = 21;
while (true) {
...
connectSocket(connectTimeout, readTimeout);
tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);
if (tunnelRequest == null) break; // Tunnel successfully created.
//關閉Socket連接,復位
closeQuietly(rawSocket);
rawSocket = null;
sink = null;
source = null;
}
establishProtocol(readTimeout, writeTimeout, connectionSpecSelector);
}
在這里先調用connectSocket
進行創(chuàng)建Socket并建立套接字連接(完成三次握手),使得okio庫與遠程socket建立了I/O連接
接著調用createTunnel
創(chuàng)建代理隧道,在這里HttpStream與Okio建立了I/O連接
當連接建立完畢后關閉Socket連接。
在connectSocket
方法中
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
rawSocket.setSoTimeout(readTimeout);
try {
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
throw new ConnectException("Failed to connect to " + route.socketAddress());
}
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
}
可以看到在connectSocket方法中創(chuàng)建了Socket,并且調用socket的連接。
Socket連接后則使用Okio庫與Socket進行I/O連接
private Request createTunnel(int readTimeout, int writeTimeout, Request tunnelRequest,
HttpUrl url) throws IOException {
// Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
String requestLine = "CONNECT " + Util.hostHeader(url, true) + " HTTP/1.1";
while (true) {
Http1xStream tunnelConnection = new Http1xStream(null, null, source, sink);
source.timeout().timeout(readTimeout, MILLISECONDS);
sink.timeout().timeout(writeTimeout, MILLISECONDS);
tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine);
tunnelConnection.finishRequest();
...
}
}
而在這里HttpStream的創(chuàng)建則與Okio庫建立了連接。
獲取到輸入輸出流后,就可以在CallServerInterceptor
寫入請求數(shù)據(jù)了。