1. 程式人生 > >Chrome原始碼分析之socket(一)

Chrome原始碼分析之socket(一)

 
作為對HTTP連線的分析,首先跟蹤一下Chrome對一個新的URL請求的處理流程。
 
從Chrome的實現來看,對一個URL資源的請求是放在Browser程序中來實現的,而不是由各個Render程序來實現,據說開發文件中提到這樣做的三個主要優勢,一是避免子程序進行網路通訊,增加安全性,二是有利於Cookie等持久化資源在不同頁面中的共享,否則在不同Render程序中傳遞Cookie比較麻煩,第三是由Browser統一進行網路通訊可以減少HTTP連線的數量。
一般來說,當你在位址列輸入URL地址並且回車確認之後,由於AutocompleteEditViewWin繼承自ATL的CRichEditCtrl類,因而他也擁有了訊息對映的能力,在訊息對映的控制下,最終OnKeyDownOnlyWritable函式被呼叫了,OnKeyDownOnlyWritable裡面的通過switch對各種按鍵分別處理,如果是回車鍵,就呼叫model_->AcceptInput,這個函式要對傳入的URL地址做一些簡單判斷,由於中間的呼叫過程比較漫長,這裡不對每個函式進行分析了,只把呼叫流程列出來:
src\chrome\browser\autocomplete\autocomplete_edit.cc         #351view_->OpenURL
src\chrome\browser\autocomplete\autocomplete_edit_view_win.cc #612 model_->OpenURL
src\chrome\browser\autocomplete\autocomplete_edit.cc         #612 model_->OpenURL,
src\chrome\browser\autocomplete\autocomplete_edit.cc         #412 controller_->OnAutocompleteAccept。
src\chrome\browser\views\location_bar\location_bar_view.cc #791 command_updater_->ExecuteCommand(IDC_OPEN_CURRENT_URL);
src\chrome\browser\command_updater.cc                                  #45 delegate_->ExecuteCommand(id);
src\chrome\browser\browser.cc                                                      #2318 ExecuteCommandWithDisposition(id, CURRENT_TAB);
src\chrome\browser\browser.cc                                                      #1245 browser::Navigate(&params);
src\chrome\browser\browser_navigator.cc                                   #324 params->target_contents->controller().LoadURL
src\chrome\browser\tab_contents\navigation_controller.cc      #496 LoadEntry(entry);
src\chrome\browser\tab_contents\navigation_controller.cc      #282 NavigateToPendingEntry(NO_RELOAD);
src\chrome\browser\tab_contents\navigation_controller.cc      #1039 tab_contents_->NavigateToPendingEntry(reload_type)
src\chrome\browser\tab_contents\tab_contents.cc                     #853 NavigateToEntry(*controller_.pending_entry(), reload_type);
src\chrome\browser\tab_contents\tab_contents.cc                     #891 dest_render_view_host->Navigate(navigate_params);
src\chrome\browser\renderer_host\render_view_host.cc         #250 Send(nav_message);
我們看看最後2行, dest_render_view_host->Navigate(navigate_params);發生在TabContents::NavigateToEntry函式中,dest_render_view_host是一個指標,在函式開始的時候通過呼叫render_manager_.Navigate(entry)得到,我們知道在Chrome的程序模型裡面,Browser程序管理其他程序和頁面,一個程序就是一個RenderProcessHost例項,一個頁面就是一個RenderViewHost例項。而頁面實際上是承載在RenderProcess上的,一個RenderProcess上可以有多個RenderView。render_manager_是TabContents中負責管理RenderViewHost的類,這裡它返回一個可用的RenderViewHost例項,RenderViewHost收集於此次URL請求相關的所有資訊,生成一個訊息並通過IPC機制傳送給對於的RenderProcess,在Send之前的函式呼叫都發生在Browser程序的MainThread中,當需要傳送訊息給RenderProcess的時候則進入了I/O Thread 的領域內。至於IPC機制我們留到以後再分析。
訊息傳送出去以後,接收訊息的應該是RenderProcess的RenderThread和RenderView,訊息首先在RenderProcess裡面經過分類和路由,後來傳到給了RenderView的
OnMessageReceived函式,下面繼續列出流程:
src\chrome\renderer\render_view.cc                                                    #764 IPC_MESSAGE_HANDLER(ViewMsg_Navigate, OnNavigate)
src\chrome\renderer\render_view.cc                                                    #1226 main_frame->loadRequest(request);
src\third_party\WebKit\WebKit\chromium\src\WebFrameImpl.cpp #883 m_frame->loader()->load(resourceRequest, false);
src\third_party\WebKit\WebCore\loader\FrameLoader.cpp              #1362  load(request, SubstituteData(), lockHistory);
src\third_party\WebKit\WebCore\loader\FrameLoader.cpp              #1375 load(loader.get());
src\third_party\WebKit\WebCore\loader\FrameLoader.cpp              #1433 loadWithDocumentLoader(newDocumentLoader, type, 0);
src\third_party\WebKit\WebCore\loader\FrameLoader.cpp              #1477 continueLoadAfterNavigationPolicy(loader->request(), formState, false);
src\third_party\WebKit\WebCore\loader\FrameLoader.cpp              #2975 continueLoadAfterWillSubmitForm();
src\third_party\WebKit\WebCore\loader\FrameLoader.cpp              #2463 m_provisionalDocumentLoader->startLoadingMainResource(identifier)
src\third_party\WebKit\WebCore\loader\DocumentLoader.cpp       #765 m_mainResourceLoader->load(m_request, m_substituteData)
src\third_party\WebKit\WebCore\loader\MainResourceLoader.cpp#583 loadNow(request)
src\third_party\WebKit\WebCore\loader\MainResourceLoader.cpp #556 ResourceHandle::create
src\third_party\WebKit\WebKit\chromium\src\ResourceHandle.cpp      #223 newHandle->start(context)
src\third_party\WebKit\WebKit\chromium\src\ResourceHandle.cpp      #251 d->start();
src\third_party\WebKit\WebKit\chromium\src\ResourceHandle.cpp      #111 m_loader->loadAsynchronously(wrappedRequest, this);
src\webkit\glue\weburlloader_impl.cc                               #717 context_->Start(request, NULL);  
src\webkit\glue\weburlloader_impl.cc                               #476 bridge_->Start(this)
src\chrome\common\resource_dispatcher.cc                           #186 dispatcher_->message_sender()->Send
到了這裡,通過IPC機制將頁面渲染的請求再封裝為一個訊息,傳送給Browser程序。當然在呼叫dispatcher_->message_sender()->Send之前程式碼依然在RenderThread上執行,不過訊息的傳送實際上是在MainThread中進行的,其中的原因在IPC機制中再行分析。
作為接受訊息的Browser程序,這個訊息首先I/O Thrad中被處理,然後轉發給MainThread,MainThread再進行一些處理最後對映到ResourceDispatcherHost的OnMessageReceived函式。

src\chrome\browser\renderer_host\resource_dispatcher_host.cc       #542 BeginRequestInternal(request);
src\chrome\browser\renderer_host\resource_dispatcher_host.cc       #1310 InsertIntoResourceQueue(request, *info);
src\chrome\browser\renderer_host\resource_dispatcher_host.cc       #1316 resource_queue_.AddRequest(request, request_info);
src\chrome\browser\renderer_host\resource_queue.cc                 #62 request->Start();
src\net\url_request\url_request.cc                                 #307  StartJob(GetJobManager()->CreateJob(this));
src\net\url_request\url_request.cc                                 #336 job_->Start();
src\net\url_request\url_request_http_job.cc                        #159 AddCookieHeaderAndStart();
src\net\url_request\url_request_http_job.cc                        #739 OnCanGetCookiesCompleted(policy);
src\net\url_request\url_request_http_job.cc                        #460  StartTransaction();
src\net\url_request\url_request_http_job.cc                        #632 transaction_->Start
src\net\http\http_network_transaction.cc                           #144 DoLoop(OK);
src\net\http\http_network_transaction.cc                           #446 DoCreateStream();
src\net\http\http_network_transaction.cc                           #449 DoCreateStreamComplete(rv);
src\net\http\http_network_transaction.cc                           #456 DoInitStreamComplete(rv);
src\net\http\http_network_transaction.cc                           #463 DoGenerateProxyAuthTokenComplete(rv);
src\net\http\http_network_transaction.cc                           #470 DoGenerateServerAuthTokenComplete(rv);
src\net\http\http_network_transaction.cc                           #475 DoSendRequest();
src\net\http\http_network_transaction.cc                           #646 stream_->SendRequest
src\net\http\http_basic_stream.cc                                  #48 parser_->SendRequest
src\net\http\http_stream_parser.cc                                 #63 DoLoop(OK)
src\net\http\http_stream_parser.cc                                 #145 DoSendHeaders(result);
src\net\http\http_stream_parser.cc                                 #228 connection_->socket()->Write
 
request->Start()函式這裡進入網路通訊部分,從connection_->socket()->Write開始詳細分析。
這裡的Write呼叫實際上是TCPClientSocketWin類的成員函式,函式傳入的三個引數分別是傳送快取buf,快取長度buf_len,回撥函式指標callback。
 
接著用下面的程式碼將傳送資料儲存起來
 
  core_->write_buffer_.len = buf_len;
  core_->write_buffer_.buf = buf->data();
  core_->write_buffer_length_ = buf_len;
core_是TCPClientSocketWin的成員變數,實際上是一個類,在TCPClientSocketWin讀寫網路資料的過程中發揮這核心作用,先看看core_的定義:
class TCPClientSocketWin::Core : public base::RefCounted<Core> {
 public:
  explicit Core(TCPClientSocketWin* socket);
  void WatchForRead();
  void WatchForWrite();
  void Detach() { socket_ = NULL; }
  OVERLAPPED read_overlapped_;
  OVERLAPPED write_overlapped_;
  WSABUF read_buffer_;
  WSABUF write_buffer_;
  scoped_refptr<IOBuffer> read_iobuffer_;
  scoped_refptr<IOBuffer> write_iobuffer_;
  int write_buffer_length_;
  int ThrottleReadSize(int size) {
    if (slow_start_throttle_ < kMaxSlowStartThrottle) {
      size = std::min(size, slow_start_throttle_);
      slow_start_throttle_ *= 2;
    }
    return size;
  }
 private:
  friend class base::RefCounted<Core>;
  class ReadDelegate : public base::ObjectWatcher::Delegate {
   public:
    explicit ReadDelegate(Core* core) : core_(core) {}
    virtual ~ReadDelegate() {}
    virtual void OnObjectSignaled(HANDLE object);
   private:
    Core* const core_;
  };
  class WriteDelegate : public base::ObjectWatcher::Delegate {
   public:
    explicit WriteDelegate(Core* core) : core_(core) {}
    virtual ~WriteDelegate() {}
    virtual void OnObjectSignaled(HANDLE object);
   private:
    Core* const core_;
  };
  ~Core();
  TCPClientSocketWin* socket_;
  ReadDelegate reader_;
  WriteDelegate writer_;
  base::ObjectWatcher read_watcher_;
  base::ObjectWatcher write_watcher_;
  static const int kInitialSlowStartThrottle = 1 * 1024;
  static const int kMaxSlowStartThrottle = 32 * kInitialSlowStartThrottle;
  int slow_start_throttle_;
  DISALLOW_COPY_AND_ASSIGN(Core);
};
總的來說類Core的作用就是為基於事件通知非同步I/O的TCP連線提供和協調所有相關的事件物件,傳送接收快取,執行緒池控制以及回撥處理等相關功能。其中OVERLAPPED read_overlapped_和OVERLAPPED write_overlapped_分別是讀寫的重疊I/O控制變數,WSABUF read_buffer_和WSABUF write_buffer_維護讀寫的快取。

在Core還在類體中即時定義了2個私有類:class ReadDelegate : public base::ObjectWatcher::Delegate和class WriteDelegate : public base::ObjectWatcher::Delegate。這2個類都只有一個成員函式OnObjectSignaled,這個函式在Chrome自建執行緒的訊息迴圈中將被呼叫。至於OnObjectSignaled為什麼會被呼叫,這跟另外兩個成員變數base::ObjectWatcher read_watcher_和base::ObjectWatcher write_watcher_直接相關,後面再詳細分析。

我們接著全面分析一下TCPClientSocketWin類,先看看它是如何建立一個TCP連線,TCPClientSocketWin首先呼叫成員函式Connect,它只是簡單的設定next_connect_state_的狀態,以及給current_ai_賦值,current_ai_是一個addrinfo型別的結構體,addrinfo相容IPV4以及IPV6的地址資訊,因此我們可以透明化的處理這2個協議族的地址。接著呼叫DoConnectLoop這個函式,再看看DoConnectLoop的實現,只有一個簡單的迴圈,首先呼叫DoConnect,如果連線立即成功的話接著呼叫DoConnectComplete,否則返回一個ERR_IO_PENDING顯示連線請求處於異步出流流程中,或者是呼叫失敗,返回錯誤程式碼。

下面看看DoConnect()函式的實現,不僅僅因為在寫入資料之前需要呼叫connect來接連新連線,而且在這個函式中還 初始化了套介面的非同步I/O模型,下面看看這個函式的主要程式碼:

#1 int TCPClientSocketWin::DoConnect() {
#2 const struct addrinfo* ai = current_ai_;
#3   DCHECK(ai);
#4   DCHECK_EQ(0, connect_os_error_);
#5   if (previously_disconnected_) {
#6     use_history_.Reset();
#7    previously_disconnected_ = false;
#8  }
#9  net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT,
#10                       new NetLogStringParameter(
#11                          "address", NetAddressToStringWithPort(current_ai_)));//這裡寫入之日方便除錯
#12   next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE;
#13   connect_os_error_ = CreateSocket(ai); //建立新的socket
#14   if (connect_os_error_ != 0)
#15     return MapWinsockError(connect_os_error_);
#16   DCHECK(!core_);
#17   core_ = new Core(this);    //建立一個core_
#18   core_->read_overlapped_.hEvent = WSACreateEvent(); //建立重疊I/O結構
#19   WSAEventSelect(socket_, core_->read_overlapped_.hEvent, FD_CONNECT);//初始化I/O模型,WSAEventSelect是一個非同步I/O模型,具體可以參考MSDN等材料的說明
#20   core_->write_overlapped_.hEvent = WSACreateEvent();
#21   if (!connect(socket_, ai->ai_addr, static_cast<int>(ai->ai_addrlen))) {//非同步模式下connect呼叫後立即返回
#22    NOTREACHED();
#23     if (ResetEventIfSignaled(core_->read_overlapped_.hEvent))
#24       return OK;
#25   } else {
#26     int os_error = WSAGetLastError();
#27    if (os_error != WSAEWOULDBLOCK) {
#28      LOG(ERROR) << "connect failed: " << os_error;
#29       connect_os_error_ = os_error;
#30       return MapConnectError(os_error);
#31     }
#32   }
#34   core_->WatchForRead(); //這裡安裝回調函式
#35   return ERR_IO_PENDING;

#36 }

在第十三行呼叫CreateSocket建立一個新的套介面,接著建立一個Core的物件core_,並給read_overlapped_和write_overlapped_分別建立用於非同步I/O的事件物件,在19行初始化WSAEventSelect模型,用於實現connect的非同步呼叫,根據微軟的文件,一旦呼叫了WSAEventSelect,目標套介面將轉為非阻塞模式,connect不再等待伺服器的響應而是立即返回,返回值是SOCKET_ERRORE,應該說connect永遠不會返回0,但是在這裡他們仍對返回0的情況做了處理,呼叫ResetEventIfSignaled來判斷事件物件是否處於已傳信狀態,還可以看到,connect與read共用了read_overlapped_,因此在Core::ReadDelegate::OnObjectSignaled函式中,會區分connect和read並分別呼叫相應的處理函式。最後34行呼叫core_->WatchForRead()講這個連線交給執行緒模型,如果連線成功的話,DidCompleteConnect將被呼叫,最後呼叫DoReadCallback通知上層的呼叫者。

 

相關推薦

Chrome原始碼分析socket

  作為對HTTP連線的分析,首先跟蹤一下Chrome對一個新的URL請求的處理流程。   從Chrome的實現來看,對一個URL資源的請求是放在Browser程序中來實現的,而不是由各個Render程序來實現,據說開發文件中提到這樣做的三個主要優勢,一是避免子程序進行網

java原始碼剖析socket

    不知不覺又到了新的的一週,時間在悄悄的溜走,所辛的是自己也在緩慢的推進著自己的學習計劃。      這周按照計劃檢視的是socket系列的相關類,儘管這之前就已經看過一遍,不過當時是越看越蒙,完全找不到北。 隨著自己能力的提升,回過頭來又去看一遍,還是看不懂其中的精

kubernetes 原始碼分析ingress

kubernetes的服務對外暴露通常有三種方式分別為nodeport、loadbalancer和ingress。nodeport很容易理解就是在每個主機上面啟動一個服務埠暴露出去,這樣弊端是造成埠浪費;loadbalancer這種方式目前只能在gce的平臺跑的

Java集合原始碼分析Queue:超級介面Queue_一點課堂多岸學院

在日常生活中,排隊幾乎隨處可見,上地鐵要排隊,買火車票要排隊,就連出門吃個大餐,也要排隊。。。之前研究的ArrayList就像是一

Dubbo原始碼分析 SPI

一、概述     dubbo SPI 在dubbo的作用是基礎性的,要想分析研究dubbo的實現原理、dubbo原始碼,都繞不過 dubbo SPI,掌握dubbo SPI 是征服dubbo的必經之路。     本篇文章會詳細介紹dubbo SPI相關的內容,

資料庫中介軟體 Sharding-JDBC 原始碼分析 —— SQL 解析詞法解析

本文主要基於 Sharding-JDBC 1.5.0 正式版 ������關注微信公眾號:【芋道原始碼】有福利: 1. RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表 2. Roc

精盡Spring MVC原始碼分析 - HandlerMapping 元件 AbstractHandlerMapping

> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring

精盡Spring MVC原始碼分析 - HandlerAdapter 元件 HandlerAdapter

> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring

java集合類源碼分析List

col 實現類 並且 link arraylist oar print 適用於 for   首先分析一下集合與數組的區別:1.java中的數組一般用於存儲基本數據類型,而且是靜態的,即長度固定不變,這就不適用於元素個數未知的情況;2.集合只能用於存儲引用類型,並且長度可變,

【集合框架】JDK1.8源碼分析HashMap 轉載

.get 修改 object set implement .com 功能 數組元素 帶來 一、前言   在分析jdk1.8後的HashMap源碼時,發現網上好多分析都是基於之前的jdk,而Java8的HashMap對之前做了較大的優化,其中最重要的一個優化就是桶中

Lua原始碼分析 Gc篇原理

前言 原理 mark階段 sweep階段 三種顏色 資料流 參考 前言 已經有很多人寫了gc原始碼分析的文章了,自己為啥還要繼續寫呢?最主要的原因有兩個: 1.純屬對於個人來說,寫東西能夠加深自己的理解和記

開源網站流量統計系統Piwik原始碼分析——引數統計

  Piwik現已改名為,這是一套國外著名的開源網站統計系統,類似於百度統計、Google Analytics等系統。最大的區別就是可以看到其中的原始碼,這正合我意。因為我一直對統計的系統很好奇,很想知道里面的執行原理是怎麼樣的,碰巧了解到有這麼一個系統,因此馬上嘗試了一下。國內關於該系統的相關資料比較匱乏,

JUC原始碼分析-集合篇:ConcurrentHashMap

ConcurrentHashMap 是一個支援併發檢索和併發更新的執行緒安全的HashMap(但不允許空key或value)。不管是在實際工作或者是面試中,ConcurrentHashMap 都是在整個JUC集合框架裡出現頻率最高的一個類,所以,對ConcurrentHas

ArrayList集合JDK1.8 【集合框架】JDK1.8原始碼分析ArrayList

簡述   List是繼承於Collection介面,除了Collection通用的方法以外,擴充套件了部分只屬於List的方法。   常用子類  ?ArrayList介紹 1.資料結構   其底層的資料結構是陣列,陣列元素型別為Object型別,即可以存放所

SpringOauth2.0原始碼分析儲存

1.概述 前面幾個章節所述內容如下: 本章節主要敘說Token的儲存情況。預設的情況下,SpringOauth2.0 提供4種方式儲存。第一種是提供了基於mysql的儲存,第二種是基於redis的儲存。第三種基於jvm的儲存,第四種基於Jwt的儲存方式。這裡我

Spring 原始碼分析(三) —— AOPAOP原理

AOP概論         AOP(Aspect-Oriented Programming,面向切面的程式設計),談起AOP,則一定會追溯到OOP(Object Oriented Programming,面向物件程式設計),因為AOP可以說是對OOP的補充和完善,而這一切的

看透SpringMVC原始碼分析與實踐

一、網站架構及其演變過程   1.軟體的三大型別          軟體分為三個型別:單機軟體、BS結構的軟體(瀏覽器-服務端)、CS結構的軟體(客戶端-服務端)。 2.BS的基礎結構     &nb

springMVC原始碼分析--國際化LocaleResolver

        springMVC給我們提供了國際化支援,簡單來說就是設定整個系統的執行語言,然後根據系統的執行語言來展示對應語言的頁面,一般我們稱之為多語言。springMVC國際化機制就是可以設定整個系統的執行語言,其定義了一個國際化支援介面LocaleResolver

SPRING原始碼學習

結合《Spring技術內幕:深入解析SPRING架構與設計原理》這本書開啟Spring學習之路。 ps:之前其實已經看過一部分了,但是也就是看過,一看而過了。o(╯□╰)o 結合FileSystemXmlApplicationContext來分析  具體實現如下: /

elasticsearch原始碼分析Transport

一、基本介紹 1.1概念介紹 transport模組是es通訊的基礎模組,在elasticsearch中用的很廣泛,比如叢集node之間的通訊、資料的傳輸、transport client方式的資料傳送等等,只要數和通訊、資料傳輸相關的都離不開transport模組的