前面我们已经分析了发送请求获取响应的主要流程,oKhttp通过调用链模式将不同的拦截器组合在一起,完成具体的请求的请求响应工作。此过程还有许多细节的地方,我们在前面并没有详细深入地去进行分析,如我们的连接是怎么管理的,数据是怎么解析的等等,此篇将分析通信过程中使用到的一些重要的类,它们是实现通过过程的基础。
StreamAllocation
首先明确三个概念:
- Connections:表示与远程服务建立的socket物理连接
- Streams:建立在底层连接基础之上的请求响应数据通信过程的抽象,在http1.x中,同一时间一个连接只能有一个stream与之对应,而http/2中,一个连接则可以同时对应多个stream。一个完整的请求或响应(称一个数据流stream,每个数据流都有一个独一无二的编号)可能会分成非连续多次发送。数据包发送的时候,都必须标记所属的数据流ID,用来区分它属于哪个数据流。
- Calls:客户端发起多个请求,多个stream的组合
用一张图来表示上面几个概念间的关系,图中描述了Http1.1和Http2的通信方式。HTTP 1.1 默认启用长TCP连接,但所有的请求-响应都是按序进行的(这里的长连接可理解成半双工协议。即便是HTTP1.1引入了管道机制,也是如此)。复用同一个TCP连接期间,即便是通过管道同时发送了多个请求,服务端也是按请求的顺序依次给出响应的;而客户端在未收到之前所发出所有请求的响应之前,将会阻塞后面的请求(排队等待),这称为"队头堵塞"(Head-of-line blocking)。HTTP/2复用TCP连接则不同,虽然依然遵循请求-响应模式,但客户端发送多个请求和服务端给出多个响应的顺序不受限制,这样既避免了"队头堵塞",又能更快获取响应。在复用同一个TCP连接时,服务器同时(或先后)收到了A、B两个请求,先回应A请求,但由于处理过程非常耗时,于是就发送A请求已经处理好的部分, 接着回应B请求,完成后,再发送A请求剩下的部分。HTTP/2长连接可以理解成全双工的协议。

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 {
//与远程请求地址通过socket建立连接,返回连接对象
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
RealConnection
此对象是完成连接的建立,其连接的建立是在connect()方法中完成的。代码就不贴了,没有什么难点,最后还是通过socket建立连接。
HttpCodec
这是一个接口,定义了操作请求(请求头,请求体)和解析响应(响应头,响应体)的方法,根据http的协议,其具体的实现类有Http1Codec和Http2Codec,分别对应Http1.x和Http/2协议。我们以Http1Codec实现类来分析,其依据状态流转,将发送请求获取响应分为以下几步:
private static final int STATE_IDLE = 0; // Idle connections are ready to write request headers.
private static final int STATE_OPEN_REQUEST_BODY = 1;
private static final int STATE_WRITING_REQUEST_BODY = 2;
private static final int STATE_READ_RESPONSE_HEADERS = 3;
private static final int STATE_OPEN_RESPONSE_BODY = 4;
private static final int STATE_READING_RESPONSE_BODY = 5;
private static final int STATE_CLOSED = 6;
此类要做的工作如下面流程描述,具体所做的工作就是写请求和读响应,虚线框表示如果没有,则可以跳过:

@Override public void writeRequestHeaders(Request request) throws IOException {
//获取请求行
String requestLine = RequestLine.get(
request, streamAllocation.connection().route().proxy().type());
//写请求行和请求头
writeRequest(request.headers(), requestLine);
}
public void writeRequest(Headers headers, String requestLine) throws IOException {
if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
//写请求行
sink.writeUtf8(requestLine).writeUtf8("\r\n");
//写请求头
for (int i = 0, size = headers.size(); i < size; i++) {
sink.writeUtf8(headers.name(i))
.writeUtf8(": ")
.writeUtf8(headers.value(i))
.writeUtf8("\r\n");
}
sink.writeUtf8("\r\n");
state = STATE_OPEN_REQUEST_BODY;
}
上述代码将请求头和请求体的内容写入到一个Sink的数据数据结构中,由此对象最后进行网络传输。okhttp底层的数据传输除了这个sink还有一个叫做source的对象,他们分别负责底层数据的发送和接收,是一个高效的数据传输对象,后面会重点介绍。 其余写请求体和读取响应的方法就不一一介绍了,看下源码就知道了。
总结
本结分析了okhttp中比较重要的几个类,他们是底层数据通信的基础,是细节的体现。RealConnection负责建立具体的连接,HttpCodec负责数据通信,StreamAllocation负责协调处理连接上的流处理。