目录
- 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.youkuaiyun.com/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官网文档,最后用自己的语言进行总结,这样才能表示你对此模块了解了些。然后可以再看看极客时间或者优快云上一些大神对此部分的总结,然后再来看这部分的代码,这样会对此模块有更深入的了解