使用QUIC

QUIC是Google新開發的一個基於UDP的協議,它提供了像TCP同樣的傳輸可靠性保證,能夠實現數據傳輸的0-RTT延遲,靈活的設計使咱們能夠對它的擁塞控制及流量控制作更多的定製,它還提供了傳輸的安全性保障,以及像HTTP/2同樣的應用數據二進制分幀傳輸。html

而QUIC協議最最吸引人的特性有兩點,一是對隊首阻塞問題的解決更爲完全。基於TCP的HTTP/2,儘管從邏輯上來講,不一樣的流之間相互獨立,不會相互影響,但在實際傳輸方面,數據仍是要一幀一幀的發送和接收,一旦某一個流的數據有丟包,則一樣會阻塞在它以後傳輸的其它與它絕不相干的流的數據的傳輸。而基於UDP的QUIC協議則能夠更爲完全地解決這樣的問題,讓不一樣的流之間真正的實現相互獨立傳輸,互不干擾。linux

另外一個特性切換網絡時的鏈接保持。當前移動端的應用環境,用戶的網絡可能會常常切換,好比從辦公室或家裏出門,WiFi斷開,網絡切換爲3G或4G。基於TCP的協議,因爲切換網絡以後,IP會改變,於是以前的鏈接不可能繼續保持。而基於UDP的QUIC協議,則能夠內建與TCP中不一樣的鏈接標識方法,從而在網絡完成切換以後,恢復以前與服務器的鏈接。android

因爲這些良好的特性,QUIC協議已經有在gmail中獲得了大量的應用。打開Wireshark,隨便對某個網卡抓包,都能看到大量的QUIC協議包:git

這裏咱們來跑一下QUIC。github

選擇一份QUIC代碼

下面的說明是用來基於chromium代碼庫編譯QUIC代碼。在Chrome支持的任何平臺上,這裏的說明都能保證是有效的,遇到問題時能夠查看一些擴展的故障排查的文檔。若是不想下載整個chromium代碼庫,則能夠嘗試github上快速而乾淨的proto-quic庫。這是chromium中的代碼的一份克隆,但剔除了大多數沒必要要的依賴,於是下載它要快得多,編譯也更快,但不必定在全部的平臺上都能用。事實上,github上的proto-quic庫,當前只支持在Ubuntu linux上編譯。chrome

編譯QUIC客戶端和服務器

Chromium中提供了一個示例客戶端和服務器實現。要使用這些東西,你首先應該已經下載Chromium的源代碼,而後構建二進制文件:json

ninja -C out/Debug quic_server quic_client

這就像編譯chromium的任何模塊同樣。能夠參考 懶人chromium net android移植指南 來對Chromium的構建系統作更多瞭解。瀏覽器

若是條件容許,github上的proto-quic庫,編譯起來也很簡單快捷。首先須要下載代碼及構建依賴的整個工具鏈:緩存

git clone https://github.com/google/proto-quic.gitcd proto-quicexport PATH=$PATH:`pwd`/depot_tools
./proto_quic_tools/sync.sh

而後編譯就是了,與普通的chromium模塊編譯同樣:安全

cd src
gn gen out/Default && ninja -C out/Default quic_client quic_server net_unittests

從www.example.org準備測試數據

下載一份www.example.org的拷貝,它主要是給quic_server二進制可執行文件用來提供本地服務的:

mkdir ~/quic-data
cd ~/quic-data
wget -p --save-headers https://www.example.org

這裏主要是要下載一個html文件,且保存文件的全部的HTTP header,固然也能夠從其它的站點下載這個文件。

手動地編輯index.html,並調整以下的headers:

  • 移除(若是存在的話):"Transfer-Encoding: chunked"

  • 移除(若是存在的話):"Alternate-Protocol: ..."

  • 添加:X-Original-Url: https://www.example.org/

生成證書

爲了運行服務器,須要一個有效的證書,及一個pkcs8格式的私有key。若是沒有,則可使用腳原本產生它們:

cd net/tools/quic/certs
./generate-certs.shcd -

除了服務器的證書及public key,這個腳本也會產生一個CA證書 (net/tools/quic/certs/out/2048-sha256-root.pem),須要把它添加到操做系統的根證書商店以便於在證書驗證期間它被信任。

一種比較簡單的管理證書的方法是使用chrome瀏覽器。在地址欄中輸入 chrome://settings/search#ssl,而後點擊「管理證書」:


在彈出的窗口中選擇「導入...」,而後按照提示一步步將證書導入便可。


在linux上管理證書相關的更多信息,請參考 這些說明

若是遺漏了這裏的添加CA證書的步驟的話,後面在執行quic_client的時候會報出以下的證書驗證錯誤:

$ ./out/Default/quic_client --host=127.0.0.1 --port=80 https://www.example.org/[1008/164047:ERROR:cert_verify_proc_nss.cc(942)] CERT_PKIXVerifyCert for www.example.org failed err=-8179[1008/164047:WARNING:proof_verifier_chromium.cc(466)] Failed to verify certificate chain: net::ERR_CERT_AUTHORITY_INVALID
Failed to connect to 127.0.0.1:80. Error: QUIC_PROOF_INVALID

運行QUIC服務器和客戶端

運行quic_server:

./out/Default/quic_server \
 --quic_in_memory_cache_dir=~/quic-data/www.example.org \
 --certificate_file=net/tools/quic/certs/out/leaf_cert.pem \
 --key_file=net/tools/quic/certs/out/leaf_cert.pkcs8

還能夠經過--port參數指定quic_server監聽的端口,及--v參數指定輸出更多信息,如:

./out/Default/quic_server --certificate_file=~/proto-quic/src/net/tools/quic/certs/out/leaf_cert.pem --key_file=~/proto-quic/src/net/tools/quic/certs/out/leaf_cert.pkcs8 --quic_in_memory_cache_dir=~/quic-data/www.example.com --port=32457 --v=1

quic_in_memory_cache_dir參數指定存放資源文件的目錄路徑。如咱們前面看到的,從www.example.org下載到頁面以後,須要調整headers,其中爲X-Original-Url設置的是相應資源的url。quic_server起來的時候,會加載該目錄下的文件。要爲quic_server添加其它的測試資源,也要注意正確的設置其headers。如json接口:

# cat testApi1 
HTTP/1.1 200 OK
Cache-Control: privateContent-Length: 3214Content-Type: application/json; charset=utf-8Server: Microsoft-IIS/7.5X-AspNet-Version: 4.0.30319X-Powered-By: ASP.NETDate: Thu, 13 Oct 2016 02:12:28 GMT
X-Original-Url: https://www.wolfcstech.com:6121/testApi1{"res":[{"ctime":"2016-10-11","title":"溫州","des":"國內","url":"http://news.163.com/16/1011/11/C33GFLIP0001124J.html"},{"ime":"2016","tit":"b","des":"a"}, {"ime":"2016","tit":"b","des":"a"}],"rea":"Su"}

而後就可使用quic_client以QUIC協議請求文件了:

./out/Default/quic_client --host=127.0.0.1 --port=32457 https://www.example.org/

注意,若是要讓服務器運行於端口32457上,則必須爲客戶端指定端口,由於它默認是80。

此外,若是本地機器有多個loopback地址 (因爲它同時使用IPv4 和 IPv6),則不得不選定一個地址。

目前還不肯定後面的缺點是否是一個bug。

注意:client和server都主要是爲了作集成測試的:它們都不能大規模使用。

要使用chrome來測試相同的下載過程,能夠執行:

chromium-browser \  --user-data-dir=/tmp/chrome-profile \
 --no-proxy-server \
 --enable-quic \
 --origin-to-force-quic-on=www.example.org:443 \
 --host-resolver-rules='MAP www.example.org:443 127.0.0.1:32457' \
 https://www.example.org

使用Cronet訪問QUIC服務

除了使用quic_client和Chrome瀏覽器以外,還能夠在移動端使用Cronet訪問QUIC服務。常規的使用Cronet訪問網絡服務的流程爲:

  1. 使用CronetEngine.Builder建立CronetEngine,也及URL請求上下文,設置是否啓用緩存,是否啓用HTTP/2等。

  2. 實現UrlRequest.Callback,用以接收請求執行的結果。

  3. 使用UrlRequest.Builder,之前面建立的CronetEngine和UrlRequest.Callback建立UrlRequest。

  4. 啓動UrlRquest的執行。

  5. 請求執行完成以後,在UrlRequest.Callback中處理請求執行結果。

訪問QUIC服務相對於常規的使用Cronet訪問網絡服務的流程的差異主要有如下幾點:

  • 在上面的第1部,建立CronetEngine時,要啓用QUIC,同時要爲CronetEngine添加Quic Hint。如:

             CronetEngine.Builder builder = new CronetEngine.Builder(context);
            builder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY,                         100 * 1024)
                    .enableHttp2(true)
                    .enableQuic(true)
                    .enableSDCH(true)
                    .setLibraryName("cronet")
                    .addQuicHint("www.example.org", 32457, 32457)
                    .addQuicHint("www.wolfcstech.com", 443, 6121)
                    .addQuicHint("www.wolfcstech.cn", 443, 6121)
                    .addQuicHint("www.wolfcstech.com", 6121, 6121)
                    .addQuicHint("www.wolfcstech.cn", 6121, 6121);
            mCronetEngine = builder.build();
  • 用以https爲scheme的URL訪問QUIC服務。如:

         String url = "https://www.wolfcstech.com:6121/testApi1";
        UrlRequest.Builder builder = new UrlRequest.Builder(url, callback, mExecutor, mCronetEngine);
        applyPostDataToUrlRequestBuilder(builder, mExecutor, postData);
        builder.build().start();

QuicHint是 (主機名, 端口號, 備選端口號) 的三元組。爲CronetEngine添加Quic Hint指示其在執行請求時,在以TCP協議鏈接 (主機名, 端口號) 並向其發送常規http或https請求的同時,以QUIC協議向 (主機名, 備選端口號) 請求相同的服務。

爲CronetEngine添加加Quic Hint,是將Quic Hint添加進了構造中的URLRequestContextConfig:

static void AddQuicHint(JNIEnv* env,                        const JavaParamRef<jclass>& jcaller,
                       jlong jurl_request_context_config,                        const JavaParamRef<jstring>& jhost,
                       jint jport,
                       jint jalternate_port) {
 URLRequestContextConfig* config =      reinterpret_cast<URLRequestContextConfig*>(jurl_request_context_config);
 config->quic_hints.push_back(
     base::WrapUnique(new URLRequestContextConfig::QuicHint(
         base::android::ConvertJavaStringToUTF8(env, jhost), jport,
         jalternate_port)));
}

在構造net::URLRequestContext時,相關的這些信息會被保存起來,以備後續訪問QUIC服務之用:

  if (config->enable_quic) {    for (auto hint = config->quic_hints.begin();
        hint != config->quic_hints.end(); ++hint) {      const URLRequestContextConfig::QuicHint& quic_hint = **hint;      if (quic_hint.host.empty()) {        continue;
     }

     url::CanonHostInfo host_info;
     std::string canon_host(net::CanonicalizeHost(quic_hint.host, &host_info));      if (!host_info.IsIPAddress() &&
         !net::IsCanonicalizedHostCompliant(canon_host)) {        continue;
     }      if (quic_hint.port <= std::numeric_limits<uint16_t>::min() ||
         quic_hint.port > std::numeric_limits<uint16_t>::max()) {        continue;
     }      if (quic_hint.alternate_port <= std::numeric_limits<uint16_t>::min() ||
         quic_hint.alternate_port > std::numeric_limits<uint16_t>::max()) {        continue;
     }

     url::SchemeHostPort quic_server("https", canon_host, quic_hint.port);
     net::AlternativeService alternative_service(
         net::AlternateProtocol::QUIC, "",          static_cast<uint16_t>(quic_hint.alternate_port));
     context_->http_server_properties()->SetAlternativeService(
         quic_server, alternative_service, base::Time::Max());
   }
 }

這種訪問QUIC服務的奇怪方式,要求終端事先知道訪問一個特定網站所用的協議及服務的端口。這樣是很是不靈活的,於是它主要用於缺少適當的協議協商機制的狀況下,用於QUIC協議的實驗階段。不久前,有一個稱爲 替代服務(Alternative Services) 的HTTP機制標準化了,其標準規範文檔爲 RFC7838。這種機制容許對一個HTTP資源的訪問,被重定向到另外的一個網絡位置,甚至是以一種不一樣的協議配置來訪問。具體而言,這種機制爲HTTP新增了一個頭部字段Alt-Svc,HTTP服務器能夠經過這個頭部字段,告知客戶端服務器被重定向到的服務的信息,包括協議,端口號等,如:

 Alt-Svc: h2="new.example.org:80"

故障排查

若是你在運行時遇到了問題,則能夠以--v=1參數運行服務器或客戶端。它將提高日誌的verbosity,更多的日誌經常能夠幫助暴露底層的問題。

參考文檔: Playing with QUIC

 


網易雲新用戶大禮包:https://www.163yun.com/gift

本文來自網易雲社區,經做者韓鵬飛受權發佈。

相關文章
相關標籤/搜索