- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
这一篇文章,我们核心要聊的事情就是HTTP的对头阻塞问题,因为HTTP的核心改进其实就是在解决HTTP的队头阻塞。所以,我们会讲的理论多一些,而实践其实很少,要学习的头字段也只有一个,我会在最开始就讲完这个头字段,然后我们安心的去学习接下来的理论知识,嗯……这些理论知识很重要.
那我们就先来看看我们本篇要学的这个唯一的头字段是什么吧.
其实在聊这个字段之前,我们要先学一些前置知识才好,但是我想先讲这个字段,后面再带着疑问去学理论,嗯……就这样.
Connection字段其实很好理解,就是用来开启长链接的,长链接的意思就是会重复利用TCP开启的通道,不会在请求一次后关闭。长链接可以这样开启 Connection: keep-alive 。这个东东在HTTP/1.1中是默认开启的,也就是说你啥也不干它就开启,当然,如果你想关闭的话可以这样 Connection: close .
不仅仅如此,客户端和服务器都可以通过 Keep-alive: timeout=value 来限定长链接的超时时间,但是服务器和客户端往往都不一定遵守,约束力并不是很强。大家了解下就好了,另外,一些代理服务器比如Nginx,也会针对该字段有一些特殊的策略,比如该通道多长时间没有发送数据就关闭,比如该通道发送了多少次数据后就关闭等等.
那么下面我们就来看看具体的例子,来实践一下。客户端和服务器的代码都很简单,基本的代码在上一篇文章中都接触过,我就不再复制代码了,可以在 这里 看。我们直接看下请求的结果.
关于Connection的有三个字段,其中Proxy-Connection从词意上来讲就是指代理的通道连接方式。然后你看,Connection是Keep-alive,Keep-Alive字段的超时时间设置为4,也就是默认设置了四秒没有在该通道上传输数据就关闭TCP通道。那怎么验证呢?我们还得用下Wireshark。先请求下数据,然后看看四秒后会不会有TCP的四次挥手断开连接.
我们可以清楚的看到三次握手后(也就是96、97、98三个id的tcp)的HTTP请求,过了四秒,就四次挥手(就是344、345、346、347四次)断开连接了,大家有兴趣自行尝试体验一下.
例子就这么简单~我们接下来要学习理论知识了,这些理论知识很重要,这个例子就当个开胃小菜吧.
我在上面的例子中说到,HTTP/1.1会默认开启长链接,那为什么要开启长链接?什么是长链接?那既然有长链接是不是还有短连接呢?嗯……你听我慢慢说.
我们知道,HTTP/0.9和HTTP/1.0都是十分简单的协议,它的底层是基于TCP的,在每次请求发送前都需要通过三次握手来和服务器建立连接,响应结束后会通过四次挥手断开连接。这就是短连接,在早期的时候也会被称为是无连接的。而这种操作是十分浪费资源的,效率就很低下.
为什么早期的HTTP会是这个样子呢?因为在当时,大家知道大多数的网站都是静态页面,一个页面上能放几个gif图那都是很酷炫的事情了。所以,在这样的场景下,没有那么多的请求需要,这样设计似乎也无可厚非,但是随着互联网页面的极速发展,一个页面可能有几个甚至几十个请求,几百个外部资源文件,每次都开启、关闭、开启、关闭,浪费的资源可不是一点半点.
所以,我们就需要对短连接进行改进,于是长连接出现了.
因为短连接实在是无法适应时代的需要,太浪费了,所以为了解决短连接带来问题,在HTTP/1.1中就增加了持久链接的方法,它的特点就是可以在一个TCP连接上传输多个HTTP请求,只要浏览器或者服务器没有明确断开,那么就会一直保持连接状态。虽然这样做并没有改善TCP的连接效率,但是由于开启和断开的次数少了,把整个开启和断开的时间平均到了多次请求中,每个请求和应答的无效时间就少了很多,从而增加了整体传输的效率.
在目前的浏览器中,同一个域名可以开启六个TCP连接,不过这里稍微要注意的是,其实HTTP规范约定的TCP连接数量是2个,但是各大浏览器厂商觉得肯定不够用,所以在实现层面上来说,每个域名可以开启6个TCP连接,HTTP规范在新的版本RFC7230中也就顺水推舟,约定可以是6到8个连接.
那如果六个TCP连接还是不够用呢?嗯……我们可以多开几个域名,比如a.zaking.com,b.zaking.com,c.zaking.com,每一个域名都指向同一台服务器,说白了就是用数量来解决质量的方式,而这种解决思路也有个高大上的名词,叫做“ 域名分片 ”.
队头阻塞是本篇的重点,也是一件比较有趣的事情,有趣在哪里呢?因为它解决不了。我们下面就来看看什么是队头阻塞.
因为HTTP是基于“请求—应答”模型的,在这个模型的基础上,HTTP规定报文必须是一发一收的,这就形成了一个先进先出的串行队列,如果你不知道什么是队列的话,请看 这里 。既然是队列,就存在一个这样的问题,队列里的请求没有优先级,谁先进来谁就先出去。但是假如某一个排在前面的请求卡住了,没有返回,那后面的所有队列中的请求都要等着那个卡住的请求结束,结果就是我分担了本来不应该由我来承担的时间损耗.
那要怎么解决这个问题呢?诶?你不是说这个问题是解决不了的么?嗯……从规范上,从设计上来说确实无法解决,既然是队列就必然是这样的,但是上有政策下有对策,我大不了多开几个域名呗,多开几个队列,让它堵的可能性小点,你是不是想到了啥?嗯,就是我们上面说到的“ 域名分片 ”技术.
其实很好理解,就好像我们在一个汽车在单车道上跑,堵车的可能性就很大,堵车了我也没办法,只能等前面解决了继续走,但是假设我是6车道,18车道,是不是就能在一定程度上解决这个问题了.
当然,你并没有从根本解决队头阻塞。只是使了点小手段罢了.
我在demo代码里写了点小例子,大家可以点击试试。坦白说我并不知道底层的实现是什么,但是大概能猜到原因.
。
当你发送很多无响应的HTTP请求后,等一会,再点有响应的HTTP请求,你会发现卡死了,我猜就是因为那些无响应的HTTP请求占用全部六个TCP连接。当然,你也可以通过Wireshark来验证这一点。不多说啦~ 。
完了嘛?还没…… 。
在HTTP/1.1中,也曾试图通过“ 管线化 ” 的技术来解决队头阻塞的问题,管线化就是指将多个HTTP请求整批发送给服务器的技术,虽然可以整批发送,但是服务器还是要按照队列的顺序返回结果,得~~~白玩了。所以最后这玩意没啥用,大家了解下就行了 。
我们回顾一下上面的三个部分,发现HTTP/1.1为了优化做了哪些努力, 一个是长连接,一个是每个域名可以同时维护6个TCP长连接,一个是就是域名分片技术。 一共三种,但是这些手段都没有从根本上解决队头阻塞的问题,HTTP数据报文在传输的某一条连接上堵塞了,还是要等待,没办法.
虽然使用这些手段一定程度上缓解了HTTP/1.0和HTTP/0.9所带来的问题,但是其实问题还是不少的,性能还可以进一步的压缩.
其中关于TCP的问题有 慢启动 问题,以及 带宽竞争 问题。在TCP进入到传输数据的状态时,会处于一种递增的状态,就像开车一样,缓慢的从0加速到多少时速,这样做是为了减少网络拥塞,但是有些数据本身就很小,等着你慢慢启动就很浪费时间.
而带宽竞争,则是指当带宽充足的时候,每条连接都会缓慢的增加发送速度,而一旦带宽不足时,这些连接传输数据的速度则会减慢,这样就回带来一个问题,就是优先级的问题,重要资源随着普通资源一起减慢了,真的是很苦恼.
这两个问题是TCP引起的,HTTP想改变也改变不了,只能接受,所以HTTP/3的时候干脆不用TCP了.
但是,队头阻塞的问题,则是HTTP可以进一步优化和解决的,想办法在一定程度上规避TCP的这两个问题,什么意思呢?
HTTP/2的思路是 一个域名只采用一个TCP连接 ,这样就能尽可能的减少TCP的慢启动和带宽竞争问题,就一个TCP连接,你也不用竞争了,就一个TCP连接,你就算启动的很慢,平分到每一个连接好像也还可以。你看,好像所有的解决思路都类似,解决不了就平分.
基于这样的思路,HTTP/2针对队头阻塞的问题提出了 多路复用 的解决方案。什么意思呢,HTTP/2实现了资源的并行请求,也就是你在任何时候都可以发送请求,不用管前一个请求是否堵塞,服务器会在处理好数据后就返回给你.
那,核心的问题来了,多路复用是如何实现的呢?
HTTP/2在HTTP和TCP的中间又加了一层,也就是 二进制分帧层:
。
就像上图这样,其实二进制分帧层属于应用层,这个二进制分帧层做了什么呢?就是把发送的HTTP数据包拆成一个一个带有id的帧,服务器收到这些帧后,会把有同一个id的帧合并成一条完整的信息,那么同样的,服务器发送给客户端的数据也要这样经过二进制分帧层的分帧处理,浏览器会根据对应的id发送给请求的数据源头.
HTTP/2就是通过这样的形式,引入了多路复用的机制,来解决队头阻塞的问题.
看起来似乎很美好了是吧,HTTP/2可以说是HTTP目前为止最完美的解决方案了。但是故事并没有就此停止.
虽然,HTTP/2解决了HTTP的队头阻塞,但是TCP也有队头阻塞,虽然你把HTTP数据包拆分成了一个又一个的帧,但是你还是传输在一条通道上,一旦某一个数据帧丢失了,那TCP就得等丢失的数据包重新传过来才行,卧槽,问题又回到了原点。那咋整?我们改一改TCP协议?
抱歉,你改不了,主要的原因在于僵化,一个是中间设备的僵化,一个是操作系统的僵化.
中间设备其实就是指数据在互联网中传输的过程中,所遇到的各种设备,比如路由器,网关,代理服务器,服务器等等等等,很多很多,这些东西比较硬性,一旦安装软件后很少升级,所以你改了客户端的TCP,这一连串的设备,甚至说全球的设备都要改,你想想,是不是很夸张.
而操作系统僵化,则是因为TCP的核心实现是由操作系统底层来处理的,所以你看,要改TCP就要改操作系统,想想就头大.
所以,由于僵化的原因,TCP改不了。那咋整?嗯……那就不用他了呗,我们用UDP好了.
HTTP/3选择用UDP作为传输协议,并且在UDP和HTTP/3中又加了QUIC层。QUIC层则针对UDP区别于TCP的一些特性进行了处理,从而让UDP的传输像TCP一样完整和安全,并且像HTTP/2那样采用多路复用机制,来解决TCP的队头阻塞.
关于QUIC或者HTTP/3的更多内容,会在后面HTTP/3的部分详细讲解,本篇就不再过多的阐述了.
嗯……本篇结束了~ 。
这篇文章并不长,理论知识稍微多一点,而其中最核心的点就是队头阻塞和多路复用,大家一定要着重学习。那么在本篇的最后,留给大家两个小问题.
最后此篇关于真正“搞”懂HTTP协议07之队头阻塞真的很烦人的文章就讲到这里了,如果你想了解更多关于真正“搞”懂HTTP协议07之队头阻塞真的很烦人的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
对于一个简单的聊天程序,我使用了一个通过 boost::python 包装的 c 库。 使用 PyQT 编写了一个简单的 GUI。接收消息是通过阻塞调用完成的lib说。对于独立刷新的 GUI,通信部分
当我创建以下内容时,我试图创建一个可以被异常终止的线程类(因为我试图让线程等待一个事件): import sys class testThread(threading.Thread): def
我正在用 Haskell 编写服务器,我想在客户端断开连接后显式关闭它们。当我调用 hClose ,线程将阻塞,直到客户端关闭其一侧的句柄。有没有办法让它在不阻塞的情况下关闭? 提前致谢! 最佳答案
这个问题已经有答案了: 已关闭12 年前。 Possible Duplicate: garbage collection Operation 我有几个相关问题。 1.JAVA垃圾收集器运行时,是否占用
我有一个 Angular 函数,它在初始 URL 中查找“列表”参数,如果找到,就会出去获取信息。否则我想获得地理位置。如果存在 URL 参数,我不想获取地理位置。我使用的术语是否正确? constr
我读了很多关于锁定数据库、表和行的文章,但我想要较低的锁定,比如只锁定“操作”,我不知道如何调用它,假设我在 php 中有函数: function update_table() { //que
在我的多线程 mfc 应用程序中,m_view->SetScrollPos 处于阻塞状态并且所有应用程序都被卡住。 View 是在另一个线程中创建的,这是这种行为的原因吗? //SetScrollPo
FreeSwitch 软件在几天内运行良好(~3 - 5 天),然后由于 FreeSwitch 被阻止,新的来电请求被接受!!正在进行的调用继续他们的 session ,他们的调用似乎没有受到影响,但
我有一组按钮,当鼠标悬停在这些按钮上时,它们会改变颜色。这些的 CSS 以这种方式运行: #navsite ul li button { height: 60px; width: 60
由于某些原因,当我调用 WSARecvFrom 时,该函数在接收到某些内容之前不会返回。 _socket = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, N
我了解一些关于 Oracle 阻塞的知识——更新如何阻塞其他更新直到事务完成,写入者如何不阻塞读取者等。 我理解悲观和乐观锁定的概念,以及有关丢失更新等典型银行教科书示例。 我也理解 JDBC 事务隔
在两个代码点之间,我是否可以判断进程是否已被内核抢占,或者更确切地说,当时是否有任何其他代码在同一处理器上运行? //Point A some_type capture = some_capture(
这是我在 Oracle 的面试问题。 有一个堆栈,即使堆栈已满,push 操作也应该等到它完成,即使堆栈为空,pop 操作也应该等到它完成。 我们怎样才能做到这一点? 我的回答 让一个线程做push
我想知道是否有人可以告诉我如何有效地使用循环平铺/循环阻塞进行大型密集矩阵乘法。我正在用 1000x1000 矩阵做C = AB。我按照 Wikipedia 上的循环平铺示例进行操作,但使用平铺得到的
我正在阅读有关绿色线程的内容,并且能够理解这些线程是由 VM 或在运行时创建的,而不是由操作系统创建的,但我无法理解以下语句 When a green thread executes a blocki
我正在创建的 JavaScript API 具有以下结构: var engine = new Engine({ engineName: "TestEngine", engineHost
ChildWindow 是一个模态窗口,但它不会阻塞。有没有办法让它阻塞?我基本上想要一个 ShowDialog() 方法,该方法将调用 ChildWindow.Show() 但在用户关闭 Child
我需要一些关于如何调试 10.6 版本下的 Cocoa 并发问题的指导。我正在将“for”循环转换为使用 NSOperations,但大多数时候,代码只是在循环的某个时刻卡住。我可以在控制台中看到 N
我正在使用 ReportViewer 控件和自定义打印作业工作流程,这给我带来了一些问题。我的代码看起来有点像这样: ids.ForEach(delegate(Guid? guid)
我有以下成功复制文件的代码。但是,它有两个问题: progressBar.setValue() 之后的 System.out.println() 不会打印 0 到 100 之间的间隔(仅打印“0”直到
我是一名优秀的程序员,十分优秀!