一次网页请求背后的连接

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a153375250/article/details/53502896

一次网页请求背后的连接

想成为一个优秀的前端,对互联网协议是必须要了解的。本文使用WireShark抓包工具,对一次网页请求背后的TCP连接、HTTP请求进行了详细的展示。对网页请求中浏览器使用的并行连接、持久连接以及TCP连接建立关闭的过程均有所分析讨论,对HTTP如何封装在TCP中进行数据请求以及数据的响应返回也有所展示。供学习交流之用。

TCP简介

本文从传输层的报文开始,下层网络IP层、链路层寻址不在这里讨论范围。可以查看相关资料补充计算机网络知识。

TCP和UDP是传输层的2个协议。

UDP是一个简单的、尽力而为的数据报传输协议,它不能确保可靠传输。提供一种无连接的服务,不可靠但效率高,适合通信量较小或者延迟敏感的传输。

相对而言TCP(Transmission Control Protocol)能够提供可靠传输,这也决定了它高度的复杂性以及较大的系统开销。TCP的复杂性足以用一本专门的教材来讲解,这里我们只对其经典的三次握手、“四次挥手”以及基本报文格式进行简介,达到看懂wireshark抓获的封包的目的。

TCP报文格式


图1

TCP的报文格式如上图所示。孜孜不倦地工作在我们每日的互联网通信中,并将长期保持不变。所以你有大把的时光慢慢记住它。

这里特别需要用到的是红色的标志位部分,分别是:URG,ACK,PSH,RST,SYN,FIN。

其中在三次握手中十分重要的标志位:

ACK:这是非常常用的一个标志位,不仅仅在三次握手以及四次挥手中,该标志位的功能就是用于确认。该位置1时表示正确接收到了对方的报文。

SYN:同步序列号标志。在建立TCP连接时,一定会有一方主动端,一方被动端。网页请求中客户端浏览器就是主动端。当SYN为1而ACK为0时,这就是一个主动请求连接的报文。若对方同意连接,返回一个ACK和SYN均为1的接受报文。可以说它就是建立连接的一个标志。

FIN:在数据发送完毕,希望断开连接时,向对方发送一个FIN位置1的报文,表示我已经发送完毕。

还有一些需要说明的是图中红色的标志位上面的2排–32位序列号以及32位确认号。

先补充说明一点,TCP连接是一个全双工的连接,也就是连接的双方均可以在此连接上进行数据的收发操作。对于大块的数据,TCP的一个报文装不下,需要分成多个报文来发送,然而在实际的网络中,报文很可能经过不同的路由到达对方。报文到达的顺序是没办法保证的因此,32位序列号就用来指明报文的顺序,其具体含义是本报文携带的数据部分,第一个字节的序号。

举例来说,假设现在有3394字节的数据需要传输(每个字节占用一个序号),每个TCP报文最多携带1424字节的数据,同时假设数据从序号1开始发送(这个序号不一定是1,你可以选定从其他序号开始发送,这个序号是在三次握手的过程中协商好的)。那么第一个TCP报文的32位序列号就为1,发送了1424个字节。那么第二个TCP报文的32位序列号就从1425开始,第三个就为1424+1424的下一个2849开始。

刚才说了,TCP是全双工的连接,发送方在发送数据的同时也会接收数据。32位确认号就是告诉对方我已经成功接收的数据序号是多少。我们现在转换角色,假设我们是上面发送3394数据的接收方,我已经正确接收了上面的第一个报文,也就是我正确接收到了1到1424字节序号,那么我在发回的报文中将会把32位确认号写为1425(这里正确接收到了1424序号,但TCP自动将其加1,只是为了方便表示我希望接收的下一个字节序号是1425)。注意,只有在ACK标志位置1时该字段才有意义,ACK置1表明确认接收。还有,收发双方选择的起始发送序号不一定相同,比如我选择从1序号开始发,你可以选择从1024序号开始发,序号只是一个编号而已,彼此根据这个编号进行确认就可以了。

下面我们还会具体分析一次网页请求的报文。

TCP三次握手

TCP是面向连接的协议,建立连接需要进行经典的3次握手,为什么一定要进行三次握手,你可以自行查阅资料。

三次握手的过程如下:


图2

在网页请求中,首先客户浏览器端向服务器发送一个SYN为1的报文,其中包含一个自己选定的初始的32位序列号x,这是第一次握手。

对方收到该请求后,如果同意建立连接,返回一个ACK和SYN标志位均为1的确认报文,将该报文的32位确认号写为x+1,与此同时,自己选定一个初始32位序列号y,这是第二次握手。

第三次握手,发起请求方收到了确认回复之后,返回一个ACK为1的确认报文,其中的32位确认号为y+1。至此连接建立。客户端将从自己设定的x+1序号开始发送数据。

这里我们使用wireshark对进行抓包,该网站是我部署在腾讯云的小主页。打开浏览器,请求该网页。抓到下面的封包:


图3

可以把图放大看,这里红线圈出来的3条就是3次握手的过程。可能看不太清,我下面会逐一对单个报文截图。

我们看到在三次握手中间还夹杂了其他的TCP包,实际上在一次网页请求中,不止建立了一个TCP端到端的连接!我们下面会说,这里我们只需要关注用红色圈起来的3个报文。

第一条:


图4

这里我们暂时不关注其他层。但是从下面第一条蓝色上面的网络IP层可看到

Internet Protocol Version 4, Src:115.156.245.180, Dst: 115.159.81.187

这是网络层的内容,它负责IP寻址,其中封装了手法双方的IP信息。加上Transmission Control Protocol中的内容:

Source Port: 60545
Destination Port: 80

可以看出这是从本机115.156.245.180:60545端口到目标主机115.159.81.187:80端口的连接,http服务默认端口就是80。我们看蓝色的Flags:0x002(SYN),可知这是一个TCP请求连接的报文,也就是第一次握手。注意看Sequence number:0,发送方将初始发送序号x选择成了0。

tip: ip+端口号 就是一个套接字

第二条:


图5

同上,我们可以看到这是一个从目标主机115.159.81.187:80端口发到本机115.156.245.180:60545端口的报文。其中Flags: 0x12(SYN, ACK),SYN、ACK置1,表明对方同意建立连接。这是第二次握手。回复的Acknowledgment number: 1也就是32位确认序列号,为第一次握手的x+1(第一次放松选择的x是0)。同时自己选择的初始发送序号Sequence number:0也为0。

第三条:


图6

这是第三次握手,由本机60545端口发向目标主机80端口一个ACK确认报文。

红色部分,回复的Acknowledgment number为第二次握手服务器方选定的Sequence number+1。至此连接建立。

发送第一个HTTP请求,获取HTML文档

紧接着三次握手建立连接之后,就是一次http请求:


图7

我们可以看到在该请求上面一共抓了6个TCP包,实际上这里建立了2个TCP连接,一共6次握手。暂时不管。我们看建立在上面3次握手的TCP连接的HTTP请求的具体内容:


图8

通过上图我们可以看出,这是一个从本机115.156.245.180:60545端口到目标主机115.159.81.187:80端口的HTTP请求。

实际上在网络传输中,从物理层、链路层、网络层、传输层每一层都进行了封装,每一层都添加了自己这一层的包头。大概像下面这个样子:


图9

简单的说,以太网的标头封装了发送接收者的MAC地址等信息,IP标头中封装了发送接收者的IP信息,用于找到主机所在的网络。

到了绿色的TCP标头就是图1中20字节的报文头。而图1中报文头下的数据部分就是本次HTTP请求的内容。也就是说HTTP请求是封装在TCP的数据部分的。

我们看图8右下角红色框中的内容,我们可以看到16进制数据解码出的字符串,就是本次HTTP请求的Request Header,对比我们在浏览器开发者工具中Network中抓取的http请求的请求头:


图10

接收HTML文档

在上一步中,发送了一个HTTP请求,用来请求HTML文档,接下来的3个TCP报文是服务器端发回的响应,响应的内容就是整个HTML文档:


图11

图中之所以说“这3个TCP报文”是因为,HTTP本身就是封装在TCP报文的数据部分的,图中红框里第三个抓到的报文也是TCP报文。打开报文内容会发现整个HTML文档内容的长度为3394个字节,分装在3个TCP报文中传输,长度分别为1424,1424和546个字节。图中三个报文长度分别为1478,1478以及600是因为图中显示的是包含头部信息的完整报文长度。作为TCP数据部分传输的HTML文档分别是1424,1424以及546字节。

我们可以在wireshark中看到解码的内容信息:

第一条:

第二条:

第三条:

如果你把内容部分组合起来就是下面的html文档以及HTTP返回头信息:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>首页 - 王浴昊的个人网站</title>

    <link rel="stylesheet" media="screen" href="http://wyuhao.com/style.css">
    <link rel="stylesheet" type="text/css" href="http://wyuhao.com/normalize.css">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <meta name="author" content="王浴昊">
    <meta name="description" content="王浴昊的个人网站 首页">
    <meta name="robots" content="all">


    <!--[if lt IE 9]>
    <script src="script/html5shiv.js"></script>
    <![endif]-->
</head>

<body id="css-zen-garden">
<div class="page-wrapper">

    <section class="intro" id="zen-intro">
        <header role="banner">
            <h1>WYH Personal Website</h1>
            <h2>Easy <abbr title="Cascading Style Sheets">AND</abbr> Professional</h2>
        </header>

        <div class="summary" id="zen-summary" role="article">
            <p>A website of sharing my notes of reading and some small demos for <abbr title="Cascading Style Sheets">NEW HANDs</abbr> in front-end learning. Feel free to leave any advice since knowledge sparks during discussion.
            <p>Try to contact me at <a href="/examples/index" title="This page's source HTML code, not to be modified.">673212779@qq.com</a></p>
        </div>

        <div class="preamble" id="zen-preamble" role="article">
            <h3>About</h3>
            <p>在这里分享了一些个人前端学习过程中摘录的一些笔记以及部分小的demo供初学者交流学习使用。</p>
            <p>点击下面的导航进入博客、演示页面。建议及评论可以留言我。</p>
        </div>

        <div class="acknowlegements" role="article">
            <h3>Acknowlegements</h3>
            <p>Special thanks to <a href="http://www.csszengarden.com/">CSS Zen Garden</a> since this website is based on the style of "Mid Century Modern"(from CSS Zen Garden) designed by <a href="http://www.andrewlohman.com/">ANDREW LOHMAN</a>.</p>
        </div>

    </section>

    <aside class="sidebar" role="complementary">
        <div class="wrapper">
            <footer>
                <a href="http://wyuhao.com/notes" title="阅读笔记" class="zen-validate-html">博客</a>
                <a href="http://wyuhao.com/demo" title="演示" class="zen-validate-css">演示</a>
                <a href="http://wyuhao.com/msgboard" title="留言板" class="zen-license">留言</a>
                <a href="http://blog.csdn.net/a153375250/" title="我的CSDN" class="zen-accessibility">CSDN</a>
                <a href="https://github.com/easysir" title="Github" class="zen-github">Github</a>
            </footer>
        </div>
    </aside>


</div>

<!--

    These superfluous divs/spans were originally provided as catch-alls to add extra imagery.
    These days we have full ::before and ::after support, favour using those instead.
    These only remain for historical design compatibility. They might go away one day.

-->
<div class="extra1" role="presentation"></div><div class="extra2" role="presentation"></div><div class="extra3" role="presentation"></div>
<div class="extra4" role="presentation"></div><div class="extra5" role="presentation"></div><div class="extra6" role="presentation"></div>

</body>
</html>

返回头信息参考图10请求头,在这里省略了。

建立了多个TCP连接

以上以HTML文档为例,展示了一次HTTP请求的连接及请求响应过程。实际上,我观察本次网页请求抓取的所有包,我发现其中一共建立了4个TCP连接,在图7中显示了其中的2个连接的握手信息。

这4个连接分别用在了获取页面中引入的样式表、样式表中的字体图片等信息。截取部分依赖:

background: #c879b2 url(icon/icons-3-pack.svg);

@font-face {
    font-family: 'fira_sansregular';
    src: url('font/firasans-regular-webfont.woff2') format('woff2'),
         url('font/firasans-regular-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

<link rel="stylesheet" type="text/css" href="http://wyuhao.com/normalize.css">

实际上,一个页面所引用的所有组件,包括样式表、图片、脚本等都是一次HTTP请求,从Network中可以看出本次页面请求一共进行了包含HTML文档在内的7次HTTP请求:

这些请求分在4个TCP连接中进行。具体在抓包中有所体现,举一个例子来说吧:

这里对页面中引用的normalize.css就是通过另一个TCP连接获取的,通过端口号可以区分,这里是建立在60546端口的TCP连接,不同于上面的60545。

实际上浏览器在这里使用了并行连接用来客服单条TCP连接的空载时间以及带宽限制。就像下面一样:

浏览器会使用并行连接来提升效率,但是需要知道的是,TCP连接本身就会消耗较多的资源。对于一个页面中包含几十上百个HTTP请求的情况,开启几十个TCP连接是不可能的。通常,浏览器会将连接数限制4。本例中,我统计了抓包信息,恰好开启了4个TCP连接。

连接的关闭

HTTP基于TCP,TCP又是面向连接的协议。连接有建立就有关闭,我在抓包的时候发现,当所有请求数据都发送完毕的时候,TCP连接并没有马上关闭。

我们回过头看第一个HTTP请求头信息:

其中有一项:Connection: keep-alive

返回头:

返回头中也有Connection: keep-alive

在HTTP请求中,keep-alive用来告诉服务器我希望建立一个持久连接,也就是在数据发送完毕后不立即关闭该TCP连接。持久连接能够避免重复地打开关闭连接的消耗同时也能回避TCP慢启动特性带来的损耗。如果服务器同意建立该持久连接,就在响应头中同样包含Connection: keep-alive,否则客户端就会认为服务器不支持keep-alive。本连接是一个持久连接。

通常持久连接的响应首部还会包含timeout以及max参数,用来估计服务器将连接保持活跃的时长以及保持持久连接数的上限。注意,该值只是估计,不是承诺。不过这里的响应头中并没有包含此类信息。

本网站我是使用Express框架做的后台,我没有手动设置timeout以及max,也没查到其默认timeout时长,不过我用系统时钟进行了测试,在数据发送完毕后120s左右,TCP连接中断:

上图中包含了所有4个TCP连接断开的4次挥手信息。

我发现,在手动关闭网页时也不会立即断开TCP连接。在连接期间刷新网页会继续使用已经建立的TCP连接。但是,如果关闭浏览器,会立即断开TCP连接。

关于4次挥手

关于TCP连接的4次挥手。就不具体分析报文了,大家可以自己手动去抓包看一看。但是把其原理说清楚。

终止连接的操作,连接的双方都可以主动发起。TCP的全双工连接实际上可以分成2个“半连接”,每个“半连接”都是一个单向的连接。关闭一个“半连接”,另一个还能正常工作。2个“半连接”的关闭一共需要4步。

将设主机A的应用进程先向其TCP发出释放请求,并且不再发送数据。则TCP将发向对方B的报文FIN标志位置1,其32位序列号为前面已经传输过最后一个字节的序号加1。

主机B收到释放连接的通知后随机发回ACK确认,32位确认号为收到的序列号加1,同时通知高层应用进程。这样A到B的连接释放。此后主机B不再接收主机A发来的数据。但若主机B还有一些数据需要发送到主机A,主机A仍然接收并正常返回确认。

主机B到A的连接释放同上。

欢迎访问 我的小站

阅读更多

没有更多推荐了,返回首页