使用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

本文来自网易云社区,经做者韩鹏飞受权发布。

相关文章
相关标签/搜索