chromium net庫設計 頂 原

總覽

網絡棧主要地是一個單線程跨平臺的庫,主要負責資源獲取。它的主要接口是URLRequestURLRequestContextURLRequest, 正如它的名字所表明的那樣,表示一個URL的請求。URLRequestContext包含實現URL請求所需的所有相關上下文,比如cookies,主機解析器,代理解析器,cache,等等。多個URLRequest對象可以共享相同的URLRequestContext。大多數的net對象不是線程安全的,儘管磁盤緩存可以使用一個專門的線程,而一些組件(主機解析,證書驗證等等)可以使用unjoined worker線程。由於它主要運行於一個單獨的網絡線程上,因而在網絡線程上的操作都不允許阻塞。所以我們通過異步的回調(典型的是CompletionCallback)來使用非阻塞操作。網絡棧的代碼也會把大多數操作記錄到NetLog,它允許消費者把表示操作的說明記錄到內存中,並以用戶友好的格式用於調試中。

Chromium開發者編寫網絡棧的目的是:

  • abstractions允許編寫跨平臺的抽象。
  • 相對於高層的系統網絡庫(WinHTTP)可提供的,提供更好的控制。
    • 避免系統庫中可能出現的bugs。
    • 方便更好地做性能優化。

代碼結構

net/base - net實用例程的百寶袋,比如主機解析,cookies,網絡改變探測,SSL。 net/disk_cache - Web資源緩存 net/ftp - FTP實現。這些代碼主要是基於老的HTTP實現的 net/http - HTTP實現。 net/ocsp - 當不使用系統庫或系統沒有提供一個OCSP實現時的OCSP實現。當前只包含一個基於NSS的實現。 net/proxy - 代理 (SOCKS和HTTP) 配置,解析,腳本獲取,等等。 net/quic - QUIC實現。 net/socket - TCP sockets,"SSL sockets",和socket池的跨平臺實現。 net/socket_stream - WebSockets的socket streams。 net/spdy - SPDY實現。 net/url_request - URLRequest,URLRequestContext,和URLRequestJob的實現。 net/websockets - WebSockets實現。

網絡請求剖析(集中於HTTP)

Chromium HTTP Network Request Diagram

URLRequest

class URLRequest {
 public:
  // Construct a URLRequest for |url|, notifying events to |delegate|.
  URLRequest(const GURL& url, Delegate* delegate);
  
  // Specify the shared state
  void set_context(URLRequestContext* context);

  // Start the request.  Notifications will be sent to |delegate|.
  void Start();

  // Read data from the request.
  bool Read(IOBuffer* buf, int max_bytes, int* bytes_read);
};

class URLRequest::Delegate {
 public:
  // Called after the response has started coming in or an error occurred.
  virtual void OnResponseStarted(...) = 0;

  // Called when Read() calls complete.
  virtual void OnReadCompleted(...) = 0;
};

當啓動一個URLRequest時,它要做的第一件事就是決定要創建何種類型的URLRequestJob。主要的job類型是URLRequestHttpJob,它用於實現http://請求。還有許多其它的jobs,比如URLRequestFileJob  (file://),URLRequestFtpJob  (ftp://),URLRequestDataJob  (data://),等等。網絡棧將確定適當的job來實現請求。但它給客戶端提供了兩種方式來定製job的創建:URLRequest::Interceptor和URLRequest::ProtocolFactory。這些相當地多餘,除了URLRequest::Interceptor的接口更具可擴展性。在job進行的過程中,它將通知URLRequest,而後者則在需要時通知URLRequest::Delegate。

URLRequestHttpJob

URLRequestHttpJob將首先確定要給HTTP請求設置的cookies,這需要查詢請求上下文中的CookieMonster。這可以是異步的,因爲CookieMonster可能是由一個sqlite數據庫支持的。做完了這些之後,它將請求請求上下文的HttpTransactionFactory來創建一個HttpTransaction。典型地, HttpCache將被指定爲HttpTransactionFactory。HttpCache將創建一個HttpCache::Transaction來處理HTTP請求。HttpCache::Transaction將首先檢查HttpCache (它會檢查磁盤緩存)來查看緩存項是否已經存在。如果存在,則意味着響應已經被緩存了,或者這個緩存項的一個網絡事物已經存在,則只是從那個項中讀取。如果緩存項不存在,則我們創建它並讓HttpCache的HttpNetworkLayer給請求的服務創建一個HttpNetworkTransaction。給HttpNetworkTransaction一個包含執行HTTP請求的上下文狀態的HttpNetworkSession。這些狀態中的一些來自於URLRequestContext

HttpNetworkTransaction

class HttpNetworkSession {
 ...

 private:
  // Shim so we can mock out ClientSockets.
  ClientSocketFactory* const socket_factory_;
  // Pointer to URLRequestContext's HostResolver.
  HostResolver* const host_resolver_;
  // Reference to URLRequestContext's ProxyService
  scoped_refptr<ProxyService> proxy_service_;
  // Contains all the socket pools.
  ClientSocketPoolManager socket_pool_manager_;
  // Contains the active SpdySessions.
  scoped_ptr<SpdySessionPool> spdy_session_pool_;
  // Handles HttpStream creation.
  HttpStreamFactory http_stream_factory_;
};

HttpNetworkTransaction請求HttpStreamFactory創建一個HttpStream

HttpStreamFactory返回一個HttpStreamRequest,它被期望處理包括如何建立連接,及一旦連接建立則把它包裝爲一個與網絡直接對話的居間HttpStream的子類等所有的邏輯。

class HttpStream {
 public:
  virtual int SendRequest(...) = 0;
  virtual int ReadResponseHeaders(...) = 0;
  virtual int ReadResponseBody(...) = 0;
  ...
};

當前,只有兩種主要的HttpStream子類:HttpBasicStreamSpdyHttpStream,儘管我們已經在計劃爲HTTP pipelining創建子類。HttpBasicStream假設它在直接地讀/寫一個socket。SpdyHttpStream讀和寫一個SpdyStream。網絡事務將會調用流上的方法,並且在完成時,將會調用回調回到HttpCache::Transaction,而它將會根據需要通知URLRequestHttpJob和URLRequest。對於HTTP路徑,http請求和響應的產生及解析將由HttpStreamParser處理。對於SPDY路徑,請求和響應的解析由SpdyStreamSpdySession來處理。基於HTTP響應,HttpNetworkTransaction可能需要執行 HTTP 認證。這可能包括重啓網絡事務。

HttpStreamFactory

HttpStreamFactory首先做代理解析來決定是否需要一個代理。端點被設置爲URL主機或代理服務器。然後HttpStreamFactory檢查SpdySessionPool來查看我們是否有這個端點的可用SpdySession。如果沒有,則stream factory從適當的池中請求一個 "socket"(TCP/proxy/SSL/etc) 。如果socket是一個SSL socket,則它檢查NPN是否指示了一個協議(可能是SPDY),如果是,則使用特定的協議。對於SPDY,我們將檢查一個SpdySession是否已經存在,如果是則使用它,否則我們將由這個SSL socket創建一個新的SpdySession,並由SpdySession創建一個SpdyStream,其中包裝一個SpdyHttpStream。對於HTTP,我們將簡單地獲取socket,並把它包裝進一個HttpBasicStream

代理解析

HttpStreamFactory查詢ProxyService來給GURL返回ProxyInfo。代理服務首先需要檢查它是否有一個最近的代理配置。如果沒有,它使用ProxyConfigService來爲當前的代理設置查詢系統。如果代理設置被設置爲沒有代理或一個特定的代理,則代理解析是很簡單的(我們返回沒有代理或特定的代理)。否則,我們需要運行PAC script來確定合適的代理(或lack thereof)。如果我們還沒有PAC script,則代理設置將指示我們應該使用WPAD auto-detection,或將指定一個定製PAC url,然後我們將通過ProxyScriptFetcher獲取PAC script。一旦我們有了PAC script,我們將通過ProxyResolver執行它。注意我們使用一個shim MultiThreadedProxyResolver對象來把PAC script執行派發給線程,它們將運行一個ProxyResolverV8實例。這是由於PAC script執行可能阻塞主機解析。然而,爲了防止一個失速的PAC script執行阻塞了其它的代理解析,我們允許併發地執行多個PAC script(警告: V8不是線程安全的,因此我們要爲javascript bindings獲取鎖,以使得一個V8 script實例阻塞在主機解析,它釋放鎖使另一個V8實例可以執行PAC script來爲不同的URL解析代理)。

連接管理

HttpStreamRequest確定了適當的端點(URL端點或代理端點)之後,它需要建立一個連接。它通過確認適當的"socket"池並從中請求一個socket來做到這一點。注意,這裏的"socket"基本上表示我們可以讀和寫以在網絡上發送數據的東西。一個SSL socket是在一個傳輸(TCP) socket之上構建的,併爲用戶加密/解密原始的TCP數據。不同的socket類型還處理不同的連接設置,對於HTTP/SOCKS代理,SSL握手,等等。Sockets池被設計爲層次結構,因而不同的連接設置可能被放在其它sockets的上層。HttpStream的實際的底層socket類型可能是不可知的,由於它僅僅需要對socket做讀和寫。Socket池執行不同的功能。它們實現我們的單個代理,單個主機和單個進程的連接限制。當前單個代理的限制被設置爲32個sockets,單個目標主機被設置爲6 sockets,單個進程被設置爲256個sockets(沒有被完全正確的實現,但足夠好)。Socket池也從實現抽象了sockets請求,從而給我們提供sockets的"late binding"。一個socket請求可以通過一個全新的連接的socket或一個idle socket(從一個之前http事務複用)來滿足。

主機解析

注意傳輸sockets的連接設置不僅僅需要傳輸(TCP)握手,也可能需要主機解析。HostResolverImpl使用getaddrinfo()來執行主機解析,這是一個阻塞調用,因而解析器在unjoined worker線程上調用這些功能。典型地,主機解析通常包括DNS解析,但可能包括非DNS命名空間,比如NetBIOS/WINS。注意,自寫入的事件起,我們將併發的主機解析的數量限制爲8,但我們也在尋求優化這個值。HostResolverImpl也包含了一個HostCache,其中緩存最多1000個主機名。

SSL

SSL sockets需要執行SSL連接設置和證書驗證。當前,在所有的平臺上,我們使用NSS的libssl來處理SSL連接邏輯。然而,我們使用平臺特有的APIs來做證書驗證。我們也將使用一個證書驗證緩存,這將把多個對相同證書的證書驗證請求聯合爲一個單獨的證書驗證任務,也將把結果緩存一段時間。

SSLClientSocketNSS嚴格地依照這個事件順序(忽略高級的功能,如基於證書驗證的Snap StartDNSSEC based certificate verification):

  • 調用Connect()。我們將基於SSLConfig描述的配置和預處理器宏設置NSS的SSL選項。然後我們啓動握手。
  • 握手完成。假設我們沒有遇到任何錯誤,我們將使用CertVerifier驗證服務器的證書。證書驗證可能會消耗一些時間,因而CertVerifier使用了WorkerPool來實際地調用X509Certificate::Verify(),這使用平臺特有的APIs來實現。

注意chromium具有它自己的NSS程序,它支持一些高級的特性,一些在系統的NSS模塊中不需要的特性,比如支持NPNFalse Start,Snap Start,OCSP stapling,etc。

TODO: talk about network change notifications

原文鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章