1 网络的建立
1.1 获取可用的连接
网络的建立由ConnectInterceptor
拦截器完成,那么先看下该拦截器的实现:
// We need the network to satisfy this request. Possibly for validating a conditional GET.
// 顾名思义。如果不是GET的时候,那么就用doExtensiveHealthChecks来判断是否要进行额外的检查。
boolean doExtensiveHealthChecks = !request.method().equals("GET");
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
这里主要是调用newExchange
来创建一个Exchange对象,该对象用来进行网络数据的读取以及发送。下面看看transmitter是怎么创建一个Exchange的:
/** Returns a new exchange to carry a new request and response. */
//注释说的很明白:返回一个新的exchange用来传输一个新的请求以及相应。
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
...
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
synchronized (connectionPool) {
this.exchange = result;
this.exchangeRequestDone = false;
this.exchangeResponseDone = false;
return result;
}
}
可以见到主要是调用exchangeFinder.find
来寻找ExchangeCodec
,然后再由此创建Exchange对象。看看exchangeFinder
是怎么创建对象的:
接下来大概是find->findHealthyConnection->findConnection
这样的调用逻辑,那么最后看下findConnection
方法,该方法分为三个步骤。
1.寻找是否已经存在可用的连接
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
hasStreamFailure = false; // This is a fresh attempt.
// Attempt to use an already-allocated connection. We need to be careful here because our
// already-allocated connection may have been restricted from creating new exchanges.
// 尝试去使用已经分配的连接。由于已经分配的连接可能被限制创建新的exchanges,我们需要保持谨慎。
releasedConnection = transmitter.connection;
//如果transmitter的连接不能创建exchanges,那么释放对应的Socket。
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;//步骤1
if (transmitter.connection != null) {
// We had an already-allocated connection and it's good.
// 我们拥有一个已经分配的链接,并且是好的。
result = transmitter.connection;
releasedConnection = null;
}
if (result == null) {//步骤2
// Attempt to get a connection from the pool.
// 尝试从连接池中取出连接来处理
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
foundPooledConnection = true;
result = transmitter.connection;
} else if (nextRouteToTry != null) {
//如果线程池中没有合适的连接同时又有下一个可尝试的路由
selectedRoute = nextRouteToTry;
nextRouteToTry = null;
} else if (retryCurrentRoute()) {
//如果上面的条件都不满足,但是可以重新尝试连接路由
selectedRoute = transmitter.connection.route();
}
}
}
//关闭socket
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
//如果发现一个已经分配的线程。那么完成任务
return result;
}
步骤1:如果connection不为空且noNewExchanges
标记为true,那么调用releaseConnectionNoEvents
来释放连接并且返回将要关闭的socket。
步骤2:调用连接池的transmitterAcquirePooledConnection
方法获取可用连接,否则的话根据条件来获取selectedRoute
,该对象用于下面的操作。
2.获取下个路由,判断是否有跟该路由匹配的连接
// If we need a route selection, make one. This is a blocking operation.
// 如果我们需要路由选择器那么生产一个。这是阻塞操作。
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
List<Route> routes = null;
//对连接池同步操作
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
// 由于现在我们有了一系列的ip地址,再次尝试从连接池中获取连接。
routes = routeSelection.getAll();
if (connectionPool.transmitterAcquirePooledConnection(
address, transmitter, routes, false)) {
foundPooledConnection = true;
result = transmitter.connection;
}
}
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
// 创建一个链接并立刻给它分配内存。这使得它有个能被cancle()的同步方法中断我们将要进行的握手操作。
result = new RealConnection(connectionPool, selectedRoute);
connectingConnection = result;
}
}
// If we found a pooled connection on the 2nd time around, we're done.
// 如果第二轮的时候找到了连接,那么问题解决了。
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
首先是调用routeSelector.next()
来获取下一个routeSelection
,然后再利用routeSelection.getAll()
获取路由列表。最后根据该路由再调用transmitterAcquirePooledConnection
来获取可用的连接。也就是说第二次的话加了个路由的条件。
3.查看是否有可复用的连接(该情况只有http2
符合条件),否则创建一个新的连接
// Do TCP + TLS handshakes. This is a blocking operation.
// 实现 TCP + TLS 握手操作。这是一个阻塞操作。
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
connectionPool.routeDatabase.connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
connectingConnection = null;
// Last attempt at connection coalescing, which only occurs if we attempted multiple
// concurrent connections to the same host.
// 最后一次尝试寻找链接。这只适合多路复用的场景。
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
// We lost the race! Close the connection we created and return the pooled connection.
//关掉刚才刚刚创建的连接
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
// It's possible for us to obtain a coalesced connection that is immediately unhealthy. In
// that case we will retry the route we just successfully connected with.
// 刚刚连接的路由可能会立即失效了
nextRouteToTry = selectedRoute;
} else {
// 如果还是寻找失败的话,那么将刚才创建的连接放入线程迟池中。
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
此时,假如有多个线程跑到了这里,创建了多个连接。如果都是http2.0
的情况下,那么有可能是可以复用的。所以先用synchronized做同步操作,第三次判断是否有可用的连接(此时再加上一个条件:可复用)。如果找到的话,那么就将刚刚创建的连接关闭。
1.2 路由
路由和连接池都会结合上面的三次寻找连接为主线。
首先在第二次寻找连接之前需要寻找下个路由,这个会调用next()方法:
public Selection next() throws IOException {
if (!hasNext()) {
throw new NoSuchElementException();
}
// Compute the next set of routes to attempt.
List<Route> routes = new ArrayList<>();
while (hasNextProxy()) {
// Postponed routes are always tried last. For example, if we have 2 proxies and all the
// routes for proxy1 should be postponed, we'll move to proxy2. Only after we've exhausted
// all the good routes will we attempt the postponed routes.
Proxy proxy = nextProxy();
for (int i = 0, size = inetSocketAddresses.size(); i < size; i++) {
Route route = new Route(address, proxy, inetSocketAddresses.get(i));
if (routeDatabase.shouldPostpone(route)) {
postponedRoutes.add(route);
} else {
routes.add(route);
}
}
if (!routes.isEmpty()) {
break;
}
}
if (routes.isEmpty()) {
// We've exhausted all Proxies so fallback to the postponed routes.
routes.addAll(postponedRoutes);
postponedRoutes.clear();
}
return new Selection(routes);
}
shouldPostpone
表示先尝试最近失败的路由。这边的意思是先填充正常可用的连接,如果实在没有再去尝试使用最近失败的路由。
注意到创建路由的时候需要传入代理,那么看下Proxy到底是怎么一回事:
private Proxy nextProxy() throws IOException {
if (!hasNextProxy()) {
throw new SocketException("No route to " + address.url().host()
+ "; exhausted proxy configurations: " + proxies);
}
Proxy result = proxies.get(nextProxyIndex++);
resetNextInetSocketAddress(result);
return result;
}
获取Proxy代理并调用resetNextInetSocketAddress
来将相关信息填充入inetSocketAddresses
。而inetSocketAddresses
正是后面生成路由所需要的。
/**
* Prepares the socket addresses to attempt for the current proxy or host.
*/
private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
inetSocketAddresses = new ArrayList<>();
String socketHost;
int socketPort;
if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
// 直连和socket直接
socketHost = address.url().host();
socketPort = address.url().port();
} else {
SocketAddress proxyAddress = proxy.address();
if (!(proxyAddress instanceof InetSocketAddress)) {
throw new IllegalArgumentException(
"Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
}
InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
socketHost = getHostString(proxySocketAddress);
socketPort = proxySocketAddress.getPort();
}
if (socketPort < 1 || socketPort > 65535) {
throw new SocketException("No route to " + socketHost + ":" + socketPort
+ "; port is out of range");
}
if (proxy.type() == Proxy.Type.SOCKS) {
// 如果是SOCKS,那么不需要dns
inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
} else {
// 直连和代理都需要代理
eventListener.dnsStart(call, socketHost);
List<InetAddress> addresses = address.dns().lookup(socketHost);
if (addresses.isEmpty()) {
throw new UnknownHostException(address.dns() + " returned no addresses for " + socketHost);
}
eventListener.dnsEnd(call, socketHost, addresses);
for (int i = 0, size = addresses.size(); i < size; i++) {
InetAddress inetAddress = addresses.get(i);
inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
}
}
}
首先根据不同类型获取host和port,然后根据host和port来填充inetSocketAddresses
。
1.3 连接池
印象最深刻的是寻找连接的时候调用了三次transmitterAcquirePooledConnection
,接下来就看看里面是怎么实现的:
boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter,
@Nullable List<Route> routes, boolean requireMultiplexed) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (requireMultiplexed && !connection.isMultiplexed()) continue;//步骤1
if (!connection.isEligible(address, routes)) continue;//步骤2
transmitter.acquireConnectionNoEvents(connection);//步骤3
return true;
}
return false;
}
这边重点是做判断:步骤1用来检测是否需要复用以及是否是可复用的。步骤二主要是对地址,路由以及连接做一些匹配判断。如果这两个都通过的话,那么就开始分配连接了:
void acquireConnectionNoEvents(RealConnection connection) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
this.connection = connection;
connection.transmitters.add(new TransmitterReference(this, callStackTrace));
}
这个也就是真正开始取出连接了,接下来看看怎么取数据的。
在1.1中,在三次判断都无法匹配的情况下会创建一个连接并存入线程池:
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);//步骤1
}
connections.add(connection);//步骤2
}
步骤1是由线程池启动清除线程。正常情况下一旦启动清除任务之后,cleanupRunning
会一直为true。直到没有连接之后,退出任务。这种情况下,如果有新的连接进来,那么就要重新启动清除线程并将cleanupRunning
设置为true。
步骤2也就是将connection存入connections中。
先看下步骤1中的这个executor是怎么构建的:
/**
* 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<>(), Util.threadFactory("OkHttp ConnectionPool", true));
SynchronousQueue
是无界的,是一种无缓冲的等待队列。它保证了最多只有一个线程在运行。也就是说每个时候最多有一个清除线程在运行。60秒之后或者其他情况下运行的线程退出,才可以建立新线程。也就是有个线程池一直维持着同時間只能有一个线程,而且过期会干掉该线程。
然后看下该清除线程长什么样:
/** The maximum number of idle connections for each address. */
private final int maxIdleConnections;
private final long keepAliveDurationNs;
private final Runnable cleanupRunnable = () -> {
while (true) {
long waitNanos = cleanup(System.nanoTime());//步骤1
if (waitNanos == -1) return;//步骤2
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (RealConnectionPool.this) {
try {
RealConnectionPool.this.wait(waitMillis, (int) waitNanos);//步骤3
} catch (InterruptedException ignored) {
}
}
}
}
};
maxIdleConnections
和keepAliveDurationNs
分别是最大拦截时间以及保活时间,这两个用来决定接下来RealConnectionPool
的阻塞时间。
步骤1:根据条件返回当前清除线程需要阻塞的时间
步骤2:如果是-1的话,表示当前没有连接。清除线程也就没有存在的必要了。
步骤3:根据前面返回的时间开始对线程池阻塞。
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) {//步骤1
inUseConnectionCount++;
continue;
}
idleConnectionCount++;
// If the connection is ready to be evicted, we're done.
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {//步骤2
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {//步骤3
// 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) {//步骤4
// A connection will be ready to evict soon.
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {//步骤5
// All connections are in use. It'll be at least the keep alive duration 'til we run again.
return keepAliveDurationNs;
} else {//步骤6
// No connections, idle or in use.
cleanupRunning = false;
return -1;
}
}
closeQuietly(longestIdleConnection.socket());//步骤7
// Cleanup again immediately.
return 0;
}
步骤1:判断连接是否有在使用,如果有的话继续循环。这边用来统计空闲的连接
步骤2:判断连接中的空闲时间最长的Connection对象以及闲时间。
步骤3:如果满足空闲时间大于临界值或者空闲数量超过临界值,那么移除空闲时间最长的连接。
步骤4:如果有空闲的连接,那么返回被制定临界值减去之后剩余的时间。
步骤5:如果都在使用,则返回keepAliveDurationNs
。
步骤6:如果都不满足,则表示没有连接存在。
步骤7:跑到这边说明有连接被移除了,关闭对应的socket。return0
,马上再次cleanup。
上面介绍的是清除线程会去移除连接,还有一种方式是连接线程中也会主动去移除连接。看看上面第一次获取连接的代码:
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;
如果连接非空且noNewExchanges
就会调用releaseConnectionNoEvents
方法:
@Nullable Socket releaseConnectionNoEvents() {
assert (Thread.holdsLock(connectionPool));
//步骤1
int index = -1;
for (int i = 0, size = this.connection.transmitters.size(); i < size; i++) {
Reference<Transmitter> reference = this.connection.transmitters.get(i);
if (reference.get() == this) {
index = i;
break;
}
}
if (index == -1) throw new IllegalStateException();
//步骤2
RealConnection released = this.connection;
released.transmitters.remove(index);
this.connection = null;
//步骤3
if (released.transmitters.isEmpty()) {
released.idleAtNanos = System.nanoTime();
if (connectionPool.connectionBecameIdle(released)) {
return released.socket();
}
}
return null;
}
步骤1:找出当前Transmitter对象的索引号。
步骤2:将当前引用置成null,并将released指向该connection。
步骤3:这个比较重要。如果连接没有transmitter引用的话说明连接处于空闲状态,那么调用connectionBecameIdle
来通知线程池。
接下来看看connectionBecameIdle
方法:
boolean connectionBecameIdle(RealConnection connection) {
assert (Thread.holdsLock(this));
if (connection.noNewExchanges || maxIdleConnections == 0) {//步骤1
connections.remove(connection);
return true;
} else {//步骤2
notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
return false;
}
}
步骤1:如果noNewExchanges
或者不允许空闲连接,那么直接移除connection。
步骤2:此时有一个空闲的connection,但是不一定满足移除条件。那么就要开启清理线程了。
2 网络数据交互
2.1 HTTP/1.x
与HTTP/2的区别
首先介绍的是HTTP1.0
。这个最简单了就是一个连接操作完成才能执行下一个连接。每个连接的流程如下所示:
(图片转载自网络)但是这有很大的缺点。比如说建立连接要三次握手关闭连接要四次挥手。不断的创建销毁连接会浪费大量资源。
于是乎,Http1.1
引入了Keep-Alive
请求头,其中可以设置两个值:timeout与
max。他们分别用于限制连接的保持时间以及连接的最大值。
(图片转载自网络)上图可以看到遇到相同地址频繁请求的情况下Keep-Alive
明显减少了。
这边还有个问题。如果是Http1.0
,那么没传输完会阻塞在那边。那么如果像上面第二张图那么关闭之前的结束要怎么判断呢?
http1.1
又引入了Content-Length
和Transfer-Encoding:chunked
的概念
Content-Length:返回的数据是固定的,通过该字段客户端就可以确定自己需要接受的字节数。
Transfer-Encoding:chunked
:如果返回的数据是动态变动的,那么这个动态编码就派上用场了。
-
在 Header 中加入
Transfer-Encoding:chunked
,表示使用分块编码 -
每一个分块有两行。第一行表示的是长度,第二行表示的就是实际的数据了。
-
分块数据长度为0的时候表示实体结束了。
http1.1
虽然改进了很多,但还是不够好。比如说虽然说连接复用的问题解决了但是数据交互时的阻塞还是存在的。为此http2.0
引入了多路复用机制,并引入了数据流、数据帧以及数据帧等概念。下面图片直观的介绍了他们之间的区别(图片转载自网络):
2.2 网络数据交互的代码实现
先看看拦截器的代码:
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Exchange exchange = realChain.exchange();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
//写入请求头
exchange.writeRequestHeaders(request);
boolean responseHeadersStarted = false;
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// 如果请求头中包含 "Expect: 100-continue"
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
// 执行请求
exchange.flushRequest();
responseHeadersStarted = true;
exchange.responseHeadersStart();
// 读入响应头
responseBuilder = exchange.readResponseHeaders(true);
}
// responseBuilder == null表示服务端返回了100
if (responseBuilder == null)
// 这边可复用和不可复用的参数是有区别的,一个true一个false。
if (request.body().isDuplex()) {
// Prepare a duplex body so that the application can send a request body later.
exchange.flushRequest();
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, true));
request.body().writeTo(bufferedRequestBody);
} else {
// 写入请求体
// Write the request body if the "Expect: 100-continue" expectation was met.
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, false));
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
} else {
//没有满足 "Expect: 100-continue" ,请求发送结束
exchange.noRequestBody();
if (!exchange.connection().isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
// from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
exchange.noNewExchangesOnConnection();
}
}
} else {
//body ,请求发送结束
exchange.noRequestBody();
}
//如果请求体为空或者不可复用。那么结束发送
if (request.body() == null || !request.body().isDuplex()) {
exchange.finishRequest();
}
if (!responseHeadersStarted) {
exchange.responseHeadersStart();
}
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(false);
}
// 执行请求并获取响应
Response response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (code == 100) {
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
response = exchange.readResponseHeaders(false)
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
exchange.responseHeadersEnd(response);
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
// 读入响应的数据
response = response.newBuilder()
.body(exchange.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
exchange.noNewExchangesOnConnection();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
这里功能主要集中在五个方法中:
-
exchange.writeRequestHeaders
写入请求头 -
exchange.createRequestBody
获取Sink
-
ResponseBody.writeTo
写入请求体 -
exchange.readResponseHeaders
读入响应头 -
exchange.openResponseBody
方法读取响应体
不同的协议有不同的实现方法,下面将分别介绍:
2.2.1 HTTP/1.x
的逻辑流程
2.2.1.1 writeRequestHeaders
方法
先看下代码:
@Override public void writeRequestHeaders(Request request) throws IOException {
String requestLine = RequestLine.get(
request, realConnection.route().proxy().type());//步骤1
writeRequest(request.headers(), requestLine);//步骤2
}
步骤1:封装请求中的第一行,包括请求的 method、url、HTTP
版本等信息。
步骤2:往请求头里面填写数据:
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;
}
按照key:value
填充数据,最后一行直接写入"\r\n"。
2.2.1.2 createRequestBody
@Override public Sink createRequestBody(Request request, long contentLength) throws IOException {
if (request.body() != null && request.body().isDuplex()) {
throw new ProtocolException("Duplex connections are not supported for HTTP/1");
}
if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
// Stream a request body of unknown length.
return newChunkedSink();
}
if (contentLength != -1L) {
// Stream a request body of a known length.
return newKnownLengthSink();
}
throw new IllegalStateException(
"Cannot stream a request body without chunked encoding or a known content length!");
}
如果长度未知的情况下返回newChunkedSink
,长度已知的情况下返回newKnownLengthSink
。分别看下这两个有啥区别:
private Sink newChunkedSink() {
if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
state = STATE_WRITING_REQUEST_BODY;
return new ChunkedSink();
}
@Override
public void write(Buffer source, long byteCount) throws IOException {
if (closed) throw new IllegalStateException("closed");
if (byteCount == 0) return;
sink.writeHexadecimalUnsignedLong(byteCount);
sink.writeUtf8("\r\n");
sink.write(source, byteCount);
sink.writeUtf8("\r\n");
}
上面没什么特殊之处,也就是按照http1.1
规定的来写入数据。两行第一行是长度,第二行是数据。
private Sink newKnownLengthSink() {
if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
state = STATE_WRITING_REQUEST_BODY;
return new KnownLengthSink();
}
@Override
public void write(Buffer source, long byteCount) throws IOException {
if (closed) throw new IllegalStateException("closed");
checkOffsetAndCount(source.size(), 0, byteCount);
sink.write(source, byteCount);
}
如果是已知长度的那就更简单了,直接写入数据。
2.2.1.3 readResponseHeaders
上代码:
@Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
...
try {
StatusLine statusLine = StatusLine.parse(readHeaderLine());//步骤1
Response.Builder responseBuilder = new Response.Builder()//步骤2
.protocol(statusLine.protocol)
.code(statusLine.code)
.message(statusLine.message)
.headers(readHeaders());
...
} catch (EOFException e) {
...
}
}
重点就是步骤1和步骤2
步骤1:调用readHeaderLine()
解析出第一行数据,里面包括了StatusLine
对象所需要的protocol、code以及message。
步骤2:填充protocol、code以及message并填充剩余的响应头内容。
2.2.1.4 openResponseBodySource
@Override public Source openResponseBodySource(Response response) {
if (!HttpHeaders.hasBody(response)) {
return newFixedLengthSource(0);
}
if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
return newChunkedSource(response.request().url());
}
long contentLength = HttpHeaders.contentLength(response);
if (contentLength != -1) {
return newFixedLengthSource(contentLength);
}
return newUnknownLengthSource();
}
跟构建请求体一样,这边也是分为固定内容长度和未知内容长度两种情况。
private Source newFixedLengthSource(long length) {
if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
state = STATE_READING_RESPONSE_BODY;
return new FixedLengthSource(length);
}
//FixedLengthSource的read方法
@Override
public long read(Buffer sink, long byteCount) throws IOException {
...
long read = super.read(sink, Math.min(bytesRemaining, byteCount));
...
return read;
}
以上是newFixedLengthSource
的代码。就是从sink中读取byteCount
长度的数据。这个byteCount
就是请求头里指定的contentLength
。
private Source newChunkedSource(HttpUrl url) {
if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
state = STATE_READING_RESPONSE_BODY;
return new ChunkedSource(url);
}
@Override
public long read(Buffer sink, long byteCount) throws IOException {
...
if (bytesRemainingInChunk == 0 || bytesRemainingInChunk == NO_CHUNK_YET) {
readChunkSize();
if (!hasMoreChunks) return -1;
}
long read = super.read(sink, Math.min(byteCount, bytesRemainingInChunk));
...
return read;
}
private void readChunkSize() throws IOException {
// Read the suffix of the previous chunk.
...
try {
bytesRemainingInChunk = source.readHexadecimalUnsignedLong();
...
} catch (NumberFormatException e) {
throw new ProtocolException(e.getMessage());
}
...
}
以上是newChunkedSource
的代码。这边和newFixedLengthSource
的区别是会调用readChunkSize
来确定读取的长度而不是固定的contentLength
。
而readChunkSize
也只是正常的读取数据。前面说过http1.1
的chunk第一次发过来的正是内容的长度,所以这边读到的就是数据内容的长度了。
2.2.2 HTTP/2的逻辑流程
2.2.2.1 exchange.writeRequestHeaders
写入请求头
上代码:
@Override
public void writeRequestHeaders(Request request) throws IOException {
if (stream != null) return;
boolean hasRequestBody = request.body() != null;
List<Header> requestHeaders = http2HeadersList(request);//步骤1
stream = connection.newStream(requestHeaders, hasRequestBody);//步骤2
。。。
}
步骤1:解析出请求头并生成list列表。该列表中包括了方法,url
路径等各种必要元素。
步骤2:根据获取的requestHeaders以及是否有请求体来创建流。
public Http2Stream newStream(List<Header> requestHeaders, boolean out) throws IOException {
return newStream(0, requestHeaders, out);
}
private Http2Stream newStream(
int associatedStreamId, List<Header> requestHeaders, boolean out) throws IOException {
boolean outFinished = !out;
boolean inFinished = false;
boolean flushHeaders;
Http2Stream stream;
int streamId;
synchronized (writer) {
synchronized (this) {
if (nextStreamId > Integer.MAX_VALUE / 2) {
shutdown(REFUSED_STREAM);
}
if (shutdown) {
throw new ConnectionShutdownException();
}
streamId = nextStreamId;//步骤1
nextStreamId += 2;
stream = new Http2Stream(streamId, this, outFinished, inFinished, null);
flushHeaders = !out || bytesLeftInWriteWindow == 0L || stream.bytesLeftInWriteWindow == 0L;
if (stream.isOpen()) {
streams.put(streamId, stream);//步骤2
}
}
if (associatedStreamId == 0) {
writer.headers(outFinished, streamId, requestHeaders);//步骤3
}
...
}
if (flushHeaders) {
writer.flush();
}
return stream;
}
步骤1:获取下一个StreamId
并自加2,这个用来接受数据的时候判断来源。
步骤2:将streamId, stream
对存入streams
步骤3:将requestHeaders
写入sink。
2.2.2.2 exchange.createRequestBody
获取 Sink
@Override
public Sink createRequestBody(Request request, long contentLength) {
return stream.getSink();
}
createRequestBody
方法是获取sink对象,他属于类Http2Stream
。下面看看这个sink写入数据是什么个逻辑:
@Override
public void write(Buffer source, long byteCount) throws IOException {
assert (!Thread.holdsLock(Http2Stream.this));
sendBuffer.write(source, byteCount);
while (sendBuffer.size() >= EMIT_BUFFER_SIZE) {
emitFrame(false);
}
}
private void emitFrame(boolean outFinishedOnLastFrame) throws IOException {
long toWrite;
synchronized (Http2Stream.this) {
writeTimeout.enter();
try {
while (bytesLeftInWriteWindow <= 0 && !finished && !closed && errorCode == null) {
waitForIo(); //步骤1
}
} finally {
writeTimeout.exitAndThrowIfTimedOut();
}
checkOutNotClosed(); // Kick out if the stream was reset or closed while waiting.
toWrite = Math.min(bytesLeftInWriteWindow, sendBuffer.size());
bytesLeftInWriteWindow -= toWrite;
}
writeTimeout.enter();
try {
boolean outFinished = outFinishedOnLastFrame && toWrite == sendBuffer.size();
connection.writeData(id, outFinished, sendBuffer, toWrite);//步骤2
} finally {
writeTimeout.exitAndThrowIfTimedOut();
}
}
逻辑主要集中在emitFrame
这个方法中。主要有两个步骤:
步骤1:waitForIo()
,如果bytesLeftInWriteWindow
小于0也就是说写入窗口没剩余了,那么就算阻塞在那边等待缓存中数据刷入网络。
步骤2:往连接写入数据:
public void writeData(int streamId, boolean outFinished, Buffer buffer, long byteCount)
throws IOException {
...
while (byteCount > 0) {
int toWrite;
synchronized (Http2Connection.this) {
try {
while (bytesLeftInWriteWindow <= 0) {
...
Http2Connection.this.wait(); // Wait until we receive a WINDOW_UPDATE.
}
}
...
}
byteCount -= toWrite;
writer.data(outFinished && byteCount == 0, streamId, buffer, toWrite);
}
}
public synchronized void data(boolean outFinished, int streamId, Buffer source, int byteCount)
throws IOException {
if (closed) throw new IOException("closed");
byte flags = FLAG_NONE;
if (outFinished) flags |= FLAG_END_STREAM;
dataFrame(streamId, flags, source, byteCount);
}
void dataFrame(int streamId, byte flags, Buffer buffer, int byteCount) throws IOException {
byte type = TYPE_DATA;
frameHeader(streamId, byteCount, type, flags);
if (byteCount > 0) {
sink.write(buffer, byteCount);
}
}
写入的流程大概是writeData->data->dataFrame
。
先说说writeData
,先是判断下bytesLeftInWriteWindow
,没剩余空间的阻塞。如果有剩余空间那么最终会跑到dataFrame
方法中。
在dataFrame
中,第一步是写入帧头,第二步写入数据。
2.2.2.3 exchange.readResponseHeaders
读入响应头
@Override
public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
Headers headers = stream.takeHeaders();//步骤1
Response.Builder responseBuilder = readHttp2HeadersList(headers, protocol);//步骤2
if (expectContinue && Internal.instance.code(responseBuilder) == HTTP_CONTINUE) {
return null;
}
return responseBuilder;
}
步骤1:从流中获取响应头的数据。
步骤2:解析响应头数据并拼接成Response.Builder
对象。
看看是怎么获取响应头的数据:
public synchronized Headers takeHeaders() throws IOException {
readTimeout.enter();
try {
while (headersQueue.isEmpty() && errorCode == null) {
waitForIo();
}
} finally {
readTimeout.exitAndThrowIfTimedOut();
}
if (!headersQueue.isEmpty()) {
return headersQueue.removeFirst();
}
throw errorException != null ? errorException : new StreamResetException(errorCode);
}
可以看到一个关键的对象headersQueue
,他是一个队列。这个函数的逻辑大概就是等待如果headersQueue如果有数据的情况下,取出第一个元素。
那么readResponseHeaders
的总体逻辑就是从队列中取出响应头,并且组装成Response.Builder
对象。
很明显这是一个生产者消费者模式。博客最后下headersQueue
到底是由哪个生产者生产出来的。
2.2.2.4 exchange.openResponseBody
方法读取响应体
@Override
public Source openResponseBodySource(Response response) {
return stream.getSource();
}
openResponseBodySource
是获取stream中的 FramingSource
对象。那么看看该对象的是怎么read数据的:
@Override
public long read(Buffer sink, long byteCount) throws IOException {
...
while (true) {
long readBytesDelivered = -1;
IOException errorExceptionToDeliver = null;
synchronized (Http2Stream.this) {
readTimeout.enter();
try {
...
if (closed) {
throw new IOException("stream closed");
} else if (readBuffer.size() > 0) {
readBytesDelivered = readBuffer.read(sink, Math.min(byteCount, readBuffer.size()));//步骤1
...
} else if (!finished && errorExceptionToDeliver == null) {
waitForIo();
continue;
}
} finally {
readTimeout.exitAndThrowIfTimedOut();
}
}
if (readBytesDelivered != -1) {
updateConnectionFlowControl(readBytesDelivered);
return readBytesDelivered;
}
...
return -1; // This source is exhausted.
}
}
所以无异常的流程就是开个死循环一直从readBuffer
读取数据。
2.2.2.5 readerRunnable
生产者任务
无论是响应头或者响应内容都是由readerRunnable
中不停的获取得到的。readerRunnable
调用流程如下:
RealConnection.connect -> establishProtocol -> startHttp2 -> http2Connection.start() ->new Thread(readerRunnable).start()
也就是说readerRunnable
在连接创建的时候就开始了!
看看readerRunnable
的execute
里面都做了什么:
@Override protected void execute() {
ErrorCode connectionErrorCode = ErrorCode.INTERNAL_ERROR;
ErrorCode streamErrorCode = ErrorCode.INTERNAL_ERROR;
IOException errorException = null;
try {
reader.readConnectionPreface(this);
while (reader.nextFrame(false, this)) {
}
connectionErrorCode = ErrorCode.NO_ERROR;
streamErrorCode = ErrorCode.CANCEL;
}
...
}
可以看到这里主要是一直在循环执行nextFrame
方法,看看nextFrame
方法具体是怎么个逻辑:
public boolean nextFrame(boolean requireSettings, Handler handler) throws IOException {
....
switch (type) {
case TYPE_DATA:
readData(handler, length, flags, streamId);
break;
case TYPE_HEADERS:
readHeaders(handler, length, flags, streamId);
break;
...
}
return true;
}
咱们重点看readData
以及readHeaders
方法。对!这就是实际获取响应头和响应内容的方法。也就是说连接建立的时候就会创建一个线程死循环在读取响应头和响应内容。