从输入URL到页面展示,这中间发生了什么?net篇

目录

  • TCP/IP 简介
  • http 2.0、quic简介
  • Chromium实现network
  • Chromium 中IPC的调用

TCP/IP 简介

      TCP/IP传输协议,即传输控制/网络协议,也叫作网络通讯协议。它是在网络的使用中的最基本的通信协议。面试过程中常问的问题。

     1、 TCP连接为什么是三次握手,不是2次也不是4次:

    第一次握手:客户端发送TCP包,置SYN标志位为1,将初始序号X,保存在包头的序列号(Seq)里。
    第二次握手:服务端回应确认包,置SYN标志位为1,置ACK为X+1,将初始序列号Y,保存在包头的序列号里。
    第三次握手:客户端对服务端的确认包进行确认,置SYN标志位为0,置ACK为Y+1,置序列号为Z
    如果不进行第三次握手,在服务器对客户端的请求进行回应(第二次握手)后,就会理所当然的认为连接已建立,而如果客户端并没有收到服务器的回应呢?此时,客户端仍认为连接未建立,服务器会对已建立的连接保存必要的资源,如果大量的这种情况,服务器会崩溃。或者第二次握手,跟第一次时间间隔太长,客户端已经将socket断开了,这时候服务器返回第二次ACK,然后开始发送数据,导致服务器资源浪费。

  2、 TCP断开为什么四次挥手    

    先由客户端向服务器端发送一个FIN,请求关闭数据传输。                                                                                                              当服务器接收到客户端的FIN时,向客户端发送一个ACK,其中ack的值等于FIN+SEQ。                                                                然后服务器向客户端发送一个FIN,告诉客户端应用程序关闭。                                                                                                        当客户端收到服务器端的FIN是,回复一个ACK给服务器端。其中ack的值等于FIN+SEQ。

    关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必所有的数据都全部发送给对方了,所以可以不必马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。

http 2.0、quic简介

     Http 2.0

      HTT2.0 的主要目标是通过支持完整的请求与响应复用来减少延迟,通过有效压缩 HTTP 标头字段将协议开销降至最低,同时增加对请求优先级和服务器推送的支持。http2.0推出了如下特性:

  • 二进制分帧,它能把一个数据划分封装为更小更便捷的数据。首先是在单链接多资源方式中,减少了服务端的链接压力,内存占用更少,链接吞吐量更大。这一点可以结合下文中的多路复用来体会。另一方面,由于TCP链接的减少而使网络拥塞状态得以改善,同时慢启动时间的减少。使拥塞和丢包恢复的速度更快。
  • 首部压缩,使报头更紧凑,更快速传输,有利于移动网络环境。减少每次通讯的数据量,使网络拥塞状态得以改善。
  • 流量控制
  • 多路复用,可以并行交错的发送请求和响应,这些请求和响应之间互不影响。只使用一个链接即可并行发送多个请求和响应。消除不必要的延迟,从而减少页面加载的时间。不必再为绕过HTTP1.x限制而多做很多工作
  • 请求优先级,每个流都可以带有一个31bit的优先值:0表示最高优先级;2的31次方-1表示最低优先级。服务器可以根据流的优先级控制资源分配(CPU、内存、宽带),而在响应数据准备好之后,优先将最高优先级的帧发送给客户端。浏览器可以在发现资源时立即分派请求,指定每个流的优先级,让服务器决定最优的响应次序。这样请求就不用排队了,既节省了时间,又最大限度的利用了每个连接。
  • 服务器推送,是一种在客户端请求之前发送数据的机制。服务器可以对一个客户端的请求发送多个响应。如果一个请求是由你的主页发送的,服务器可能会响应主页内容、logo以及样式表,因为他知道客户端会用到这些东西。这样不但减轻了数据传送冗余步骤,也加快了页面响应的速度,提高了用户体验。

Quick

      QUIC 全称 Quick UDP Internet Connection, 是Google制定的一种基于 UDP 协议的低时延互联网应用层协议。Quic 相比现在广泛应用的 tls+http+tcp 协议有如下优势 ,有兴趣的小伙伴可以看看,面试的时候有时候也会问到:      

  • 减少了 TCP 三次握手及 TLS 握手时间。
  • 改进的拥塞控制。
  • 避免队头阻塞的多路复用。
  • 连接迁移。
  • 前向冗余纠错

Chromium实现network

       从所见即所得来看浏览器的流程,简单理解为输入网址,页面加载,页面解析,页面显示。

       浏览器多线程工作,有CrBrowserMain、NetworkService、Chrome_InProcGpuThread、Chrome_InProcRendererThread、VizWebView等线程,每个线程都有自己的任务。我们先来看看NetworkService 线程是如何拿到需要加载网页的地址的。

NetworkService 线程是如何拿到需要加载网页的地址

       在输入框输入网页地址,NavigationControllerImpl.java会调用loadUrl,调用jni函数

    public void loadUrl(LoadUrlParams params) {
        if (mNativeNavigationControllerAndroid != 0) {
            NavigationControllerImplJni.get().loadUrl(mNativeNavigationControllerAndroid,
                    NavigationControllerImpl.this, params.getUrl(), params.getLoadUrlType(),
                    params.getTransitionType(),
                    params.getReferrer() != null ? params.getReferrer().getUrl() : null,
                    params.getReferrer() != null ? params.getReferrer().getPolicy() : 0,
                    params.getUserAgentOverrideOption(), params.getExtraHeadersString(),
                    params.getPostData(), params.getBaseUrl(), params.getVirtualUrlForDataUrl(),
                    params.getDataUrlAsString(), params.getCanLoadLocalResources(),
                    params.getIsRendererInitiated(), params.getShouldReplaceCurrentEntry());
        }
    }

对应JNI函数的实现

void NavigationControllerAndroid::LoadUrl(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jstring>& url,
    jint load_url_type,
    jint transition_type,
    const JavaParamRef<jstring>& j_referrer_url,
    jint referrer_policy,
    jint ua_override_option,
    const JavaParamRef<jstring>& extra_headers,
    const JavaParamRef<jobject>& j_post_data,
    const JavaParamRef<jstring>& base_url_for_data_url,
    const JavaParamRef<jstring>& virtual_url_for_data_url,
    const JavaParamRef<jstring>& data_url_as_string,
    jboolean can_load_local_resources,
    jboolean is_renderer_initiated,
    jboolean should_replace_current_entry) {
......
  navigation_controller_->LoadURLWithParams(params);
}

代码路径:./content/browser/frame_host/navigation_controller_android.cc

void NavigationControllerImpl::LoadURLWithParams(const LoadURLParams& params) {
......
  NavigateWithoutEntry(params);
}

 

void NavigationControllerImpl::NavigateWithoutEntry(
    const LoadURLParams& params) {
......
  node->navigator()->Navigate(std::move(request), reload_type,
                              RestoreType::NONE);

  in_navigate_to_pending_entry_ = false;
}

代码路径:./content/browser/frame_host/navigation_controller_impl.cc

我加了部分stacktrace,要跟流程的,

./content/browser/loader/navigation_url_loader_impl.cc:584		MaybeStartLoader
./content/browser/loader/navigation_url_loader_impl.cc:571		MaybeStartLoader(nullptr 
./content/browser/loader/navigation_url_loader_impl.cc:481		Restart();
./content/browser/loader/navigation_url_loader_impl.cc:1428		request_controller_->Start(
./buildtools/third_party/libc++/trunk/include/memory:3043
./content/browser/loader/navigation_url_loader.cc:46				 std::make_unique<NavigationURLLoaderImpl>
./content/browser/frame_host/navigation_request.cc:2402			loader_ = NavigationURLLoader::Create(
./content/browser/frame_host/navigation_request.cc:3193			OnStartChecksComplete(result);
./content/browser/frame_host/navigation_request.cc:3163			OnWillStartRequestProcessed(result);
./content/browser/frame_host/navigation_throttle_runner.cc:198	delegate_->OnNavigationEventProcessed(event, result);	
./content/browser/frame_host/navigation_throttle_runner.cc:187	InformDelegate(NavigationThrottle::PROCEED);
./content/browser/frame_host/navigation_request.cc:3388			throttle_runner_->ProcessNavigationEvent(
./content/browser/frame_host/navigation_request.cc:1301			WillStartRequest();
./content/browser/frame_host/render_frame_host_impl.cc:2885		frame_tree_node_->navigator()->BeforeUnloadCompleted(
./content/browser/frame_host/render_frame_host_impl.cc:2803		initiator->ProcessBeforeUnloadCompletedFromFrame(
./content/browser/frame_host/render_frame_host_impl.cc:8012		impl->ProcessBeforeUnloadCompleted(
./services/network/public/cpp/wrapper_shared_url_loader_factory.h:66	void CreateLoaderAndStart
./third_party/blink/common/loader/throttling_url_loader.cc:467			start_info_->url_loader_factory->CreateLoaderAndStart(
./third_party/blink/common/loader/throttling_url_loader.cc:392			StartNow();
./third_party/blink/common/loader/throttling_url_loader.cc:224			loader->Start(std::move(factory), routing_id, request_id, options,
./content/browser/loader/navigation_url_loader_impl.cc:669				url_loader_ = blink::ThrottlingURLLoader::CreateLoaderAndStart(

wrapper_shared_url_loader_factory.h的CreateLoaderAndStart还是主线程。接下来就是NetworkService线程开始创建请求,这里的请求并不是指开始网络socket请求。如下是网络中调用的关系。看内核代码还是要有不错的c++功底的,如果基础不好,建议先回去看看基础。例如http_cache_transaction 中的DoLoop只是更改状态机的状态,而实际功能是通过回调函数,执行读取网络包的操作。网络加载此部分的逻辑,也可以参照https://blog.csdn.net/mengxin00100/article/details/106451250这篇博客,写了一个请求URL的生命过程。看内核源码,建议配着原版的资料看,这样更容易理解。很多看一遍不懂,可以看一遍,然后看看代码,再回来看,你就有那种豁然开朗的感觉。

http_cache_transaction.cc调用DoLoop开启网络线程的状态机,是从cache中读取缓存、还是从simple disk获取缓存、还是从网络进行加载都在这里执行,这里需要根据不同场景进行跟踪,才能理清缓存判断。有兴趣的可以小伙伴可以看一看,此部分的状态转换逻辑还算是比较简单的,如下是我加载一个简单页面的状态的切换过程。

这里状态的切换,仅只是其状态机的状态改变,像STATE_SEND_REQUEST下对应的case只是更改状态,并没对应的具体功能,具体功能是通过call_back进行实现。像读取数据、读取数据结束都是通过回调执行,如果是主资源,则通知browser线程,如果非主资源,则通知render线程。网络请求,最终还是会走到TCP socket的连接,数据收发,如果没有网络知识相关基础,建议抽空学习下此部分消息。

如下是调用socket 调用read的流程

./../../net/socket/socket_posix.cc:497									int SocketPosix::DoRead(IOBuffer* buf, int buf_len)
./../../net/socket/socket_posix.cc:302									int rv = DoRead(buf, buf_len);
./../../net/socket/socket_posix.cc:281									int rv = ReadIfReady(
./../../net/socket/tcp_socket_posix.cc:266								int rv = socket_->Read(
./../../net/socket/tcp_client_socket.cc:180								socket_->Read(buf, buf_len, std::move(complete_read_callback));
./../../net/socket/tcp_client_socket.cc:376								return ReadCommon(buf, buf_len, std::move(callback),
./../../net/http/http_stream_parser.cc:681								return stream_socket_->Read(read_buf_.get(), read_buf_->RemainingCapacity
./../../net/http/http_stream_parser.cc:490								result = DoReadHeaders();
./../../net/http/http_stream_parser.cc:363								result = DoLoop(result);
./../../net/http/http_basic_stream.cc:68								return parser()->ReadResponseHeaders(std::move(callback));
./../../net/http/http_network_transaction.cc:1122						return stream_->ReadResponseHeaders(io_callback_);
./../../net/http/http_network_transaction.cc:840						rv = DoReadHeaders();
./../../net/http/http_network_transaction.cc:693						int rv = DoLoop(result);
./../../net/http/http_network_transaction.cc:564						OnIOComplete(OK);
./../../net/http/http_stream_factory_job_controller.cc:242				delegate_->OnStreamReady(used_ssl_config, job->proxy_info(),

如下是调用socket 调用write的流程

./../../net/socket/socket_posix.cc:535								int SocketPosix::DoWrite(IOBuffer* buf, int buf_len) 
./../../net/socket/socket_posix.cc:340								int rv = DoWrite(buf, buf_len);
./../../net/socket/tcp_socket_posix.cc:317							rv = socket_->Write(buf, buf_len, std::move(write_callback),
./../../net/socket/tcp_client_socket.cc:406							int result = socket_->Write(buf, buf_len, std::move(complete_write_callback),
./../../net/http/http_stream_parser.cc:526							return stream_socket_->Write(
./../../net/http/http_stream_parser.cc:464							result = DoSendHeaders();
./../../net/http/http_stream_parser.cc:322							result = DoLoop(OK);	
./../../net/http/http_basic_stream.cc:59							return parser()->SendRequest(
./../../net/http/http_network_transaction.cc:1103					return stream_->SendRequest(request_headers_, &response_, io_callback_);
./../../net/http/http_network_transaction.cc:830					rv = DoSendRequest();
./../../net/http/http_network_transaction.cc:693					int rv = DoLoop(result);
./../../net/http/http_network_transaction.cc:564					OnIOComplete(OK);
./../../net/http/http_stream_factory_job_controller.cc:242			delegate_->OnStreamReady(used_ssl_config, job->proxy_info(),

DNS解析

我们在浏览器中输入的是网址,需要将网址转换成IP地址,才能进行网络请求,这里就涉及到了DNS解析,dns解析最终调用的函数是通过调用getaddrinfo() 获取地址,调用的流程如下。这是一个单独的线程,NetworkService线程通知其进行DNS解析。函数的调用关系很简单,DoLookup函数在host_resolver_manager.cc文件实现,有兴趣的小伙伴可以看看

看浏览器内核代码很大,建议看一个模块之前,先看chromium官网的文档,然后再看代码,用trace、log、stacktrace跟踪流程,然后再看chromium官网文档,最后用自己的语言进行总结,这样才能表示你对此模块了解了些。然后可以再看看极客时间或者CSDN上一些大神对此部分的总结,然后再来看这部分的代码,这样会对此模块有更深入的了解

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值