Cronet是什么?
From chatgpt
Cronet(Chromium Network Stack)是Google Chrome浏览器的网络堆栈的开源版本,用于处理网络请求和响应。它是一个用于Android平台的网络库,提供了高性能的网络功能,包括HTTP请求、数据下载、WebSocket支持等。Cronet的目标是为Android应用程序提供高效、可靠和安全的网络通信能力。
以下是Cronet的一些特点和功能:
-
性能优化: Cronet旨在提供高性能的网络通信,通过多线程处理和异步操作来最大程度地减少网络延迟。
-
HTTP/2支持: Cronet支持HTTP/2,这是一种现代的、高效的HTTP协议,可以加快页面加载速度并减少网络资源的浪费。
-
安全性: Cronet支持HTTPS,可以确保数据在传输过程中的加密和安全性。
-
Quic支持: Cronet还支持QUIC协议,这是Google开发的一种基于UDP的传输协议,旨在提供更低的延迟和更高的性能。
-
WebSocket支持: Cronet可以用于处理WebSocket连接,使应用程序可以进行实时双向通信。
-
缓存管理: Cronet可以自动管理缓存,以提高资源的重复利用率,并减少不必要的网络请求。
-
可配置性: Cronet允许开发人员配置各种网络参数,以满足应用程序的需求。
Cronet最初是为Chrome浏览器的Android版本开发的,但后来被开源并提供给其他Android应用程序开发人员使用。它为开发人员提供了更多灵活性和控制,使他们能够更好地管理应用程序的网络通信,同时受益于Google Chrome团队的网络优化经验。
需要注意的是,Cronet在不同版本中可能会有一些变化和改进,因此在使用时应查阅官方文档以获取最新的信息和指南。
Chromium-net是什么?
From chatgpt
Chromium Net(简称Chromium网络)是Chromium项目的一部分,用于处理网络通信的底层组件。Chromium是一个开源项目,它是Google Chrome浏览器的基础,因此Chromium Net在Chrome浏览器中用于处理网络请求和响应。
以下是Chromium Net的一些主要特点和功能:
-
网络协议支持: Chromium Net支持各种网络协议,包括HTTP、HTTPS、HTTP/2、QUIC(Quick UDP Internet Connections)等。这使得Chrome浏览器能够与Web服务器进行高效的通信,并利用现代网络协议提供更快的页面加载速度和更好的性能。
-
网络安全性: Chromium Net负责处理HTTPS通信,确保通过SSL/TLS加密传输的数据的安全性。它还实施了一系列的安全策略和机制,用于保护用户免受网络攻击和恶意网站的威胁。
-
代理支持: 该组件允许配置代理服务器,以允许网络请求通过代理服务器进行中转,从而实现更高级的网络控制和隐私保护。
-
流量管理: Chromium Net可以帮助浏览器管理网络流量,包括请求和响应的缓存、重定向、流量控制等。
-
数据压缩: 它支持数据压缩,以减少传输的数据量,提高网络性能。
-
HTTP缓存: Chromium Net包括HTTP缓存管理,以在本地存储和管理已下载的资源,以减少重复下载和提高页面加载速度。
Chromium Net是Chromium项目中的一个关键组件,它使得Chrome浏览器能够在各种网络条件下提供出色的性能和安全性。除了在Chrome浏览器中使用外,Chromium Net的一些部分也可以在其他项目中使用,以处理网络通信。它的开源性质使得开发人员可以查看其源代码并进行自定义,以满足特定需求。需要注意的是,Chromium Net是一个复杂的网络堆栈,需要深入的专业知识来理解和配置。
如何使用Cronet发起请求
Cronet提供两种发起请求的方式,异步和同步。
-
异步
val requestBuilder = cronetEngine.newUrlRequestBuilder(url, callback, executorService) requestBuilder.build().start()
-
同步
val connection = engine.openConnection(url) URL.setURLStreamHandlerFactory(engine.createURLStreamHandlerFactory())
跟代码
-
JAVA 层
本文将跟着异步调用的方式跟着代码看网络请求大致流程,所以我们跟代码的入口是:
CronetUrlRequest.start()
//CronetUrlRequest.java
@Override
public void start() {
synchronized (mUrlRequestAdapterLock) {
...
startInternalLocked();
}
}
private void startInternalLocked() {
CronetUrlRequestJni.get().start(mUrlRequestAdapter, CronetUrlRequest.this);
}
//CronetUrlRequestJni.class
public void start(long nativePtr, CronetUrlRequest caller) {
GEN_JNI.org_chromium_net_impl_CronetUrlRequest_start(nativePtr, caller);
}
-
cronet_url_request.cc
从上面的流程可以看到,java 层就是纯粹的包装层,C++层的入口是 cronet_url_request.cc#start(),那我们直接去看这个文件。
//cronet_url_request.cc
void CronetURLRequest::Start() {
DCHECK(!context_->IsOnNetworkThread());
context_->PostTaskToNetworkThread(
FROM_HERE,
base::BindOnce(&CronetURLRequest::NetworkTasks::Start,
base::Unretained(&network_tasks_),
base::Unretained(context_), initial_method_,
std::move(initial_request_headers_), std::move(upload_)));
}
实际上逻辑是在 NetworkTasks 中
//CronetRequest::NetworkTasks
void CronetURLRequest::NetworkTasks::Start(
CronetURLRequestContext* context,
const std::string& method,
std::unique_ptr<net::HttpRequestHeaders> request_headers,
std::unique_ptr<net::UploadDataStream> upload) {
url_request_ = context->GetURLRequestContext()->CreateRequest(
initial_url_, net::DEFAULT_PRIORITY, this, MISSING_TRAFFIC_ANNOTATION);
//... 设置一堆请求头、优先级之类的东西
url_request_->set_method(method);
//...
url_request_->Start();
}
从上面这个函数体来看,cronet 的 C++ 层代码跑到这里基本流程就结束了,后续流程嫁接进 chromuim-net 模块了,先通过 url_request_context 构造 url_request,再调用 url_request 的 Start() 方法发起请求。
-
net/url_request/url_request_context.cc
构造 url_request
//net/url_request/url_request_context.cc
std::unique_ptr<URLRequest> URLRequestContext::CreateRequest(
const GURL& url,
RequestPriority priority,
URLRequest::Delegate* delegate,
NetworkTrafficAnnotationTag traffic_annotation,
bool is_for_websockets) const {
return base::WrapUnique(new URLRequest(
url, priority, delegate, this, traffic_annotation, is_for_websockets));
}
-
net/url_request/url_request.cc
调用 url_request 的 Start() 函数进入 chromium-net 的核心网络请求流程。
//net/url_request/url_request.cc
void URLRequest::Start() {
//... 检查一堆东西
load_timing_info_ = LoadTimingInfo();
load_timing_info_.request_start_time = response_info_.request_time;
load_timing_info_.request_start = base::TimeTicks::Now();
//... 省去network_delegate逻辑
StartJob(context_->job_factory()->CreateJob(this));
}
StartJob(context_->job_factory()->CreateJob(this))
这一句核心代码包含两个元素,一个是通过 URLRequestJobFactory 构造一个 URLRequestJob,然后 startJob,这两个类所在的文件是:
-
net/url_request/url_request_job_factory.h
-
net/url_request/url_request_job.h
在继续往底下深入看之前,用我们自己的思路大概猜一下,URLRequestJob 是单个网络请求任务的包装类,内部包含着各个阶段的流转,而 URLRequest 是 job 的外观类。我们继续看 StartJob 函数:
//net/url_request/url_request.cc
void URLRequest::StartJob(std::unique_ptr<URLRequestJob> job) {
//跟上面一样的,向 job 设置一堆东西,同时设置进去一堆 callback
job_ = std::move(job);
job_->SetExtraRequestHeaders(extra_request_headers_);
job_->SetPriority(priority_);
job_->SetRequestHeadersCallback(request_headers_callback_);
job_->SetEarlyResponseHeadersCallback(early_response_headers_callback_);
job_->SetResponseHeadersCallback(response_headers_callback_);
//... 省略
// Start() always completes asynchronously.
//
// Status is generally set by URLRequestJob itself, but Start() calls
// directly into the URLRequestJob subclass, so URLRequestJob can't set it
// here.
status_ = ERR_IO_PENDING;
job_->Start();
}
这里可以看到,当 URLRequestJob
start
后,URLRequest
的状态变成了 ERR_IO_PENDING
,这是 chromium-net
里到处都能见到的状态机流转来实现异步等待的方式,有许多地方设置状态,状态机收到状态后做相应的响应,ERR_IO_PENDING
的状态说明当前线程已经 pending
了,需要等待 IO 线程执行完任务后解除 pending 状态。
-
net/url_request/url_request_job.h
那我们继续往下看,目前跟到了 URLRequestJob 的 start 方法了
// net/url_request/url_request_job.h
// If any error occurs while starting the Job, NotifyStartError should be
// called asynchronously.
// This helps ensure that all errors follow more similar notification code
// paths, which should simplify testing.
virtual void Start() = 0;
URLRequestJob::Start()方法是个虚方法,是由子类实现具体的方法体的,因为我们重点关注 http 的请求,所以 直接去 url_request_http_job 里看 http 协议相关实现即可
-
net/url_request/url_request_http_job.cc
直接看 Start() 方法
//net/url_request/url_request_http_job.cc
void URLRequestHttpJob::Start() {
//给 HttpRequestInfo 设置一堆东西
request_info_.url = request_->url();
request_info_.method = request_->method();
//...省略一堆
//注入 header
AddExtraHeaders();
//注入 cookie 并且开始请求
AddCookieHeaderAndStart();
}
//net/url_request/url_request_http_job.cc
void URLRequestHttpJob::AddCookieHeaderAndStart() {
//省略一堆东西,如果是需要异步拿 Cookie 的话,拿完 Cookie 后,入口会变成 SetCookieHeaderAndStart
StartTransaction();
}
//net/url_request/url_request_http_job.cc
void URLRequestHttpJob::StartTransaction() {
//省略network_delegate逻辑
StartTransactionInternal();
}
//net/url_request/url_request_http_job.cc
void URLRequestHttpJob::StartTransactionInternal() {
//省略一堆transaction_已经存在,走 restart 的逻辑,这里会去通过 http_transaction_factory 创建 HttpTransaction
//这里的 http_transaction_factory 是 HttpNetworkLayer,transaction 是 HttpNetworkTransaction
rv = request_->context()->http_transaction_factory()->CreateTransaction(
priority_, &transaction_);
//省略一堆 wss:// 和 wso://逻辑
if (rv == OK) {
// 给 transation 注入一堆 callback,
transaction_->SetConnectedCallback(base::BindRepeating(
&URLRequestHttpJob::NotifyConnectedCallback, base::Unretained(this)));
transaction_->SetRequestHeadersCallback(request_headers_callback_);
rv = transaction_->Start(
&request_info_,
base::BindOnce(&URLRequestHttpJob::OnStartCompleted,
base::Unretained(this)),
request_->net_log());
}
//...省略对外部的回调
}
-
net/http/http_network_transaction.cc
那我们继续看 HttpNetworkTransaction 的 Start() 入口
//net/http/http_network_transaction.cc
int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info,
CompletionOnceCallback callback,
const NetLogWithSource& net_log) {
//...
session_->GetSSLConfig(&server_ssl_config_, &proxy_ssl_config_);
//...
next_state_ = STATE_NOTIFY_BEFORE_CREATE_STREAM;
int rv = DoLoop(OK);
if (rv == ERR_IO_PENDING)
callback_ = std::move(callback);
// This always returns ERR_IO_PENDING because DoCreateStream() does, but
// GenerateNetworkErrorLoggingReportIfError() should be called here if any
// other net::Error can be returned.
DCHECK_EQ(rv, ERR_IO_PENDING);
return rv;
}
这里出现了上面我们说过的状态机,通过设置 next_state_(即下一步动作), DoLoop 转动状态轮盘,执行下一步任务,这里下一步的状态是准备创建 stream,stream 是请求流,可以理解为服务器和客户端 TCP 连接内用于交换帧数据的独立双向序列,如果协议是 http1.0,那么一个 TCP 连接只有一个 stream,如果是 http2,由于多路复用的特性,多个 stream 可以共用一个 TCP 连接。 既然这里已经走到了创建 stream 的状态了,大概也可以猜到,网络请求的状态流转就是依赖HttpNetworkTransaction 中的状态机,这有点像是 OKHttp 中的 InterceptorChain,一个是通过状态流转,一个是链式推进,那我们看看 HttpNetworkTransaction::DoLoop 函数究竟长啥样。
//net/http/http_network_transaction.cc
int HttpNetworkTransaction::DoLoop(int result) {
DCHECK(next_state_ != STATE_NONE);
int rv = result;
do {
State state = next_state_;
next_state_ = STATE_NONE;
switch (state) {
case STATE_NOTIFY_BEFORE_CREATE_STREAM:
//准备创建 stream,直接下一状态
rv = DoNotifyBeforeCreateStream();
break;
case STATE_CREATE_STREAM:
//创建 stream,整个过程包括从 SocketPool 分配 Socket、握手、建连过程,这里需要异步等待
//因此 DoCreateStream 方法会返回 ERR_IO_PENDING 暂时退出循环
//同时,http_network_transaction 实现了HttpStreamRequest::Delegate,即成功/失败后的 callback
//等到 HttpStream 创建完成后,会回调实现的HttpStreamRequest::Delegate::OnStreamReady 方法
//在 OnStreamReady方法里,调用OnIOComplete,继续 DoLoop,进入下一个状态
//注意,这里的 HttpStream 只是持有了 Socket,并没有在这个 Socket 上初始化 stream
rv = DoCreateStream();
break;
case STATE_CREATE_STREAM_COMPLETE:
//创建 HttpStream 结束,如果是 H2,是 SpdyHttpStream,如果是 QUIC,是 QuicHttpStream
rv = DoCreateStreamComplete(rv);
break;
case STATE_INIT_STREAM:
//构造SpdySession,SpdySession 将持有 SessionSocket
//通过 SpdySession 构造 SpdyStream,这个类将直接操作 SpdyBuffer(IOBuffer)
//用来读写字节流,所以这里的 initStream 类似于
//BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
rv = DoInitStream();
break;
case STATE_INIT_STREAM_COMPLETE:
//SpdyStream 创建完成后,回调上层,连接成功了,在这个函数里会 invoke connected_callback_ 进入下一状态
rv = DoInitStreamComplete(rv);
break;
case STATE_CONNECTED_CALLBACK_COMPLETE:
//connected_callback_ 被调用了,这个函数内只改了next_state_ 继续loop
rv = DoConnectedCallbackComplete(rv);
break;
case STATE_GENERATE_PROXY_AUTH_TOKEN:
//如果没有 http proxy,会直接下一状态
rv = DoGenerateProxyAuthToken();
break;
case STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE:
//这个函数内只改了next_state_ 继续loop
rv = DoGenerateProxyAuthTokenComplete(rv);
break;
case STATE_GENERATE_SERVER_AUTH_TOKEN:
//如果请求不是 PRIVACY_MODE_DISABLE,就会直接跳过,是的话,就会生成 auth token
//如果开启了 privacy mode,server 将无法追踪到请求来源
rv = DoGenerateServerAuthToken();
break;
case STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE:
//这个函数内只改了next_state_ 继续loop
rv = DoGenerateServerAuthTokenComplete(rv);
break;
case STATE_INIT_REQUEST_BODY:
//post、put、patch、delete 等 method 需要上传 requestBody 的 case 才会走到
//里面会调用 upload_data_stream->Init 来 init request body
//upload_data_stream 是 java 层通过 requestBuilder.setUploadDataProvider() 传入并在合适时机创建出来的的
rv = DoInitRequestBody();
break;
case STATE_INIT_REQUEST_BODY_COMPLETE:
//这个函数内只改了next_state_ 继续loop
rv = DoInitRequestBodyComplete(rv);
break;
case STATE_BUILD_REQUEST:
//构建request_headers_ 和 body
rv = DoBuildRequest();
break;
case STATE_BUILD_REQUEST_COMPLETE:
//这个函数内只改了next_state_ 继续loop
rv = DoBuildRequestComplete(rv);
break;
case STATE_SEND_REQUEST:
//1. 设置请求幂等性 - idempotency
//2. 发送请求,调用SpdyStream::SendRequest 来发送请求头和数据,同时传入io_callback,随后进入 ERR_IO_PENDING 状态
//SendRequest 最终会调用到 SpdySession::EnqueueStreamWrite 来写入数据,当数据写入后,如果仍然有数据未写入
//会在 OnDataSent 中,调用 QueueNextDataFrame 来入队下一个data frame
//等到数据全部发送完成后,会回调io_callback,解除 pending 状态
rv = DoSendRequest();
break;
case STATE_SEND_REQUEST_COMPLETE:
//如果写入失败,则返回 IOError,否则 next_state_=STATE_READ_HEADERS,继续 loop
rv = DoSendRequestComplete(rv);
break;
case STATE_READ_HEADERS:
// 会立马调用 ReadResponseHeaders,调用这个方法后,默认会进入 ERR_IO_PENDING 状态,等待 IO 返回
// 同时会将 io_callback 继续传入,等待 readHeader 结束后解除 pending 状态
rv = DoReadHeaders();
break;
case STATE_READ_HEADERS_COMPLETE:
//会检查 read header的结果,检查是否有 io error,以及检查状态码、content-encoding
rv = DoReadHeadersComplete(rv);
break;
case STATE_READ_BODY:
//当读完 header 后,data 部分就开始读取
rv = DoReadBody();
break;
case STATE_READ_BODY_COMPLETE:
rv = DoReadBodyComplete(rv);
break;
case STATE_DRAIN_BODY_FOR_AUTH_RESTART:
rv = DoDrainBodyForAuthRestart();
break;
case STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE:
rv = DoDrainBodyForAuthRestartComplete(rv);
break;
default:
NOTREACHED() << "bad state";
rv = ERR_FAILED;
break;
}
} while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
return rv;
}
从上面代码中,我们可以看到,HttpNetworkTransaction 中的状态机从初始的STATE_NOTIFY_BEFORE_CREATE_STREAM 状态开始轮转,逐个状态往下推进,最终完成了整个网络请求。代码中的每个状态在代码中的顺序,就是一个普通网络请求的生命周期,上一个状态执行完后,只要不是 IO_PENDING,在 Doxxx 中总会指定下一状态,并且继续 loop,从而执行下一状态的对应工作。
总结
我们的这一期分享中,并没有深入细节,只是从比较高的视野去看整体的流程,后续会继续深入细节处,分享更加细节的chromium-net相关知识点,包括但不仅限于:
- socket pool 和 OKHttp 的ConnectionPool 异同
- cache 控制
- 设计模式
- ...
受限于本人有限的水平,文中必然有描述错误或者理解错误的地方,如看到请指正。
你可能感兴趣
Android QUIC 实践 - 基于 OKHttp 扩展出 Cronet 拦截器 - 掘金 (juejin.cn)
Android启动优化实践 - 秒开率从17%提升至75% - 掘金 (juejin.cn)
如何科学的进行Android包体积优化 - 掘金 (juejin.cn)
Android稳定性:Looper兜底框架实现线上容灾(二) - 掘金 (juejin.cn)
基于 Booster ASM API的配置化 hook 方案封装 - 掘金 (juejin.cn)
记 AndroidStudio Tracer工具导致的编译失败 - 掘金 (juejin.cn)
Android 启动优化案例-WebView非预期初始化排查 - 掘金 (juejin.cn)
chromium-net - 跟随 Cronet 的脚步探索大致流程(1) - 掘金 (juejin.cn)
Android稳定性:可远程配置化的Looper兜底框架 - 掘金 (juejin.cn)
一类有趣的无限缓存OOM现象 - 掘金 (juejin.cn)
Android - 一种新奇的冷启动速度优化思路(Fragment极度懒加载 + Layout子线程预加载) - 掘金 (juejin.cn)