系统层知识网络

Chromium内核原理之网络栈

2019-02-13  本文已影响21人  码上就说

《Chromium内核原理之blink内核工作解密》
《Chromium内核原理之多进程架构》
《Chromium内核原理之进程间通信(IPC)》
《Chromium内核原理之网络栈》
《Chromium内核原理之网络栈HTTP Cache》
《Chromium内核原理之Preconnect》
《Chromium内核原理之Prerender》
《Chromium内核原理之cronet独立化》

1.内核网络栈概述
2.代码结构
3.网络请求剖析(专注于HTTP)

3.1 URLRequest
3.2 URLRequestHttpJob
3.3 HttpNetworkTransaction
3.4 HttpStreamFactory

3.4.1 Proxy解析
3.4.2 连接管理
3.4.3 Host解析
3.4.4 SSL/TLS

1.内核网络栈概述

网络堆栈主要是单线程跨平台库,主要用于资源获取。它的主要接口是URLRequest和URLRequestContext。 URLRequest,如其名称所示,表示对URL的请求。 URLRequestContext包含完成URL请求所需的所有关联上下文,例如cookie,主机解析器,代理解析器,缓存等。许多URLRequest对象可以共享相同的URLRequestContext。尽管磁盘缓存可以使用专用线程,但是大多数网络对象都不是线程安全的,并且几个组件(主机解析,证书验证等)可能使用未连接的工作线程。由于它主要在单个网络线程上运行,因此不允许阻止网络线程上的操作。因此,我们使用非阻塞操作和异步回调(通常是CompletionCallback)。网络堆栈代码还将大多数操作记录到NetLog,这允许消费者在内存中记录所述操作并以用户友好的格式呈现它以进行调试。

Chromium开发人员编写了网络堆栈,以便:

2.代码结构

3.网络请求剖析(专注于HTTP)

http_network.jpg
3.1 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://请求。还有其他各种工作,例如URLRequestFileJob(file://),URLRequestFtpJob(ftp://),URLRequestDataJob(data://)等。网络堆栈将确定满足请求的相应作业,但它为客户端提供了两种自定义作业创建的方法:URLRequest :: Interceptor和URLRequest :: ProtocolFactory。这些是相当多余的,除了URLRequest :: Interceptor的接口更广泛。随着工作的进行,它将通知URLRequest,URLRequest将根据需要通知URLRequest :: Delegate。

3.2 URLRequestHttpJob

URLRequestHttpJob将首先识别要为HTTP请求设置的cookie,这需要在请求上下文中查询CookieMonster。这可以是异步的,因为CookieMonster可能由sqlite数据库支持。执行此操作后,它将询问请求上下文的HttpTransactionFactory以创建HttpTransaction。通常,HttpCache将被指定为HttpTransactionFactory。 HttpCache将创建一个HttpCache :: Transaction来处理HTTP请求。 HttpCache :: Transaction将首先检查HttpCache(它检查磁盘缓存)以查看缓存条目是否已存在。如果是这样,这意味着响应已经被缓存,或者此缓存条目已经存在网络事务,因此只需从该条目中读取即可。如果缓存条目不存在,那么我们创建它并要求HttpCache的HttpNetworkLayer创建一个HttpNetworkTransaction来为请求提供服务。给HttpNetworkTransaction一个HttpNetworkSession,它包含执行HTTP请求的上下文状态。其中一些状态来自URLRequestContext。

3.3 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;
  ...
};

目前,只有两个主要的HttpStream子类:HttpBasicStream和SpdyHttpStream,尽管我们计划为HTTP流水线创建子类。 HttpBasicStream假设它正在直接读取/写入套接字。 SpdyHttpStream读取和写入SpdyStream。网络事务将调用流上的方法,并在完成时,将调用回调到HttpCache :: Transaction,它将根据需要通知URLRequestHttpJob和URLRequest。对于HTTP路径,http请求和响应的生成和解析将由HttpStreamParser处理。对于SPDY路径,请求和响应解析由SpdyStream和SpdySession处理。根据HTTP响应,HttpNetworkTransaction可能需要执行HTTP身份验证。这可能涉及重新启动网络事务。

3.4 HttpStreamFactory

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

3.4.1 Proxy解析

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

3.4.2 连接管理

在HttpStreamRequest确定了适当的端点(URL端点或代理端点)之后,它需要建立连接。它通过识别适当的“套接字”池并从中请求套接字来实现。请注意,“socket”在这里基本上意味着我们可以读取和写入的内容,以通过网络发送数据。 SSL套接字构建在传输(TCP)套接字之上,并为用户加密/解密原始TCP数据。不同的套接字类型还处理不同的连接设置,HTTP / SOCKS代理,SSL握手等。套接字池设计为分层,因此各种连接设置可以分层在其他套接字之上。 HttpStream可以与实际的底层套接字类型无关,因为它只需要读取和写入套接字。套接字池执行各种功能 - 它们实现每个代理,每个主机和每个进程限制的连接。目前这些设置为每个代理32个套接字,每个目标主机6个套接字,每个进程256个套接字(未正确实现,但足够好)。套接字池还从履行中抽象出套接字请求,从而为我们提供套接字的“后期绑定”。套接字请求可以由新连接的套接字或空闲套接字实现(从先前的http事务重用)。

3.4.3 Host解析

请注意,传输套接字的连接设置不仅需要传输(TCP)握手,还可能需要主机解析。 HostResolverImpl使用getaddrinfo()来执行主机解析,这是一个阻塞调用,因此解析器会在未连接的工作线程上调用这些调用。通常,主机解析通常涉及DNS解析,但可能涉及非DNS命名空间,例如NetBIOS / WINS。请注意,截至编写本文时,我们将并发主机分辨率的数量限制为8,但希望优化此值。 HostResolverImpl还包含一个HostCache,它可以缓存多达1000个主机名。

3.4.4 SSL/TLS

SSL套接字需要执行SSL连接设置以及证书验证。目前,在所有平台上,我们使用NSS的libssl来处理SSL连接逻辑。但是,我们使用特定于平台的API进行证书验证。我们正在逐步使用证书验证缓存,它将多个同一证书的证书验证请求合并到一个证书验证作业中,并将结果缓存一段时间。

SSLClientSocketNSS大致遵循这一系列事件(忽略Snap Start或基于DNSSEC的证书验证等高级功能):

请注意,Chromium有自己的NSS补丁,它支持一些不一定在系统的NSS安装中的高级功能,例如支持NPN,False Start,Snap Start,OCSP装订等。

参考:https://www.chromium.org/developers/design-documents/network-stack

上一篇下一篇

猜你喜欢

热点阅读