chromium中HTTP网络资源的加载过程
来源:互联网 发布:matlab矩阵逻辑运算 编辑:程序博客网 时间:2024/04/28 11:10
chromium中HTTP网络资源的加载主要分两部分,一部分是缓存的网络资源,一部分是线上的网络资源。
我们访问http网页的时候,首先访问httpcache,看缓存中的数据是否有效,如果有效,那么我们加载这个数据,如果无效,那么我们访问网络去重新加载资源,当然chromium中HTTP网络资源的加载并没有说起来这么简单,实际上架构设计还是比较复杂的。首先我们先看看HttpCache::Transaction的相关设计及实现,研究一下缓存的处理。
HttpCache::Transaction
在源码分析之前,我们先看一个小示例,从使用者的角度看看cache的使用过程,那么就需要一个切入点。
情景分析
首先我们在windbg中启动chromium,然后设置多进程调试。
0:000> .childdbg 1Processes created by the current process will be debugged0:000> g
运行后待程序稳定后,中断到调试器,之后在主进程上下断
0:011> bp chrome_7feed810000!net::HttpCache::Transaction::Start
之后运行起来,然后在浏览器的地址窗口中输入一个字符“c”,这是我假定的设置的,因为浏览器的地址窗口默认设置了百度的搜索引擎,当用户输入一个字符的时候会触发一次搜索。而实际上确实是这样。当输入字符后,我的程序中断了。
0:028> kc # Call Site00 chrome_7feed810000!net::HttpCache::Transaction::Start01 chrome_7feed810000!net::URLRequestHttpJob::StartTransactionInternal02 chrome_7feed810000!net::URLRequestHttpJob::MaybeStartTransactionInternal03 chrome_7feed810000!net::URLRequestHttpJob::StartTransaction04 chrome_7feed810000!net::URLRequestHttpJob::SetCookieHeaderAndStart05 chrome_7feed810000!base::internal::RunnableAdapter<void (__cdecl LocalDataContainer::*)(std::list<content::CacheStorageUsageInfo,std::allocator<content::CacheStorageUsageInfo> > const &)>::Run06 chrome_7feed810000!base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__cdecl LocalDataContainer::*)(std::list<content::CacheStorageUsageInfo,std::allocator<content::CacheStorageUsageInfo> > const &)> >::MakeItSo07 chrome_7feed810000!base::internal::Invoker<base::IndexSequence<0>,base::internal::BindState<base::internal::RunnableAdapter<void (__cdecl LocalDataContainer::*)(std::list<content::CacheStorageUsageInfo,std::allocator<content::CacheStorageUsageInfo> > const & __ptr64) __ptr64>,void __cdecl(LocalDataContainer * __ptr64,std::list<content::CacheStorageUsageInfo,std::allocator<content::CacheStorageUsageInfo> > const & __ptr64),base::WeakPtr<LocalDataContainer> >,base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__cdecl LocalDataContainer::*)(std::list<content::CacheStorageUsageInfo,std::allocator<content::CacheStorageUsageInfo> > const & __ptr64) __ptr64> >,void __cdecl(std::list<content::CacheStorageUsageInfo,std::allocator<content::CacheStorageUsageInfo> > const & __ptr64)>::Run08 chrome_7feed810000!base::Callback<void __cdecl(void)>::Run09 chrome_7feed810000!net::CookieMonster::CookieMonsterTask::InvokeCallback0a chrome_7feed810000!net::CookieMonster::GetCookieListWithOptionsTask::Run0b chrome_7feed810000!net::CookieMonster::DoCookieTaskForURL0c chrome_7feed810000!net::CookieMonster::GetCookieListWithOptionsAsync0d chrome_7feed810000!net::URLRequestHttpJob::AddCookieHeaderAndStart0e chrome_7feed810000!net::URLRequestHttpJob::Start0f chrome_7feed810000!net::URLRequest::StartJob10 chrome_7feed810000!net::URLRequest::BeforeRequestComplete11 chrome_7feed810000!net::URLRequest::Start12 chrome_7feed810000!net::URLFetcherCore::StartURLRequest13 chrome_7feed810000!net::URLFetcherCore::StartURLRequestWhenAppropriate14 chrome_7feed810000!net::URLFetcherCore::DidInitializeWriter15 chrome_7feed810000!net::URLFetcherCore::StartOnIOThread16 chrome_7feed810000!base::Callback<void __cdecl(void)>::Run17 chrome_7feed810000!base::debug::TaskAnnotator::RunTask18 chrome_7feed810000!base::MessageLoop::RunTask19 chrome_7feed810000!base::MessageLoop::DeferOrRunPendingTask1a chrome_7feed810000!base::MessageLoop::DoWork1b chrome_7feed810000!base::MessagePumpForIO::DoRunLoop1c chrome_7feed810000!base::MessagePumpWin::Run1d chrome_7feed810000!base::MessageLoop::RunHandler1e chrome_7feed810000!base::RunLoop::Run1f chrome_7feed810000!base::MessageLoop::Run20 chrome_7feed810000!base::Thread::Run21 chrome_7feed810000!content::BrowserThreadImpl::IOThreadRun22 chrome_7feed810000!content::BrowserThreadImpl::Run23 chrome_7feed810000!base::Thread::ThreadMain24 chrome_7feed810000!base::`anonymous namespace'::ThreadFunc25 kernel32!BaseThreadInitThunk26 ntdll!RtlUserThreadStart
研究一下堆栈信息,其实就已经知道了大抵上的调用层次,这个请求的方式和我之前介绍过的chromium中FTP网络资源的加载 文章中很相似。
在一个IO线程中启动了URL的加载请求,这是一个内部请求,使用的是URLFetcherCore,然后启动URLRequest,之后开启一个特定的job,在这里这个job是URLRequestHttpJob,之后启动一个传输,而这个传输就是我们要说的HttpCache::Transaction,内部数据的请求基于HttpCache。
0:028> ~~[1244]s;.frame 0n0;dv /t /vchrome_7feed810000!net::HttpCache::Transaction::Start:000007fe`ee46c6e4 48895c2410 mov qword ptr [rsp+10h],rbx ss:00000000`0961c408=0000000012695a6000 00000000`0961c3f8 000007fe`ee4bf027 chrome_7feed810000!net::HttpCache::Transaction::Start [c:\b\build\slave\win64\build\src\net\http\http_cache_transaction.cc @ 254]@rcx class net::HttpCache::Transaction * this = 0x00000000`1269c170@rdx struct net::HttpRequestInfo * request = 0x00000000`12695c28@r8 class base::Callback<void __cdecl(int)> * callback = 0x00000000`12695d40@r9 class net::BoundNetLog * net_log = 0x00000000`12691ec8<unavailable> int rv = <value unavailable>0:028> dx -id 0,0 -r1 (*((chrome_7feed810000!net::HttpRequestInfo *)0x12695c28))(*((chrome_7feed810000!net::HttpRequestInfo *)0x12695c28)) [Type: net::HttpRequestInfo] [+0x000] url [Type: GURL] [+0x078] method : "GET" [Type: std::basic_string<char,std::char_traits<char>,std::allocator<char> >] [+0x098] extra_headers [Type: net::HttpRequestHeaders] [+0x0b0] upload_data_stream : 0x0 [Type: net::UploadDataStream *] [+0x0b8] load_flags : 64 [+0x0bc] motivation : NORMAL_MOTIVATION (2) [Type: net::HttpRequestInfo::RequestMotivation] [+0x0c0] privacy_mode : PRIVACY_MODE_DISABLED (0) [Type: net::PrivacyMode]0:028> dx -id 0,0 -r1 (*((chrome_7feed810000!GURL *)0x12695c28))(*((chrome_7feed810000!GURL *)0x12695c28)) [Type: GURL] [+0x000] spec_ : "http://suggestion.baidu.com/su?wd=c&action=opensearch&ie=UTF-8" [Type: std::basic_string<char,std::char_traits<char>,std::allocator<char> >] [+0x020] is_valid_ : true [+0x028] parsed_ [Type: url::Parsed] [+0x070] inner_url_ [Type: scoped_ptr<GURL,std::default_delete<GURL> >]
接着,我们来看一下,当我们在浏览器中输入一个字符的时候,浏览器的一个URL的请求过程,上面的网址就是要请求的URL,“http://suggestion.baidu.com/su?wd=c&action=opensearch&ie=UTF-8”,这是一个http GET命令。Opensearch 字符”c”.
当然,浏览器地址栏的结果数据的提供过程并不是我们讨论的主题,我们是通过这个切入点去详细了解其中一点,http网络资源的加载过程。
源码分析
HttpCache::Transaction::Start
上面我们看到了最后一个函数栈,是一个缓存传输启动的过程,我们从这里开始,已知的一点是我们向启动函数中传递了请求信息,包含了URL和method。
int HttpCache::Transaction::Start(const HttpRequestInfo* request, const CompletionCallback& callback, const BoundNetLog& net_log) {...... if (!cache_.get()) return ERR_UNEXPECTED; SetRequest(net_log, request); // We have to wait until the backend is initialized so we start the SM. next_state_ = STATE_GET_BACKEND; int rv = DoLoop(OK); // Setting this here allows us to check for the existence of a callback_ to // determine if we are still inside Start. if (rv == ERR_IO_PENDING) callback_ = callback; return rv;}
我们来看一下源码,这里我们设置了下一跳状态,然后启动状态机循环,这里多少和FTP资源的加载有些相似,FTP的加载要较为简单些。
HttpCache::Transaction::State
enum State { // Normally, states are traversed in approximately this order. STATE_NONE, STATE_GET_BACKEND, STATE_GET_BACKEND_COMPLETE, STATE_INIT_ENTRY, STATE_OPEN_ENTRY, STATE_OPEN_ENTRY_COMPLETE, STATE_DOOM_ENTRY, STATE_DOOM_ENTRY_COMPLETE, STATE_CREATE_ENTRY, STATE_CREATE_ENTRY_COMPLETE, STATE_ADD_TO_ENTRY, STATE_ADD_TO_ENTRY_COMPLETE, ..... }
HttpCache的传输过程也是状态改变的过程,状态比较多,情况就比较多。
chromium源码作者还是比较贴心的,在源码状态机循环函数前以注释的方式列出了各种情况。
// 1. Not-cached entry:// Start():// GetBackend* -> InitEntry -> OpenEntry* -> CreateEntry* -> AddToEntry* ->// SendRequest* -> SuccessfulSendRequest -> OverwriteCachedResponse ->// CacheWriteResponse* -> TruncateCachedData* -> TruncateCachedMetadata* ->// PartialHeadersReceived//// Read():// NetworkRead* -> CacheWriteData*//// 2. Cached entry, no validation:// Start():// GetBackend* -> InitEntry -> OpenEntry* -> AddToEntry* -> CacheReadResponse*// -> CacheDispatchValidation -> BeginPartialCacheValidation() ->// BeginCacheValidation() -> SetupEntryForRead()//// Read():// CacheReadData*//// 3. Cached entry, validation (304):// Start():// GetBackend* -> InitEntry -> OpenEntry* -> AddToEntry* -> CacheReadResponse*// -> CacheDispatchValidation -> BeginPartialCacheValidation() ->// BeginCacheValidation() -> SendRequest* -> SuccessfulSendRequest ->// UpdateCachedResponse -> CacheWriteUpdatedResponse* ->// UpdateCachedResponseComplete -> OverwriteCachedResponse ->// PartialHeadersReceived//// Read():// CacheReadData*//// 4. Cached entry, validation and replace (200):// Start():// GetBackend* -> InitEntry -> OpenEntry* -> AddToEntry* -> CacheReadResponse*// -> CacheDispatchValidation -> BeginPartialCacheValidation() ->// BeginCacheValidation() -> SendRequest* -> SuccessfulSendRequest ->// OverwriteCachedResponse -> CacheWriteResponse* -> DoTruncateCachedData* ->// TruncateCachedMetadata* -> PartialHeadersReceived//// Read():// NetworkRead* -> CacheWriteData*//// 5. Sparse entry, partially cached, byte range request:// Start():// GetBackend* -> InitEntry -> OpenEntry* -> AddToEntry* -> CacheReadResponse*// -> CacheDispatchValidation -> BeginPartialCacheValidation() ->// CacheQueryData* -> ValidateEntryHeadersAndContinue() ->// StartPartialCacheValidation -> CompletePartialCacheValidation ->// BeginCacheValidation() -> SendRequest* -> SuccessfulSendRequest ->// UpdateCachedResponse -> CacheWriteUpdatedResponse* ->// UpdateCachedResponseComplete -> OverwriteCachedResponse ->// PartialHeadersReceived//// Read() 1:// NetworkRead* -> CacheWriteData*//// Read() 2:// NetworkRead* -> CacheWriteData* -> StartPartialCacheValidation ->// CompletePartialCacheValidation -> CacheReadData* ->//// Read() 3:// CacheReadData* -> StartPartialCacheValidation ->// CompletePartialCacheValidation -> BeginCacheValidation() -> SendRequest* ->// SuccessfulSendRequest -> UpdateCachedResponse* -> OverwriteCachedResponse// -> PartialHeadersReceived -> NetworkRead* -> CacheWriteData*//// 6. HEAD. Not-cached entry:// Pass through. Don't save a HEAD by itself.// Start():// GetBackend* -> InitEntry -> OpenEntry* -> SendRequest*//// 7. HEAD. Cached entry, no validation:// Start():// The same flow as for a GET request (example #2)//// Read():// CacheReadData (returns 0)//// 8. HEAD. Cached entry, validation (304):// The request updates the stored headers.// Start(): Same as for a GET request (example #3)//// Read():// CacheReadData (returns 0)//// 9. HEAD. Cached entry, validation and replace (200):// Pass through. The request dooms the old entry, as a HEAD won't be stored by// itself.// Start():// GetBackend* -> InitEntry -> OpenEntry* -> AddToEntry* -> CacheReadResponse*// -> CacheDispatchValidation -> BeginPartialCacheValidation() ->// BeginCacheValidation() -> SendRequest* -> SuccessfulSendRequest ->// OverwriteCachedResponse//// 10. HEAD. Sparse entry, partially cached:// Serve the request from the cache, as long as it doesn't require// revalidation. Ignore missing ranges when deciding to revalidate. If the// entry requires revalidation, ignore the whole request and go to full pass// through (the result of the HEAD request will NOT update the entry).//// Start(): Basically the same as example 7, as we never create a partial_// object for this request.//// 11. Prefetch, not-cached entry:// The same as example 1. The "unused_since_prefetch" bit is stored as true in// UpdateCachedResponse.//// 12. Prefetch, cached entry:// Like examples 2-4, only CacheToggleUnusedSincePrefetch* is inserted between// CacheReadResponse* and CacheDispatchValidation if the unused_since_prefetch// bit is unset.//// 13. Cached entry less than 5 minutes old, unused_since_prefetch is true:// Skip validation, similar to example 2.// GetBackend* -> InitEntry -> OpenEntry* -> AddToEntry* -> CacheReadResponse*// -> CacheToggleUnusedSincePrefetch* -> CacheDispatchValidation ->// BeginPartialCacheValidation() -> BeginCacheValidation() ->// SetupEntryForRead()//// Read():// CacheReadData*//// 14. Cached entry more than 5 minutes old, unused_since_prefetch is true:// Like examples 2-4, only CacheToggleUnusedSincePrefetch* is inserted between// CacheReadResponse* and CacheDispatchValidation.
简要看看,一般的缓存传输基本上都要经历的过程是获取Backend,然后初始化Entry,之后打开,然后从中加载进来,之后进行验证,后面的过程就有些不一样了。自然,从地址栏中输入单个字符引发搜索,URL资源请求也就符合其中一种情况。
操作Backend准备Cache数据
base::WeakPtr<HttpCache> cache_;
int HttpCache::Transaction::DoGetBackend() { cache_pending_ = true; next_state_ = STATE_GET_BACKEND_COMPLETE; net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_GET_BACKEND); return cache_->GetBackendForTransaction(this);}int HttpCache::Transaction::DoInitEntry() { DCHECK(!new_entry_); if (!cache_.get()) return ERR_UNEXPECTED; if (mode_ == WRITE) { next_state_ = STATE_DOOM_ENTRY; return OK; } next_state_ = STATE_OPEN_ENTRY; return OK;}int HttpCache::Transaction::DoOpenEntry() { DCHECK(!new_entry_); next_state_ = STATE_OPEN_ENTRY_COMPLETE; cache_pending_ = true; net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY); first_cache_access_since_ = TimeTicks::Now(); return cache_->OpenEntry(cache_key_, &new_entry_, this);}
Httpcache的传输实际上操作httpCache来完成具体的功能,做前期的准备工作,最后通过cache_key_,来查询某一项。
[+0x3a0] cache_key_ : "http://suggestion.baidu.com/su?wd=c&action=opensearch&ie=UTF-8"
这里面的cache_key_其实就是URL,在HttpCache中URL唯一检索资源,就是通过这个key值来找到对应的Entry。
int HttpCache::Transaction::DoAddToEntry() { DCHECK(new_entry_); cache_pending_ = true; next_state_ = STATE_ADD_TO_ENTRY_COMPLETE; net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_ADD_TO_ENTRY); DCHECK(entry_lock_waiting_since_.is_null()); entry_lock_waiting_since_ = TimeTicks::Now(); int rv = cache_->AddTransactionToEntry(new_entry_, this); if (rv == ERR_IO_PENDING) { if (bypass_lock_for_test_) { OnAddToEntryTimeout(entry_lock_waiting_since_); } else { int timeout_milliseconds = 20 * 1000; if (partial_ && new_entry_->writer && new_entry_->writer->range_requested_) { timeout_milliseconds = 25; } base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::Bind(&HttpCache::Transaction::OnAddToEntryTimeout, weak_factory_.GetWeakPtr(), entry_lock_waiting_since_), TimeDelta::FromMilliseconds(timeout_milliseconds)); } } return rv;}
当我们找到对应的项后,我们具体的加载这个项的数据,如果返回IO_PENDING,那么设置超时回调函数。设置下一状态为STATE_ADD_TO_ENTRY_COMPLETE,在完成函数中,设置下一跳状态,记录处理时间。
HttpCache::Transaction Read Entry and validation
int HttpCache::Transaction::DoCacheReadResponse() { DCHECK(entry_); next_state_ = STATE_CACHE_READ_RESPONSE_COMPLETE; io_buf_len_ = entry_->disk_entry->GetDataSize(kResponseInfoIndex); read_buf_ = new IOBuffer(io_buf_len_); net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_READ_INFO); return entry_->disk_entry->ReadData(kResponseInfoIndex, 0, read_buf_.get(), io_buf_len_, io_callback_);}
当一切都准备完成后,开始从缓存中读取数据,函数中设置下一跳,然后从entry中获取到缓存的大小,申请空间,之后操作entry来读取数据。
int HttpCache::Transaction::DoCacheDispatchValidation() { int result = ERR_FAILED; switch (mode_) { case READ: UpdateTransactionPattern(PATTERN_ENTRY_USED); result = BeginCacheRead(); break; case READ_WRITE: result = BeginPartialCacheValidation(); break; case UPDATE: result = BeginExternallyConditionalizedRequest(); break; case WRITE: default: NOTREACHED(); } return result;}
当我们读取完数据后,就开始了验证数据的过程。这里是个读写的情况。在派发验证的过程中,跳过了验证,符合上面的情况2,Cached entry, no validation。
HttpCache::Transaction 数据读取
当我们准备cache,操作并查找到对应的项,然后读取项,之后判断是否验证项,如果这都通过了之后,下层会通知上层来提取数据。
0:026> kc # Call Site00 chrome_7feed900000!net::HttpCache::Transaction::DoCacheReadData01 chrome_7feed900000!net::HttpCache::Transaction::DoLoop02 chrome_7feed900000!net::HttpCache::Transaction::Read03 chrome_7feed900000!net::URLRequestHttpJob::ReadRawData04 chrome_7feed900000!net::URLRequestJob::ReadRawDataHelper05 chrome_7feed900000!net::URLRequestJob::Read06 chrome_7feed900000!net::URLRequest::Read07 chrome_7feed900000!net::URLFetcherCore::ReadResponse08 chrome_7feed900000!net::URLFetcherCore::OnResponseStarted09 chrome_7feed900000!net::URLRequest::NotifyResponseStarted0a chrome_7feed900000!net::URLRequestJob::NotifyHeadersComplete0b chrome_7feed900000!net::URLRequestHttpJob::NotifyHeadersComplete0c chrome_7feed900000!net::URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete0d chrome_7feed900000!net::URLRequestHttpJob::OnStartCompleted0e chrome_7feed900000!base::Callback<void __cdecl(int)>::Run0f chrome_7feed900000!net::HttpCache::Transaction::DoLoop10 chrome_7feed900000!base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int)>::Run11 chrome_7feed900000!base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int)> >::MakeItSo12 chrome_7feed900000!base::internal::Invoker<base::IndexSequence<0>,base::internal::BindState<base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int) __ptr64>,void __cdecl(extensions::NativeMessageProcessHost * __ptr64,int),base::WeakPtr<extensions::NativeMessageProcessHost> >,base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int) __ptr64> >,void __cdecl(int const & __ptr64)>::Run13 chrome_7feed900000!base::Callback<void __cdecl(int)>::Run14 chrome_7feed900000!disk_cache::InFlightBackendIO::OnOperationComplete15 chrome_7feed900000!disk_cache::BackgroundIO::OnIOSignalled16 chrome_7feed900000!base::Callback<void __cdecl(void)>::Run17 chrome_7feed900000!base::debug::TaskAnnotator::RunTask18 chrome_7feed900000!base::MessageLoop::RunTask19 chrome_7feed900000!base::MessageLoop::DeferOrRunPendingTask1a chrome_7feed900000!base::MessageLoop::DoWork1b chrome_7feed900000!base::MessagePumpForIO::DoRunLoop1c chrome_7feed900000!base::MessagePumpWin::Run1d chrome_7feed900000!base::MessageLoop::RunHandler1e chrome_7feed900000!base::RunLoop::Run1f chrome_7feed900000!base::MessageLoop::Run20 chrome_7feed900000!base::Thread::Run21 chrome_7feed900000!content::BrowserThreadImpl::IOThreadRun22 chrome_7feed900000!content::BrowserThreadImpl::Run23 chrome_7feed900000!base::Thread::ThreadMain24 chrome_7feed900000!base::`anonymous namespace'::ThreadFunc25 kernel32!BaseThreadInitThunk26 ntdll!RtlUserThreadStart
disk_cache 是HttpCache实际存储在硬盘上的数据组织结构,当完成后通知URLRequestHttpJob启动完成,然后再通过HttpCache进行读取数据。
int HttpCache::Transaction::DoCacheReadData() { if (request_->method == "HEAD") return 0; DCHECK(entry_); next_state_ = STATE_CACHE_READ_DATA_COMPLETE; if (net_log_.IsCapturing()) net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_READ_DATA); if (partial_) { return partial_->CacheRead(entry_->disk_entry, read_buf_.get(), io_buf_len_, io_callback_); } return entry_->disk_entry->ReadData(kResponseContentIndex, read_offset_, read_buf_.get(), io_buf_len_, io_callback_);}
而数据的读取操作的实际处理也是通过disk_cache进行实际的数据处理,这里HttpCache做中间的一个传输的过程。
HttpCache::Transaction 小结
这里简要介绍了HttpCache的传输过程,仅介绍了其中的一种情况(Cached entry, no validation ),当我们在地址栏中输入字符的时候,会触发一个URL的请求,而这个请求事先也是要通过HttpCache的传输的,请求去查询cache中的项,然后读取出来,在验证的时候跳过了验证机制,之后开始读取实际的数据,完成数据的传输。
HttpNetworkTransaction
上面我们分析了HttpCache的传输方式,如果HttpCache中没有我们要查阅的数据呢,因为我们要访问的是一个我们从没有访问过的URL,又或者我们访问的cache数据已经失效了,那么怎么办呢,此时就要通过网络来加载数据,需要通过http网络传输来完成具体的数据加载。
情景分析
现假设我们访问chromium中FTP网络资源的加载 这篇文章,我的HttpCache中并没有这个URL相关的信息,我们可以在浏览器的地址栏中输入chrome://cache/ 来看HttpCache的存储情况。
我们没有这个缓存,目前我们符合上面的情况一,当我们检索cache后发现没有数据,那么我们要启动网络传输的请求。
0:026> kc # Call Site00 chrome_7feed900000!net::HttpNetworkTransaction::Start01 chrome_7feed900000!DevToolsNetworkTransaction::Start02 chrome_7feed900000!net::HttpCache::Transaction::DoSendRequest03 chrome_7feed900000!net::HttpCache::Transaction::DoLoop04 chrome_7feed900000!base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int)>::Run05 chrome_7feed900000!base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int)> >::MakeItSo06 chrome_7feed900000!base::internal::Invoker<base::IndexSequence<0>,base::internal::BindState<base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int) __ptr64>,void __cdecl(extensions::NativeMessageProcessHost * __ptr64,int),base::WeakPtr<extensions::NativeMessageProcessHost> >,base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int) __ptr64> >,void __cdecl(int const & __ptr64)>::Run07 chrome_7feed900000!base::Callback<void __cdecl(int)>::Run08 chrome_7feed900000!net::HttpCache::WorkItem::NotifyTransaction09 chrome_7feed900000!net::HttpCache::OnIOComplete0a chrome_7feed900000!net::HttpCache::OnPendingOpComplete0b chrome_7feed900000!base::Callback<void __cdecl(int)>::Run0c chrome_7feed900000!disk_cache::InFlightBackendIO::OnOperationComplete0d chrome_7feed900000!disk_cache::BackgroundIO::OnIOSignalled0e chrome_7feed900000!base::Callback<void __cdecl(void)>::Run0f chrome_7feed900000!base::debug::TaskAnnotator::RunTask10 chrome_7feed900000!base::MessageLoop::RunTask11 chrome_7feed900000!base::MessageLoop::DeferOrRunPendingTask12 chrome_7feed900000!base::MessageLoop::DoWork13 chrome_7feed900000!base::MessagePumpForIO::DoRunLoop14 chrome_7feed900000!base::MessagePumpWin::Run15 chrome_7feed900000!base::MessageLoop::RunHandler16 chrome_7feed900000!base::RunLoop::Run17 chrome_7feed900000!base::MessageLoop::Run18 chrome_7feed900000!base::Thread::Run19 chrome_7feed900000!content::BrowserThreadImpl::IOThreadRun1a chrome_7feed900000!content::BrowserThreadImpl::Run1b chrome_7feed900000!base::Thread::ThreadMain1c chrome_7feed900000!base::`anonymous namespace'::ThreadFunc1d kernel32!BaseThreadInitThunk1e ntdll!RtlUserThreadStart
我们看一下局部变量信息,看看运行时的状态
0:026> ~~[10c4]s;.frame 0n0;dv /t /vchrome_7feed900000!net::HttpNetworkTransaction::Start:000007fe`ee58d66c 48895c2410 mov qword ptr [rsp+10h],rbx ss:00000000`0914e158=000000000aec661000 00000000`0914e148 000007fe`ee28f812 chrome_7feed900000!net::HttpNetworkTransaction::Start [c:\b\build\slave\win64\build\src\net\http\http_network_transaction.cc @ 191]@rcx class net::HttpNetworkTransaction * this = 0x00000000`11a21640@rdx struct net::HttpRequestInfo * request_info = 0x00000000`0aac8ff8@r8 class base::Callback<void __cdecl(int)> * callback = 0x00000000`11d18b80@r9 class net::BoundNetLog * net_log = 0x00000000`11d187a0<unavailable> int rv = <value unavailable>0:026> dx -id 0,0 -r1 (*((chrome_7feed900000!net::HttpRequestInfo *)0xaac8ff8))(*((chrome_7feed900000!net::HttpRequestInfo *)0xaac8ff8)) [Type: net::HttpRequestInfo] [+0x000] url [Type: GURL] [+0x078] method : "GET" [Type: std::basic_string<char,std::char_traits<char>,std::allocator<char> >] [+0x098] extra_headers [Type: net::HttpRequestHeaders] [+0x0b0] upload_data_stream : 0x0 [Type: net::UploadDataStream *] [+0x0b8] load_flags : 4352 [+0x0bc] motivation : NORMAL_MOTIVATION (2) [Type: net::HttpRequestInfo::RequestMotivation] [+0x0c0] privacy_mode : PRIVACY_MODE_DISABLED (0) [Type: net::PrivacyMode]0:026> dx -id 0,0 -r1 (*((chrome_7feed900000!GURL *)0xaac8ff8))(*((chrome_7feed900000!GURL *)0xaac8ff8)) [Type: GURL] [+0x000] spec_ : "http://blog.csdn.net/feiniao251314/article/details/52230012" [Type: std::basic_string<char,std::char_traits<char>,std::allocator<char> >] [+0x020] is_valid_ : true [+0x028] parsed_ [Type: url::Parsed] [+0x070] inner_url_ [Type: scoped_ptr<GURL,std::default_delete<GURL> >]
在cache中没有找到URL相关的资源,那么我们将请求信息传递到HttpNetworkTransaction中,试图通过网络来请求数据。
源码分析
在源码分析之前,我们看一下HttpNetworkTransaction的状态,根据状态,我们大体上就能了解网络HttpNetworkTransaction要处理的网络情况
HttpNetworkTransaction::State
enum State { STATE_NOTIFY_BEFORE_CREATE_STREAM, STATE_CREATE_STREAM, STATE_CREATE_STREAM_COMPLETE, STATE_INIT_STREAM, STATE_INIT_STREAM_COMPLETE, STATE_GENERATE_PROXY_AUTH_TOKEN, STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE, STATE_GENERATE_SERVER_AUTH_TOKEN, STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE, STATE_GET_TOKEN_BINDING_KEY, STATE_GET_TOKEN_BINDING_KEY_COMPLETE, STATE_INIT_REQUEST_BODY, STATE_INIT_REQUEST_BODY_COMPLETE, STATE_BUILD_REQUEST, STATE_BUILD_REQUEST_COMPLETE, STATE_SEND_REQUEST, STATE_SEND_REQUEST_COMPLETE, STATE_READ_HEADERS, STATE_READ_HEADERS_COMPLETE, STATE_READ_BODY, STATE_READ_BODY_COMPLETE, STATE_DRAIN_BODY_FOR_AUTH_RESTART, STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE, STATE_NONE };
状态要比HttpCache要少许多,和ftp网络传输状态相当。分创建,初始化,生成key,初始化请求,建立请求,发送请求,读取请求等等。我们从中简要分析其中的过程。
启动与初始化
int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info, const CompletionCallback& callback, const BoundNetLog& net_log) { net_log_ = net_log; request_ = request_info;....... next_state_ = STATE_NOTIFY_BEFORE_CREATE_STREAM; int rv = DoLoop(OK); if (rv == ERR_IO_PENDING) callback_ = callback; return rv;}int HttpNetworkTransaction::DoNotifyBeforeCreateStream() { next_state_ = STATE_CREATE_STREAM; bool defer = false; if (!before_network_start_callback_.is_null()) before_network_start_callback_.Run(&defer); if (!defer) return OK; return ERR_IO_PENDING;}int HttpNetworkTransaction::DoCreateStream() { // TODO(mmenke): Remove ScopedTracker below once crbug.com/424359 is fixed. tracked_objects::ScopedTracker tracking_profile( FROM_HERE_WITH_EXPLICIT_FUNCTION( "424359 HttpNetworkTransaction::DoCreateStream")); response_.network_accessed = true; next_state_ = STATE_CREATE_STREAM_COMPLETE; if (ForWebSocketHandshake()) { stream_request_.reset( session_->http_stream_factory_for_websocket() ->RequestWebSocketHandshakeStream( *request_, priority_, server_ssl_config_, proxy_ssl_config_, this, websocket_handshake_stream_base_create_helper_, net_log_)); } else { stream_request_.reset( session_->http_stream_factory()->RequestStream( *request_, priority_, server_ssl_config_, proxy_ssl_config_, this, net_log_)); } DCHECK(stream_request_.get()); return ERR_IO_PENDING;}
创建比较简单,使用会话session_来创建一个请求Stream,内部创建了socket,我们暂且不向下深挖,暂时先分析http这一层次的传输过程,下面给出创建socket的堆栈示意:
0:026> kc # Call Site00 chrome_7feed900000!net::CreatePlatformSocket01 chrome_7feed900000!net::UDPSocketWin::Open02 chrome_7feed900000!net::UDPClientSocket::Connect03 chrome_7feed900000!net::`anonymous namespace'::IsGloballyReachable04 chrome_7feed900000!net::HostResolverImpl::IsIPv6Reachable05 chrome_7feed900000!net::HostResolverImpl::GetEffectiveKeyForRequest06 chrome_7feed900000!net::HostResolverImpl::Resolve07 chrome_7feed900000!net::SingleRequestHostResolver::Resolve08 chrome_7feed900000!net::TransportConnectJobHelper::DoResolveHost09 chrome_7feed900000!net::TransportConnectJob::DoResolveHost0a chrome_7feed900000!net::TransportConnectJobHelper::DoLoop<net::TransportConnectJob>0b chrome_7feed900000!net::ConnectJob::Connect0c chrome_7feed900000!net::internal::ClientSocketPoolBaseHelper::RequestSocketInternal0d chrome_7feed900000!net::internal::ClientSocketPoolBaseHelper::RequestSocket0e chrome_7feed900000!net::ClientSocketPoolBase<net::TransportSocketParams>::RequestSocket0f chrome_7feed900000!net::ClientSocketHandle::Init<net::SSLClientSocketPool>10 chrome_7feed900000!net::`anonymous namespace'::InitSocketPoolHelper11 chrome_7feed900000!net::InitSocketHandleForHttpRequest12 chrome_7feed900000!net::HttpStreamFactoryImpl::Job::DoInitConnection13 chrome_7feed900000!net::HttpStreamFactoryImpl::Job::DoLoop14 chrome_7feed900000!net::HttpStreamFactoryImpl::Job::RunLoop15 chrome_7feed900000!net::HttpStreamFactoryImpl::RequestStreamInternal16 chrome_7feed900000!net::HttpStreamFactoryImpl::RequestStream17 chrome_7feed900000!net::HttpNetworkTransaction::DoCreateStream18 chrome_7feed900000!net::HttpNetworkTransaction::DoLoop19 chrome_7feed900000!net::HttpNetworkTransaction::Start1a chrome_7feed900000!DevToolsNetworkTransaction::Start1b chrome_7feed900000!net::HttpCache::Transaction::DoSendRequest1c chrome_7feed900000!net::HttpCache::Transaction::DoLoop1d chrome_7feed900000!base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int)>::Run1e chrome_7feed900000!base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int)> >::MakeItSo1f chrome_7feed900000!base::internal::Invoker<base::IndexSequence<0>,base::internal::BindState<base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int) __ptr64>,void __cdecl(extensions::NativeMessageProcessHost * __ptr64,int),base::WeakPtr<extensions::NativeMessageProcessHost> >,base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int) __ptr64> >,void __cdecl(int const & __ptr64)>::Run20 chrome_7feed900000!base::Callback<void __cdecl(int)>::Run21 chrome_7feed900000!net::HttpCache::WorkItem::NotifyTransaction22 chrome_7feed900000!net::HttpCache::OnIOComplete23 chrome_7feed900000!net::HttpCache::OnPendingOpComplete24 chrome_7feed900000!base::Callback<void __cdecl(int)>::Run25 chrome_7feed900000!disk_cache::InFlightBackendIO::OnOperationComplete26 chrome_7feed900000!disk_cache::BackgroundIO::OnIOSignalled27 chrome_7feed900000!base::Callback<void __cdecl(void)>::Run28 chrome_7feed900000!base::debug::TaskAnnotator::RunTask29 chrome_7feed900000!base::MessageLoop::RunTask2a chrome_7feed900000!base::MessageLoop::DeferOrRunPendingTask2b chrome_7feed900000!base::MessageLoop::DoWork2c chrome_7feed900000!base::MessagePumpForIO::DoRunLoop2d chrome_7feed900000!base::MessagePumpWin::Run2e chrome_7feed900000!base::MessageLoop::RunHandler2f chrome_7feed900000!base::RunLoop::Run30 chrome_7feed900000!base::MessageLoop::Run31 chrome_7feed900000!base::Thread::Run32 chrome_7feed900000!content::BrowserThreadImpl::IOThreadRun33 chrome_7feed900000!content::BrowserThreadImpl::Run34 chrome_7feed900000!base::Thread::ThreadMain35 chrome_7feed900000!base::`anonymous namespace'::ThreadFunc36 kernel32!BaseThreadInitThunk37 ntdll!RtlUserThreadStart
int HttpNetworkTransaction::DoInitStream() { DCHECK(stream_.get()); next_state_ = STATE_INIT_STREAM_COMPLETE; stream_->GetRemoteEndpoint(&remote_endpoint_); return stream_->InitializeStream(request_, priority_, net_log_, io_callback_);}
创建请求完成后,初始化Stream,然后生成服务器和客户端的Token.
发送请求
int HttpNetworkTransaction::DoSendRequest() { // TODO(mmenke): Remove ScopedTracker below once crbug.com/424359 is fixed. tracked_objects::ScopedTracker tracking_profile( FROM_HERE_WITH_EXPLICIT_FUNCTION( "424359 HttpNetworkTransaction::DoSendRequest")); send_start_time_ = base::TimeTicks::Now(); next_state_ = STATE_SEND_REQUEST_COMPLETE; return stream_->SendRequest(request_headers_, &response_, io_callback_);}
这里我们可以查看一下request_headers_的相关信息,目前是connection请求。
0:025> dx -id 0,0 -r1 (*((chrome_7fee4550000!net::HttpRequestHeaders::HeaderKeyValuePair *)0x114f49e0))(*((chrome_7fee4550000!net::HttpRequestHeaders::HeaderKeyValuePair *)0x114f49e0)) [Type: net::HttpRequestHeaders::HeaderKeyValuePair] [+0x000] key : "Host" [Type: std::basic_string<char,std::char_traits<char>,std::allocator<char> >] [+0x020] value : "blog.csdn.net" [Type: std::basic_string<char,std::char_traits<char>,std::allocator<char> >]0:025> dx -id 0,0 -r1 (*((chrome_7fee4550000!net::HttpRequestHeaders::HeaderKeyValuePair *)0x114f4a20))(*((chrome_7fee4550000!net::HttpRequestHeaders::HeaderKeyValuePair *)0x114f4a20)) [Type: net::HttpRequestHeaders::HeaderKeyValuePair] [+0x000] key : "Connection" [Type: std::basic_string<char,std::char_traits<char>,std::allocator<char> >] [+0x020] value : "keep-alive" [Type: std::basic_string<char,std::char_traits<char>,std::allocator<char> >]0:025> dx -id 0,0 -r1 (*((chrome_7fee4550000!net::HttpRequestHeaders::HeaderKeyValuePair *)0x114f4a60))(*((chrome_7fee4550000!net::HttpRequestHeaders::HeaderKeyValuePair *)0x114f4a60)) [Type: net::HttpRequestHeaders::HeaderKeyValuePair] [+0x000] key : "Accept" [Type: std::basic_string<char,std::char_traits<char>,std::allocator<char> >] [+0x020] value : "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" [Type: std::basic_string<char,std::char_traits<char>,std::allocator<char> >]
接收数据
int HttpNetworkTransaction::DoSendRequestComplete(int result) { send_end_time_ = base::TimeTicks::Now(); if (result < 0) return HandleIOError(result); next_state_ = STATE_READ_HEADERS; return OK;}int HttpNetworkTransaction::DoReadHeaders() { next_state_ = STATE_READ_HEADERS_COMPLETE; return stream_->ReadResponseHeaders(io_callback_);}
当发送请求完成后,会设置下一跳状态为读取头部状态,然后返回状态循环,开始读取头部信息。
HttpNetworkTransaction 小结
当在HttpCache中无法获取到有效信息的时候,那么就会生成一个HttpNetworkTransaction请求,通过网络来请求最新数据,HttpNetworkTransaction负责建立连接,初始化终端,发送和接收数据,将数据组织好。
网络请求结果的转储
上面我们说了HttpCache::transaction和HttpNetworkTransaction的情况,一个是本地缓存传输,一个是网络数据传输,当本地数据无效的时候,会请求网络传输,而当网络传输完毕的时候会通知本地缓存处理。
情景分析
我们可以如下下断
0:025> bp chrome_7fee4550000!net::HttpCache::Transaction::DoSuccessfulSendRequest
这样,当HttpNetworkTransaction传输完成的时候,会中断到调试器,这样跟踪一下,看看实际的运行过程。
下面是堆栈示意。
0:025> kc # Call Site00 chrome_7fee4550000!net::HttpCache::Transaction::DoSuccessfulSendRequest01 chrome_7fee4550000!net::HttpCache::Transaction::DoLoop02 chrome_7fee4550000!base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int)>::Run03 chrome_7fee4550000!base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int)> >::MakeItSo04 chrome_7fee4550000!base::internal::Invoker<base::IndexSequence<0>,base::internal::BindState<base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int) __ptr64>,void __cdecl(extensions::NativeMessageProcessHost * __ptr64,int),base::WeakPtr<extensions::NativeMessageProcessHost> >,base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__cdecl extensions::NativeMessageProcessHost::*)(int) __ptr64> >,void __cdecl(int const & __ptr64)>::Run05 chrome_7fee4550000!base::Callback<void __cdecl(int)>::Run06 chrome_7fee4550000!net::HttpNetworkTransaction::DoCallback07 chrome_7fee4550000!net::HttpNetworkTransaction::OnIOComplete08 chrome_7fee4550000!net::HttpNetworkTransaction::OnStreamReady09 chrome_7fee4550000!net::HttpStreamFactoryImpl::Job::OnStreamReadyCallback0a chrome_7fee4550000!base::internal::RunnableAdapter<void (__cdecl browser_sync::BookmarkModelAssociator::*)(void)>::Run0b chrome_7fee4550000!base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__cdecl browser_sync::BookmarkModelAssociator::*)(void)> >::MakeItSo0c chrome_7fee4550000!base::internal::Invoker<base::IndexSequence<0>,base::internal::BindState<base::internal::RunnableAdapter<void (__cdecl browser_sync::BookmarkModelAssociator::*)(void) __ptr64>,void __cdecl(browser_sync::BookmarkModelAssociator * __ptr64),base::WeakPtr<browser_sync::BookmarkModelAssociator> >,base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__cdecl browser_sync::BookmarkModelAssociator::*)(void) __ptr64> >,void __cdecl(void)>::Run0d chrome_7fee4550000!base::Callback<void __cdecl(void)>::Run0e chrome_7fee4550000!base::debug::TaskAnnotator::RunTask0f chrome_7fee4550000!base::MessageLoop::RunTask10 chrome_7fee4550000!base::MessageLoop::DeferOrRunPendingTask11 chrome_7fee4550000!base::MessageLoop::DoWork12 chrome_7fee4550000!base::MessagePumpForIO::DoRunLoop13 chrome_7fee4550000!base::MessagePumpWin::Run14 chrome_7fee4550000!base::MessageLoop::RunHandler15 chrome_7fee4550000!base::RunLoop::Run16 chrome_7fee4550000!base::MessageLoop::Run17 chrome_7fee4550000!base::Thread::Run18 chrome_7fee4550000!content::BrowserThreadImpl::IOThreadRun19 chrome_7fee4550000!content::BrowserThreadImpl::Run1a chrome_7fee4550000!base::Thread::ThreadMain1b chrome_7fee4550000!base::`anonymous namespace'::ThreadFunc1c kernel32!BaseThreadInitThunk1d ntdll!RtlUserThreadStart
在HttpNetworkTransaction完成的时候会调用callback,这个是在启动HttpNetworkTransaction请求的时候设置的,完成时回调到HttpCache::transaction,进行数据的转储处理。
源码分析
调用过程
// 1. Not-cached entry:// Start():// GetBackend* -> InitEntry -> OpenEntry* -> CreateEntry* -> AddToEntry* ->// SendRequest* -> SuccessfulSendRequest -> OverwriteCachedResponse ->// CacheWriteResponse* -> TruncateCachedData* -> TruncateCachedMetadata* ->// PartialHeadersReceived//// Read():// NetworkRead* -> CacheWriteData*
我们回顾一下情况一,如上,我们收到了HttpNetworkTransaction请求完成后,会重写缓存,但是我们的缓存中并没有这一项,就没有实际的重写操作,而是进行下一状态,STATE_CACHE_WRITE_RESPONSE.
int HttpCache::Transaction::DoCacheWriteResponse() { next_state_ = STATE_CACHE_WRITE_RESPONSE_COMPLETE; return WriteResponseInfoToEntry(truncated_);}
这一状态的处理就是实际的写响应信息到HttpCache的Entry中。
int HttpCache::Transaction::WriteResponseInfoToEntry(bool truncated) { if (!entry_) return OK;...... bool skip_transient_headers = true; scoped_refptr<PickledIOBuffer> data(new PickledIOBuffer()); response_.Persist(data->pickle(), skip_transient_headers, truncated); data->Done(); io_buf_len_ = data->pickle()->size(); return entry_->disk_entry->WriteData(kResponseInfoIndex, 0, data.get(), io_buf_len_, io_callback_, true);}
在这个函数中实际的写数据操作通过Entry来实现,将数据写到disk_cache中。
转储小结
就是这样,当HttpCache中没有有效数据的时候,就通过HttpNetworkTransaction请求网络数据,当网络数据请求完毕时,回调到HttpCache::transaction中将数据存储到HttpCache中,以便下次读取。
样本的版本信息
chrome 50.0.2661.87
符号路径:
https://chromium-browser-symsrv.commondatastorage.googleapis.com
源码网站:
www.chromium.org
感谢chromium项目相关作者及人员。
- chromium中HTTP网络资源的加载过程
- chromium中FTP网络资源的加载
- chromium中FTP网络资源的加载
- 关于 Xcode7中不能加载网络资源的问题App Transport Security has blocked a cleartext HTTP (http://) resource
- Chromium扩展(Extension)的Content Script加载过程分析
- Chromium扩展(Extension)的Content Script加载过程分析
- Android WebView加载Chromium动态库的过程分析
- Android中webview加载网络资源
- bee中下载网络资源时,加载转圈的进度条
- chromium网络资源加载分析(二) 主资源加载逻辑分析 之head部分加载---chromium39
- chromium网络资源加载分析(三) 主资源加载逻辑分析 之body部分加载---chromium39
- chromium网络资源加载分析(一) 主资源加载逻辑分析 ---chromium39
- Chromium网页URL加载过程分析
- Chromium扩展(Extension)加载过程分析
- Chromium网页URL加载过程分析
- Android Chromium动态库加载过程
- chromium源码编译过程中出现的问题及解决方法
- chromium的gyp构建过程
- java第一周
- React Native的WebStorm基本设置
- Ubiquitous Religions ---并查集入门
- 阶乘 hdu 1124 (Factorial)
- (四十一)、设计模式
- chromium中HTTP网络资源的加载过程
- xml解析pull
- dll工程中如何生成lib文件
- android zipalign
- java 时间格式转换
- ubuntu下不能识别安卓设备
- JAVA简单Factory模式
- USB OTG插入检测识别
- React Native之hellWord