HTTP 指南
HTTP 入门
官方文档:developer.mozilla.org/zh-CN/docs/…
HTTP 基础
1 HTTP 定义
HTTP(超文本传输协议)是一个用于传输超媒体文档(例如 HTML)的应用层协议。
HTTP 的基本特征:
- 简单:HTTP 报文简单易懂
- 可扩展:HTTP 的标头支持自定义扩展。
- 无状态:服务器不会在两个请求之间保留任何数据(状态),但是可以通过 Cookie 创建有状态的回话。
HTTP 能够控制以下内容:
- 资源缓存:通过 cace-control 请求头实现
- 跨域访问:允许请求不同域名下的资源
- 身份认证:通过 Authenticate 请求头实现
- 请求代理:用于跳过内网限制等。
- 状态会话:通过 Cookie 实现
2 HTTP 1.x
HTTP 的消息通过 TCP 连接通道进行传输。在 HTTP/1.x 中,TCP 连接有三种模式:
-
短连接:每次 HTTP 传输都建立一个 TCP 连接通道 。
-
长连接(也叫 keep-alive 连接):一个 TCP 连接通道可以进行多次 HTTP 传输。长连接的优缺点如下:
- 长连接优点:长连接节省了新建 TCP 连接握手的时间,还可以利用 TCP 热连接的性能增强能力。
- 长连接缺点:空闲状态会消耗服务器资源;重负载时可能遭受 DoS 攻击。
- 在 HTTP/1.0 中,默认不使用长连接。如果要设置长连接,需要把 Connection 请求头设为 非 close 的值,比如: retry-after。
- 在 HTTP/1.1 里,默认会使用长连接,Connection 请求头的默认值为 keep-alive。
-
流水线:一条长连接上发出连续的请求,而不用等待应答返回。
- 流水线优点:提升传输性能
- 流水线缺点:可能延迟重要消息(比如:html 文件的请求)
注意事项:
- 浏览器为每个域名最多建立 6 个 TCP连接,如果尝试大于这个数字,就有触发服务器 DoS 保护的风险。
- 域名分片可以建立更多的 TCP 连接,但是不建议使用,因为域名分片这一技术已经被 HTTP/2 连接取代了。
3 HTTP 2.0
HTTP/2 是 HTTP 协议自 1999 年 HTTP1.1 发布后的首个更新,主要基于 SPDY 协议,特点是更快。
3.1 什么是SPDY协议
SPDY是Speedy的昵音,意为“更快”。它是Google开发的基于TCP协议的应用层协议。目标是优化HTTP协议的性能,通过压缩、多路复用和优先级等技术,缩短网页的加载时间并提高安全性。SPDY协议的核心思想是尽量减少TCP连接数。SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。
3.2 二进制分帧层
为了保证HTTP不受影响,那就需要在应用层(HTTP2.0)和传输层(TCP or UDP)之间增加一个二进制分帧层。在二进制分帧层上,HTTP2.0 会将所有传输的信息分为更小的消息和帧,并采用二进制格式编码,其中 HTTP1.x 的首部信息会被封装到 Headers 帧,而 Request Body 则封装到Data帧。
3.3 首部压缩
在 HTTP2.0 中,我们使用了HPACK(HTTP2头部压缩算法)压缩格式对传输的 header 进行编码,减少了header的大小。并在两端维护了索引表,用于记录出现过的 header,后面在传输过程中就可以传输已经记录过的 header 的键名,对端收到数据后就可以通过键名找到对应的值。
3.4 多路复用
HTTP2.0 中,基于二进制分帧层,HTTP2.0 可以在共享 TCP 连接的基础上同时发送请求和响应。HTTP 消息被分解为独立的帧,而不破坏消息本身的语义,交错发出去,在另一端根据流标识符和首部将他们重新组装起来。 通过该技术,可以避免 HTTP 旧版本的队头阻塞问题,极大提高传输性能。
3.5 服务器推送
HTTP2.0 新增的一个强大的新功能,就是服务器可以对一个客户端请求发送多个响应。服务器向客户端推送资源无需客户端明确的请求。
推送的缺点:所有推送的资源都必须遵守同源策略。换句话说,服务器不能随便将第三方资源推送给客户端,而必须是经过双方的确认才行。
4 HTTP 响应
HTTP 响应状态码用来表示一个 HTTP 请求是否成功完成。响应状态码被分为 5 种类型:
- 1xx:代表请求已被收到,需要继续处理。比如,100 代表客户端应当继续发送请求。
- 2xx:代表请求已被服务器成功响应。比如,200 代表请求成功。
- 3xx:代表请求被重定向到后续的请求地址。比如:300 代表请求有多个响应,用户需要从中选择一个;301 代表请求的 URL 被永久改变;302 代表请求的 URL 被暂时改变。
- 4xx:代表客户端发生了错误。比如:400 代表客户端错误;403 代表客户端没有访问齐全想念;404 代表请求的 URL 找不到。
- 5xx:代表服务端发生了错误。比如:500 代表服务器未知错误;504 代表服务器响应超时。
HTTP 安全
1 CSP
CSP(内容安全策略)用于在 HTML 中限制内容(资源文件)的访问域名和运行方式,从而检测并削弱 XSS(跨站脚本)和数据注入等攻击。
CSP 有两种配置方式:
第一种,在 Web 服务器配置 Content-Security-Policy 响应头。注意: 在响应 HTML 文件时配置。
第二种,在 HTML 的 meta 元素中配置 Content-Security-Policy,比如:
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src https://*; child-src 'none';" />
2 CORS
2.1 同源策略
出于安全考虑,浏览器的同源策略禁止 HTML 的某些内容访问不同源的资源,如果需要跨域访问(访问不同源的资源),需要让浏览器 "认可" 资源的安全性。
浏览器的同源策略作用在以下地方:
- 在 JS 中发送 Ajax 请求:通过 Fetch 或 XMLHttpRequest API
- 在 JS 中访问 WebGL 贴图。
- 在 JS 中使用 CanvasRenderingContext2D.drawImage() 访问图片。
- 在 CSS 中通过
@font-face访问 Web 字体 - 在 CSS 中通过 shape-outside 属性引用图片。
注意事项:
-
同源是指协议、域名和端口都相同。
-
同源策略是浏览器的限制,而不是服务器的限制,也就是说服务器响应了请求,只是被浏览器拦截了。
-
同源策略与 CSP 是互补的关系:
- 同源策略一般用于限制动态资源,通过 JS 脚本访问。
- CSP 一般用于限制静态资源,通过 HTML 标签访问。
2.2 跨域访问
CORS(跨源资源共享) 是一种让浏览器允许跨域访问的方式,它通过在服务端配置 CORS 响应头的方式,让浏览器知道当前资源是安全可靠的,从而允许访问。
CORS 在允许跨域访问的同时,还规定可能对服务器数据产生副作用的 HTTP 请求,必须先使用 OPTIONS 方法发起一个预检请求(preflight request),服务器同意访问后,再发起正在的请求。
基于以上规定,我们将跨域访问请求分为两种:
- 简单请求:不会触发预检请求的 HTTP 请求。
- 复杂请求:自动触发预检请求,然后执行真正请求的请求。注意: 预检请求是浏览器自动触发的。
2.2.1 简单请求
执行简单请求的跨域访问的步骤如下:
第一步,判断它是不是简单请求,看它是否满足以下所有标准:
- 请求方法是 get、head 或 post
- 人为设置的请求头是 Accept、Accept-Language、Content-Language、Content-Type 或 Range
- Content-Type 的值是 text/plain、multipart/form-data 或 application/x-www-form-urlencoded
- Range 的值是简单范围,比如:bytes=256- 或 bytes=127-255。
第二步,在服务端设置 CORS 响应头,用于指定允许跨域访问的域名:
Access-Control-Allow-Origin: <origin> | *
对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符“*”,表示允许来自任意源的请求。
2.2.2 复杂请求
执行复杂请求的跨域访问的步骤如下:
第一步,判断它是不是复杂请求,看它是不是简单请求。详情见简单请求的判定。
第二步,在服务端设置 options 方法的 CORS 响应头,用来响应预检请求:
- Access-Control-Allow-Origin:指定允许跨域访问的域名
- Access-Control-Allow-Methods:指定允许跨域访问的请求方法
- Access-Control-Allow-Headers:指定允许跨域访问的请求头
- Access-Control-Max-Age:指定预检请求结果的缓存时间
第三步,在服务端设置真正请求方法的 CORS 响应头,用于指定允许跨域访问的域名。
第四步,浏览自动发送 options 方法的预检请求,包含以下内容:
- Origin:跨域请求的域名
- Access-Control-Request-Method:跨域请求的真正请求方法
- Access-Control-Request-Headers:跨域请求的真正请求头
第五步,浏览器发送真正的 HTTP 请求
2.3 附带身份凭证的请求
一般而言,对于跨域 Ajax 请求,浏览器不会发送身份凭证信息(Cookie)。如果希望发送凭证信息,需要执行如下步骤:
第一步,在 Cookie 中指定 SameSite 的值为 None,并且指定 Secure 属性
第二步,指定 Ajax 请求需要发送身份凭证,比如:
xhr.withCredentials = true;
fetch(url,{credentials:'include'})
第三步,在服务端设置 CORS 响应头:
Access-Control-Allow-Credentials:true
第三步,如果是复杂请求,那么还要满足以下条件:
- 服务器设置 options 方法的 CORS 响应头:
Access-Control-Allow-Credentials:true - 服务器不能将
Access-Control-Allow-Origin的值设为通配符“*” - 服务器不能将
Access-Control-Allow-Headers的值设为通配符“*” - 服务器不能将
Access-Control-Allow-Methods的值设为通配符“*”
3 Cookie
HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是存储在浏览器的小块数据,浏览器在发送请求时会自动携带它,一般有以下用途:
- 会话状态管理:让服务端识别发送 HTTP 请求的用户。
- 个性化设置:在服务端保存个性化设置,以便于在其他浏览器也能使用相同的个性化设置。
- 浏览器行为跟踪:在服务端对浏览器行为进行分析。
Cookie 一般用于存储需要传给服务端的小块数据,其他数据使用 Web storage 或 IndexedDB 存储。
3.1 Cookie 的创建
创建 Cookie 有两种方式:
第一种,在服务端添加 Set-Cookie 响应头,比如:
Set-Cookie: <name1>=<value1>
Set-Cookie: <name2>=<value2>
注意: 在 HTTP 的请求头中,使用 Cookie 标头。
第二种,在浏览器中使用 document.cookie,比如:
document.cookie = "name=tom";
document.cookie = "age=18";
console.log(document.cookie); // name=tom; age=18
3.2 Cookie 的保存
浏览器在收到服务端的 Cookie 或者通过 document 设置的 Cookie 后,会基于域名(Domain) 来保存。如果 Cookie 中没有指定域名,那么默认的域名为当前网站的子域名。
在 Cookie 中指定域名时,可以指定二级域名和子域名,子域名和二级域名的数据会分别保存,并且当前网站(对应子域名)可以访问二级域名中的 Cookie 数据。
注意:通过 document 设置 Cookie 时,不能指定其他网站的域名。
3.3 Cookie 的发送
浏览器发送请求时,会自动携带请求地址的相同域名的 Cookie 。其中,发送的请求不只包含 Ajax 请求,还包含资源请求(比如:请求图片)。
如果浏览器发送的是跨站请求,那么还会根据 SameSite 值来判断是否携带 Cookie。SameSite 包含以下值:
- None:允许跨站请求携带 Cookie。注意: SameSite 设置为 None 时,必须指定 Secure 属性
- Lax:允许部分跨站请求携带 Cookie,是 SameSite 的默认值。注意: 新版谷歌浏览器中,Lax 的效果跟 Strict 差不多。
- Strict:不允许跨站请求携带 Cookie
注意: 浏览器发送跨站请求时,如果携带了 Cookie,那么对应的跨站网页也就可以访问这个 Cookie 了。
如果希望跨站的资源请求携带 Cookie,需要将 SameSite 的值设为 None,并且指定 Secure 属性,比如:
document.cookie = 'name=tom;Domain=127.0.0.1;SameSite=None;Secure;';
注意: 指定 Secure 属性后,HTTP 的跨站请求也会携带 Cookie。
如果希望跨站的 Ajax 请求携带 Cookie,参见 CORS - 附带身份凭证的请求。
3.4 Cookie 的生命周期
Cookie 的生命周期可以通过两种方式定义:
- 会话期 Cookie :在当前的会话结束之后删除,默认就是会话期 Cookie。一般情况下,在浏览器窗口关闭时,当前会话才结束。
- 持久性 Cookie :通过 Expires 指定过期时间,或者通过 Max-Age 指定有效时间。
备注:
- 当 Cookie 的过期时间(
Expires)被设定时,设定的日期和时间只与客户端相关,而不是服务端。 - 每次对用户进行身份验证时,最好重新生成 Cookie,以便于防止 会话固定攻击(session fixation attacks)。
3.5 Cookie 的限制访问
除了 Domain 和 SameSite 属性,还可以通过以下属性限制 Cookie 的访问:
- Path 属性:指定接受 Cookie 的 路径
- Secure 属性:跨站请求时,只允许使用 HTTPS 传输。
- HttpOnly 属性:只允许服务端访问。注意: HttpOnly 属性只能在服务端的响应头中设置。
下面是一个简单的示例:
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
4 X-XSS-Protection
X-XSS-Protection 响应头用于指定网页对于 XSS 攻击的防护策略。如果通过 CSP 禁用了内联脚本,那么可以不需要它,但是在一些不支持 CSP 的浏览器上仍然需要它。
X-XSS-Protection 的可选值如下:
- 0:禁止 XSS 过滤。
- 1:启用 XSS 过滤,默认值是 1。如果检测到跨站脚本攻击,浏览器将清除页面(删除不安全的部分)。
1;mode=block:启用 XSS 过滤。如果检测到攻击,浏览器将不会清除页面,而是阻止页面加载。
5 X-Frame-Options
X-Frame-Options 响应头用于标记网页在 iframe、embed、object 元素中的展示方式,从而避免点击劫持攻击。
X-Frame-Options 的可选值如下:
- DENY:不允许在 frame 中展示
- SAMEORIGIN:只允许在相同域名的页面中展示。
HTTP 优化
1 HTTP 缓存
HTTP 缓存会存储与请求关联的响应,并将存储的响应复用于后续请求。
1.1 缓存的类型
按照格式,缓存的类型可以分为两种:
- 标准缓存:通过 Cache-Control 响应头明确指定的缓存。标准缓存也分为两种:私有缓存和共享缓存。
- 启发式缓存:没有 Cache-Control 响应头,但是可以推断需要缓存的缓存。比如:整整一年没有更新的内容一般都需要缓存。
注意: 启发式缓存是在 Cache-Control 被广泛采用之前的方案,现在基本上都使用标准缓存。
1.1.1 私有缓存
私有缓存是绑定到特定客户端的缓存,通常是浏览器缓存。
如果希望响应内容只进行私有缓存,则必须使用 private 指令。一般个性化内容只进行私有缓存。
Cache-Control: private
注意: 如果响应具有 Authorization 标头,则不能将其存储在私有缓存或共享缓存中,除非 Cache-Control 指定的是 public。
1.1.2 共享缓存
共享缓存位于客户端和服务器之间,可以存储能在用户之间共享的响应。共享缓存可以进一步细分为
- 代理缓存:存储在代理服务器中的缓存。随着 HTTPS 的普及,已经很少使用代理缓存了。
- 托管缓存:存储在专门的托管服务器中的缓存,需要服务开发人员明确部署。示例包括反向代理、CDN 和 service worker 与缓存 API 的组合。
如果希望响应内容只进行托管缓存,则需要使用 no-store 指令:
Cache-Control: no-store
HTTP 缓存没有定义显式删除缓存的方法,但是使用托管缓存,可以通过仪表板操作、API 调用、重新启动等实时删除已经存储的响应。
1.2 缓存的策略
存储的 HTTP 响应有两种状态:fresh 和 stale。fresh 状态通常表示响应仍然有效,可以重复使用,而 stale 状态表示缓存的响应已经过期。
缓存策略用于判断缓存是否有效,按照缓存格式可以分为以下几种:
- age:通过 max-age 响应头指示有效时间
- Expires:通过 Expires 响应头指示过期时间
- Vary:通过 Vary 响应头指示有效内容。
1.2.1 age
age 策略需要服务器在响应时,指定如下响应头:
Cache-Control: max-age=604800
如果响应存储在共享缓存中时,共享缓存有必要通知客户端响应的 age。比如:
Cache-Control: max-age=604800
Age: 86400
1.2.2 Expires
Expires 策略需要服务器在响应时,指定如下响应头:
Expires: Tue, 28 Feb 2022 22:22:22 GMT
如果 Expires 和 age 都可用,则将首选 age。
1.2.3 Vary
同一个 URL 可能响应不同的内容, HTTP 的内容协商机制会根据 Accept、Accept-Language 和 Accept-Encoding 请求标头来响应不同的内容,比如:英文响应与中文响应。
如果希望缓存特定的内容,需要使用 Vary 响应头,比如:
Vary: Accept-Language
1.3 缓存的禁用
如果服务器希望客户端始终获取最新内容,可以在响应头中使用 no-cache,比如:
Cache-Control: no-cache
如果服务器希望客户端禁用缓存,可以在响应头中使用 no-store,比如:
Cache-Control: no-store
注意: no-cache 不会禁用缓存,只是不会复用缓存。
1.4 重新加载
如果浏览器希望重新加载资源文件,可以在发送请求时,添加以下请求头:
Cache-Control: max-age=0
If-None-Match: "deadbeef"
If-Modified-Since: Tue, 22 Feb 2022 20:20:20 GMT
上面的指令中:
首先,max-age=0 用来告诉浏览器,缓存过期了,需要重新验证。
然后,浏览器使用 If-None-Match 通过与服务器的 Etag 比较的方式判断缓存是否需要更新,或者使用 If-Modified-Since 通过与服务器的 过期时间 比较的方式判断缓存是否需要更新
最后,如果判断需要更新,才会执行缓存更新,也就是从服务器获取新的资源。
该行为也在 Fetch 标准中定义,并且可以通过在缓存模式设置为 no-cache 的情况下,在 JavaScript 中调用 fetch() 来重现:
// 注意:“reload”不是正常重新加载的正确模式;“no-cache”才是
fetch("/", { cache: "no-cache" });
注意: 默认情况下,对于缓存内容,浏览器都执行重新加载的逻辑,以防止缓存过期。
1.5 强制重新加载
通过 max-age=0 的方式,需要先判断是否需要更新,如果服务端的资源更新了,但是 Etag 没有更新,那么就会出错。
如果希望跳过判断步骤强制进行更新,可以在发送请求时,添加以下请求头:
Pragma: no-cache
Cache-Control: no-cache
该行为也在 Fetch 标准中定义,并且可以通过在缓存模式设置为 reload 的情况下,在 JavaScript 中调用 fetch() 来重现:
// 注意:“reload”——而不是“no-cache”——是“强制重新加载”的正确模式
fetch("/", { cache: "reload" });
1.6 实际开发
在实际的项目开发中,一般使用 SPA (单页面)的应用模式。在 SPA 中,HTML 只是载体,实际的内容完全通过 JS 和 CSS 进行展示。
为了保证每次发版后,浏览器都能及时更新,一般采取以下的缓存策略:
- HTML 文件:禁用缓存。在 Web 服务器的响应头中添加:
Cache-Control: no-cache - HTML 中的资源文件:使用破坏 URL,也就使用在每次打包时,通过 hash 方式生成新的文件名。
2 HTTP 范围请求
HTTP 协议范围请求允许服务器只发送 HTTP 消息的一部分到客户端。范围请求在传送大的媒体文件,或者与文件下载的断点续传功能搭配使用时非常有用。
范围请求需要服务器支持,如果服务器响应头包含 Accept-Ranges,并且值不为 none ,那就代表服务器支持范围请求。
2.1 范围请求的方式
范围请求需要在请求头中指定 Range ,具体有以下三种方式:
第一种,单一范围请求,用于请求单一内容,比如:
Range: bytes=0-1023
服务器端会返回状态码为 206 的响应:
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1023/146515
Content-Length: 1024
...
(binary content)
第二种,多重范围请求,用于请求多块内容,比如:
Range: bytes=0-50, 100-150
服务器端会返回状态码为 206 、Content 有多个的响应:
HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=3d6b6a416f9b5
Content-Length: 282
--3d6b6a416f9b5
Content-Type: text/html
Content-Range: bytes 0-50/1270
<!doctype html>
<html>
<head>
<title>Example Do
--3d6b6a416f9b5
Content-Type: text/html
Content-Range: bytes 100-150/1270
eta http-equiv="Content-type" content="text/html; c
--3d6b6a416f9b5--
第三种,条件范围请求,用于校验上一个片段是否修改,比如:
If-Range: Wed, 21 Oct 2015 07:28:00 GMT
如果条件范围成功,那么返回 206 的范围内容响应;如果不成立,那么就返回 200 的响应,同时返回整个资源。该首部可以与 Last-Modified 验证器或者 ETag 一起使用,但是二者不能同时使用。
2.2 与分块传输编码的对比
Transfer-Encoding 允许分块编码,一般用于数量很大、但是体积大小未知的情况。
范围请求与分块传输是兼容的,可以单独或搭配使用。
3 HTTP 重定向
HTTP 重定向是指访问资源时,重新指定 URL 并进行访问,一般有以下两种使用场景:
- 站点维护或停机期间的临时重定向。
- 站点更改的永久重定向。
3.1 重定向原理
HTTP 重定向由以 3 开头的状态码,以及 Location 标头实现,其中,状态码是告诉浏览器需要重定向,Location 则是重定向的 URL。
HTTP 重定向可以划分为三个类别:
3.2 重定向的其他方式
实现重定向,除了 HTTP 重定向,还有两种方法:
第一种,使用 HTML 的 meta 元素,比如:
<head>
<meta http-equiv="Refresh" content="0; URL=http://example.com/" />
</head>
第二种,使用 JS 的方法,比如:
window.location = "https://example.com/";
在三种 URL 重定向机制中:
- HTTP 重定向的优先级最高
- meta 重定向的优先级中等
- JS 重定向的优先级最低
HTTP 网络模型
1 定义
计算机网络模型分为 OSI 七层模型和 TCP/IP 概念层模型:
| OSI七层模型 | TCP/IP餐念层模型 | 功能 | TCP/IP协议族 |
|---|---|---|---|
| 应用层 | 应用层 | 文件传输,电子邮件,文件服务,虚拟终端 | TFTP, HTTP, SNMP, PTP, SWTP, DNS, Telnet |
| 表示层 | 数据格式化,代码转换,数据加密 | 没有协议 | |
| 会话层 | 解除或建立与别的接点的联系 | 没有协议 | |
| 传输层 | 传输层 | 提供端对端的接口 | TCP, UDP |
| 网络层 | 网络层 | 为数据包选择路由 | IP, ICMP,RIP,OSPF,BGP,IGMP |
| 数据链路层 | 链路层 | 传输有地址的帧以及错误检测功能 | SLIP, CSLIP, PPP, ARP, RARP, MTU |
| 物理层 | 以二进制数据形式在物理媒体上传输数据 | ISO2110,IEEE802 |
2 TCP 传输
2.1 TCP 连接:三次握手
TCP 通过三次握手建立连接:
-
服务器处于 LISTEN(监听)状态,等待客户端连接。
-
第一次握手。客户端向服务器发送请求建立 TCP 连接的报文:SYN=1 seq=x,然后进入 SYN-SENT(同步已发送)状态。
- SYN=1,表示请求建立 TCP 连接
- seq=x,表示一个随机生成的序列号
-
第二次握手。服务器收到请求后,向客户端回复确认建立 TCP 连接的报文:SYN=1 ACK=1 seq=y ack=x+1,然后进入 SYN-RCVD(同步已接受) 状态。
- SYN=1 ACK=1,表示确认建立 TCP 连接
- seq=y,表示一个随机生成的序列号
- ack=x+1,表示服务器回复的是序列号为 x 的请求
-
第三次握手。客户端收到确认连接的回复后,向服务端回复确认的报文:ACK=1 seq=x+1 ack=y+1,然后进入 ESTABLISHED(连接已建立)状态。服务器收到回复后,进入 ESTABLISHED(连接已建立)状态。
- ACK=1,表示对消息的确认回复
- seq=x+1,表示第二次发送报文的序列号
- ack=y+1,表示客户端回复的是序列号为 y 的请求
为什么是三次握手?
因为需要第三次握手用来判断无效请求:
-
客户端发送连接请求后,由于网络原因,可能迟迟收不到回复。这个时候,客户端会将第一个连接请求置为无效,然后会发送第二个连接请求。
-
服务器收到最快到达的连接请求后,无法判断请求是否有效,所以需要客户端来判断。于是,服务器客户端回复确认信息(第二次握手)。客户端收到确认信息后,会根据序列号 x 判断请求是否有效。
- 如果有效,客户端会回复确认的报文(第三次握手),TCP连接建立。
- 如果无效,客户端会回复取消连接的报文(第三次握手)。服务器收到取消连接的信息后,会撤销第二次握手时建立的半连接通道。
2.2 TCP 断开:四次挥手
TCP 通过四次挥手断开连接:
-
第一次挥手。客户端向服务器发送请求关闭 TCP 连接的报文:FIN=1 seq=u,然后进入 FIN-WAIT1(终止等待1)状态,并停止发送数据。
- FIN=1,表示请求关闭 TCP 连接
- seq=u,表示一个随机生成的序列号
-
第二次挥手。服务器收到请求后,向客户端回复确认的报文:ACK=1 seq=v ack=u+1,然后进入 CLOSE-WAIT(关闭等待)状态。客户端收到确认回复后,进入 FIN-WAIT2(终止等待2)状态。
- ACK=1,表示对消息的确认回复
- seq=v,表示一个随机生成的序列号
- ack=u+1,表示服务器回复的是序列号为 u 的请求
-
第三次挥手。服务器向客户端回复确认关闭 TCP 连接的报文:FIN=1 ACK=1 seq=w ack=u+1,然后进入 LAST-ACK(最终确认)状态。
- FIN=1 ACK=1,表示确认关闭 TCP 连接
- seq=w,表示一个随机生成的序列号
- ack=u+1,表示服务器回复的是序列号为 u 的请求
-
第四次挥手。客户端收到确认关闭的回复后,向服务器回复确认的报文:ACK=1 seq=u+1 ack=w+1,然后进入 TIME-WAIT 状态。等待 2 MSL 的时间后,客户端进入 CLOSED 状态。服务器收到确认回复后,进入CLOSED 状态。
- ACK=1,表示对消息的确认回复
- seq=u+1,表示第二次发送报文的序列号
- ack=w+1,表示服务器回复的是序列号为 w 的请求
为什么 TCP 连接的时候是 3 次,断开的时候却是 4 次?
当服务器收到关闭连接的请求时,可能正在接收数据,并且这个过程可能有点长。这个时候,服务器不能关闭连接,也不能让客户端一直等待,所以先回一个确认消息。客户端收到确认信息后,知道通信没有问题,就安心等待服务器确认关闭连接。所以,多了第二次挥手。
为什么要等待 2 MSL?
-
保证被动关闭方正确关闭:
- MSL 是 Maximum Segment Lifetime的 英文缩写,可译为“最长报文段寿命”,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
- 等待 1 MSL 后,客户端回复确认的报文,如果丢失了。此时,由于超时重传机制,服务器已经重新发送确认关闭 TCP 连接的报文,而这个发送时间不会超过 1 MSL 。
- 等待 2 MSL 内,客户端再次收到确认连接的回复,然后客户端就会再次发送确认的报文,并且重新等待 2 MSL 。
- 所以,等待 2 MSL 可以保证客户端的确认回复被服务端接收。
-
避免历史连接数据:在客户端进入 TIME-WAIT 状态后,TCP 通道上可能还有延迟的数据。等待 2 MSL 可以保证这些数据正常消失。