OkHttp解析系列
OkHttp解析(一)从用法看清原理
OkHttp解析(二)网络连接
OkHttp解析(三)关于Okio
第一篇文章中从OkHttp的使用出发讲解了大部分源码思路,而OkHttp关于网络请求的访问则是在Interceptor拦截器中进行的
Socket管理(StreamAllocation)
对于StreamAllocation,第一篇文章中已经介绍过,也讲解了一点,但未详细。我们知道它是在 RetryAndFollowUpInterceptor
这个拦截器中创建的,并以此通过责任链传递给下一个拦截器
而它的使用则是在ConnectionInterceptor
拦截器中去与服务器建立连接。
更详细的说:建立TCP连接,处理SSL/TLS握手,完成HTTP2协商等过程在 ConnectInterceptor
中完成,具体是在StreamAllocation.newStream()
。
而向网络写数据,是在CallServerInterceptor
中。
要知道OkHttp底层并未使用HttpURLConnection进行网络请求,而是使用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相当于流分配器,用于协调连接、流和请求三者之间的关系。
对于它的成员变量
Address:描述某一个特定的服务器地址。
Route:表示连接的线路
ConnectionPool:连接池,所有连接的请求都保存在这里,内部由线程池维护
RouteSelector:线路选择器,用来选择线路和自动重连
RealConnection:用来连接到Socket链路
HttpStream:则是Http流,它是一个接口,实现类是Http1xStream、Http2xStream。分别对应HTTP/1.1、HTTP/2和SPDY协议
前面说了,StreamAllocation
是在RetryAndFollowUpInterceptor
创建,看下构造方法
public StreamAllocation(ConnectionPool connectionPool, Address address) {
this.connectionPool = connectionPool;
this.address = address;
this.routeSelector = new RouteSelector(address, routeDatabase());
}
这个连接池则是OkHttpClient
在Builder里面默认创建的,而Address
则是在RetryAndFollowUpInterceptor
中根据对应的URL创建出来
此外,这里还创建了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则是默认的路线数据库
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
则会创建一些默认的代理,默认值为Proxy.NO_PROXY
,接着复位nextProxyIndex = 0
准备进行代理服务器的连接。
在创建StreamAllocation
中,主要做了
根据对应了URL创建了指定的服务器地址
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);
}
这里首先会进行网络的请求判断,然后调用到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
方法中,则根据设置的超时时间来创建相应了连接RealConnection
,接着创建一个与RealConnection
绑定的HttpStream
,可能是Http2xStream
也可能是Http1xStream
。
可以说真正连接访问的是RealConnection
,前面在RouteSelector
中,我们已经确定好了对应的端口,地址,接下来就可以进行TCP连接了。我们看下RealConnection
的创建获取情况
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;
}
}
//根据指定路线创建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会创建RealConnection对象
创建出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
则是协议,如果是一开始连接,则protocol == null
,直到连接成功获取到对应的协议。
先判断当前线路是否有设置代理隧道,如有则调用buildTunneledConnection
没有则调用`buildConnection
private void buildTunneledConnection(int connectTimeout, int readTimeout, int writeTimeout,
ConnectionSpecSelector connectionSpecSelector) throws IOException {
//创建默认的隧道代理请求
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
进行创建Socket并建立套接字连接(完成三次握手),使得okio库与远程socket建立了I/O连接
接着调用createTunnel
创建代理隧道,在这里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方法中创建了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的创建则与Okio库建立了连接。
获取到输入输出流后,就可以在CallServerInterceptor
写入请求数据了。
流程图总结前面分析:
转载:https://www.jianshu.com/p/da071ad21b6d