网络堆栈(转译)

原文路径:https://www.chromium.org/developers/design-documents/network-stack

总览

网络堆栈是主要用于资源获取的主要是单线程的跨平台库。它的主要接口是URLRequest和URLRequestContext。如名称所示,URLRequest表示对URL的请求。 URLRequestContext包含满足URL请求所需的所有关联上下文,例如cookie,主机解析器,代理解析器,缓存等。许多URLRequest对象可能共享相同的URLRequestContext。尽管磁盘缓存可以使用专用线程,并且大多数组件(主机解析,证书验证等)可以使用未连接的工作线程,但大多数网络对象都不是线程安全的。由于它主要在单个网络线程上运行,因此不允许在网络线程上进行任何操作。因此,我们将非阻塞操作与异步回调(通常是CompletionCallback)一起使用。网络堆栈代码还将大多数操作记录到NetLog,这使使用者可以将所述操作记录在内存中,并以用户友好的格式呈现它以用于调试目的。
 
Chromium开发人员编写了网络堆栈,以便:
  • 允许编码为跨平台抽象
  • 提供比高级系统网络库(例如WinHTTP或WinINET)更好的控制
    • 避免系统库中可能存在的错误
    • 为性能优化创造更多机会

代码布局

Anatomy of a Network Request (focused on HTTP)

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类型。 主要作业类型是URLRequestHttpJob,用于完成http://请求。 还有许多其他job,例如URLRequestFileJob(file://),URLRequestFtpJob(ftp://),URLRequestDataJob(data://)等。 网络堆栈将确定合适的job来满足请求,但是它为客户端提供了两种自定义job创建的方式:URLRequest :: Interceptor和URLRequest :: ProtocolFactory。 这些都是多余的,只是URLRequest :: Interceptor的接口更广泛。 随着job的进行,它将通知URLRequest,URLRequest将根据需要通知URLRequest :: Delegate。

URLRequestHttpJob

URLRequestHttpJob将首先识别要为HTTP请求设置的cookie,这需要在请求上下文中查询CookieookMonster。由于Cookie可以由sqlite数据库支持,因此这可以是异步的。这样做之后,它将要求请求上下文的HttpTransactionFactory创建一个HttpTransaction。通常,会将HttpCache指定为HttpTransactionFactory。 HttpCache将创建一个HttpCache :: Transaction来处理HTTP请求。 HttpCache :: Transaction将首先检查HttpCache(检查磁盘缓存),以查看缓存条目是否已存在。如果是这样,则意味着该响应已被缓存,或者该缓存项已经存在网络事务,因此只需从该条目中读取即可。如果缓存条目不存在,则我们创建它并要求HttpCache的HttpNetworkLayer创建一个HttpNetworkTransaction来处理请求。为HttpNetworkTransaction提供了一个HttpNetworkSession,其中包含用于执行HTTP请求的上下文状态。此状态的一部分来自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,该HttpStreamRequest应该处理弄清楚如何建立连接的所有逻辑,一旦建立连接,就将其包装为HttpStream子类,该子类直接与网络对话。
 
class HttpStream {
 public:
  virtual int SendRequest(...) = 0;
  virtual int ReadResponseHeaders(...) = 0;
  virtual int ReadResponseBody(...) = 0;
  ...
};
 
目前,尽管我们计划为HTTP管道创建子类,但主要只有两个HTTPStream子类:HttpBasicStream和SpdyHttpStream。 HttpBasicStream假定它是直接在套接字中读/写。 SpdyHttpStream读取和写入SpdyStream。 网络事务将在流上调用方法,并在完成后将调用回调返回到HttpCache :: Transaction,这将在必要时通知URLRequestHttpJob和URLRequest。 对于HTTP路径,http请求和响应的生成和解析将由HttpStreamParser处理。 对于SPDY路径,请求和响应解析由SpdyStream和SpdySession处理。 根据HTTP响应,HttpNetworkTransaction可能需要执行HTTP身份验证。 这可能涉及重新启动网络事务。

HttpStreamFactory

HttpStreamFactory首先执行代理解析,以确定是否需要代理。 端点设置为URL主机或代理服务器。 HttpStreamFactory然后检查SpdySessionPool以查看我们是否为此端点有可用的SpdySession。 如果不是,则流工厂从适当的池中请求“套接字”(TCP /代理/ SSL /等)。 如果套接字是SSL套接字,则它将检查以查看NPN是否指示协议(可能是SPDY),如果是,则使用指定的协议。 对于SPDY,我们将检查是否已经存在一个SpdySession,如果存在则使用它,否则我们将从此SSL套接字创建一个新的SpdySession,并从SpdySession创建一个SpdyStream,并在其周围包装一个SpdyHttpStream。 对于HTTP,我们只需获取套接字并将其包装在HttpBasicStream中即可。

代理解析

HttpStreamFactory查询ProxyService返回GURL的ProxyInfo。代理服务首先需要检查它是否具有最新的代理配置。如果不是,它将使用ProxyConfigService向系统查询当前代理设置。如果将代理设置设置为无代理或特定代理,则代理解析很简单(我们不返回任何代理或特定代理)。否则,我们需要运行PAC脚本来确定适当的代理(或缺少代理)。如果我们还没有PAC脚本,则代理设置将指示我们应该使用WPAD自动检测,或者将指定自定义PAC URL,然后将使用ProxyScriptFetcher提取PAC脚本。有了PAC脚本后,我们将通过ProxyResolver执行它。请注意,我们使用了一个ShimMultiThreadedProxyResolver对象将PAC脚本执行分派给运行ProxyResolverV8实例的线程。这是因为PAC脚本执行可能会阻止主机解析。因此,为了防止一个停滞的PAC脚本执行阻止其他代理解析,我们允许同时执行多个PAC脚本(注意:V8不是线程安全的,因此我们获取了javascript绑定的锁,因此在一个V8实例被阻止时主机解析,它将释放锁定,以便另一个V8实例可以执行PAC脚本来解析其他URL的代理。
 

连接管理

在HttpStreamRequest确定了适当的终结点(URL终结点或代理终结点)后,需要建立连接。它通过标识适当的“套接字”池并向其请求套接字来实现。请注意,这里的“套接字”基本上是指我们可以读写的东西,可以通过网络发送数据。 SSL套接字建立在传输(TCP)套接字的顶部,并为用户加密/解密原始TCP数据。不同的套接字类型还处理不同的连接设置,例如HTTP / SOCKS代理,SSL握手等。套接字池设计为分层的,因此可以将各种连接设置层叠在其他套接字之上。 HttpStream可以与实际的底层套接字类型无关,因为它只需要读写套接字即可。套接字池执行各种功能-它们按代理,主机和进程限制实现我们的连接。当前,每个代理服务器将它们设置为32个套接字,每个目标主机设置为6个套接字,每个进程设置256个套接字(实现不完全正确,但足够好)。套接字池还从实现中抽象出套接字请求,从而使我们对套接字进行“后期绑定”。套接字请求可以通过新连接的套接字或空闲套接字(从先前的http事务重用)来实现。
 

主机解析

请注意,传输套接字的连接设置不仅需要传输(TCP)握手,而且可能已经需要主机解析。 HostResolverImpl使用包括getaddrinfo()在内的各种机制来执行主机解析,这是一个阻塞调用,因此解析器在未加入的工作线程上调用这些调用。 通常,主机解析通常涉及DNS解析,但可能涉及非DNS名称空间,例如NetBIOS / WINS。 请注意,在撰写本文时,我们将并发主机解析的数量限制为8,但是正在寻求优化此值。 HostResolverImpl还包含一个HostCache,最多可缓存1000个主机名。

SSL/TLS

SSL套接字需要执行SSL连接设置以及证书验证。 除iOS以外,Chromium使用BoringSSL来处理SSL连接逻辑。 但是,我们使用平台特定的API进行证书验证。 我们也正朝着使用证书验证缓存的方向发展,它将把对同一证书的证书验证的多个请求合并到一个证书验证作业中,并将结果缓存一段时间。
 
危险:过时
SSLClientSocketNSS大致遵循以下事件序列(忽略高级功能,例如基于快照的启动或基于DNSSEC的证书验证):
  • Connect()被调用。 我们基于SSLConfig指定的配置或预处理程序宏设置NSS的SSL选项。 然后我们开始握手。
  • 握手完成。 假设我们没有遇到任何错误,我们继续使用CertVerifier验证服务器的证书。 证书验证可能需要一些时间,因此,CertVerifier使用WorkerPool实际调用X509Certificate :: Verify(),这是使用平台特定的API实现的。
请注意,Chromium拥有自己的NSS补丁程序,这些补丁程序支持某些高级功能,这些功能在系统的NSS安装中不一定是必需的,例如对NPN,False Start,Snap Start,OCSP装订等的支持。
 
TODO:谈论网络更改通知
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值